diff options
| author | Bobby <[email protected]> | 2024-09-06 01:13:12 -0400 |
|---|---|---|
| committer | Bobby <[email protected]> | 2024-09-06 01:13:12 -0400 |
| commit | 55d87c6a2c04cd8e1e5ea215dd670f0a8c42ec19 (patch) | |
| tree | 406ba2bc81900bc5ca8636a0599635bae5a78cc4 | |
| parent | 8aa601d5d7a8fa37ec3b905e0b94790f7e6ac191 (diff) | |
| download | yugen-55d87c6a2c04cd8e1e5ea215dd670f0a8c42ec19.tar.xz yugen-55d87c6a2c04cd8e1e5ea215dd670f0a8c42ec19.zip | |
fancier detail page
| -rw-r--r-- | detail/views.py | 12 | ||||
| -rw-r--r-- | static/css/main.css | 49 | ||||
| -rw-r--r-- | templates/detail/detail.html | 450 |
3 files changed, 502 insertions, 9 deletions
diff --git a/detail/views.py b/detail/views.py index f9fbd9c..9b6908c 100644 --- a/detail/views.py +++ b/detail/views.py @@ -5,6 +5,7 @@ import requests from functools import lru_cache from authentication.utils import get_single_anime_mal from watch.utils import get_all_episode_metadata, get_from_redis_cache, store_in_redis_cache +from watch.views import get_seasons_by_zid def index(request): @@ -26,8 +27,19 @@ def detail(request, anime_id): context = { "anime": anime_data, "episodes": anime_episodes, + "related": anime_data.get("relations", []), + "recommendations": anime_data.get("recommendations", []), } + zid = anime_data["episodes"][0]["id"].split("$")[0] if len(anime_data["episodes"]) > 0 else None + if zid: + seasons = get_seasons_by_zid(zid) + if seasons: + context["seasons"] = seasons + + if "nextAiringEpisode" in anime_data: + context["nextAiringEpisode"] = anime_data["nextAiringEpisode"] + if mal_data: context["mal_data"] = mal_data context["mal_episode_range"] = range(1, mal_data["num_episodes"] + 1) diff --git a/static/css/main.css b/static/css/main.css index 3220336..12d8fa4 100644 --- a/static/css/main.css +++ b/static/css/main.css @@ -719,6 +719,11 @@ video { margin-bottom: 2rem; } +.mx-2 { + margin-left: 0.5rem; + margin-right: 0.5rem; +} + .mb-2 { margin-bottom: 0.5rem; } @@ -783,6 +788,10 @@ video { display: inline-flex; } +.grid { + display: grid; +} + .hidden { display: none; } @@ -1048,6 +1057,10 @@ video { list-style-position: inside; } +.grid-cols-1 { + grid-template-columns: repeat(1, minmax(0, 1fr)); +} + .flex-row { flex-direction: row; } @@ -1114,6 +1127,12 @@ video { margin-bottom: calc(0.5rem * var(--tw-space-y-reverse)); } +.space-x-2 > :not([hidden]) ~ :not([hidden]) { + --tw-space-x-reverse: 0; + margin-right: calc(0.5rem * var(--tw-space-x-reverse)); + margin-left: calc(0.5rem * calc(1 - var(--tw-space-x-reverse))); +} + .overflow-auto { overflow: auto; } @@ -1593,6 +1612,11 @@ video { background-color: rgb(161 98 7 / var(--tw-bg-opacity)); } +.bg-gray-800 { + --tw-bg-opacity: 1; + background-color: rgb(31 41 55 / var(--tw-bg-opacity)); +} + .bg-opacity-10 { --tw-bg-opacity: 0.1; } @@ -2436,6 +2460,11 @@ main { background-color: rgb(161 98 7 / var(--tw-bg-opacity)); } +.hover\:bg-gray-700:hover { + --tw-bg-opacity: 1; + background-color: rgb(55 65 81 / var(--tw-bg-opacity)); +} + .hover\:bg-opacity-30:hover { --tw-bg-opacity: 0.3; } @@ -2631,6 +2660,10 @@ main { height: 39vw; } + .lg\:h-28 { + height: 7rem; + } + .lg\:max-h-24 { max-height: 6rem; } @@ -2667,6 +2700,22 @@ main { width: 15rem; } + .lg\:w-48 { + width: 12rem; + } + + .lg\:w-auto { + width: auto; + } + + .lg\:flex-shrink-0 { + flex-shrink: 0; + } + + .lg\:grid-cols-2 { + grid-template-columns: repeat(2, minmax(0, 1fr)); + } + .lg\:flex-row { flex-direction: row; } diff --git a/templates/detail/detail.html b/templates/detail/detail.html index 9f97aa9..5ae62ee 100644 --- a/templates/detail/detail.html +++ b/templates/detail/detail.html @@ -4,13 +4,20 @@ <style> @media (max-width: 640px) { .detail-section { - top: 8rem; + top: 0rem; left: -8.5rem; width: 100vw; } + + .w-fix { + position: relative; + left: -8rem; + width: calc(100vw - 1rem); + } } </style> <link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/animate.css/4.1.1/animate.min.css"/> +<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/swiper@11/swiper-bundle.min.css"/> {% endblock css %} {% block content %} <div style="background-image: url('{{ anime.cover }}')" class="relative bg-center bg-cover h-32 lg:h-96 my-4 rounded-lg" > @@ -170,7 +177,7 @@ {{ anime.description|strip_html }} </p> {% if mal_data %} - <section class="flex flex-col lg:flex-row gap-4 my-2"> + <section class="flex flex-col lg:flex-row gap-4 my-2 w-fix"> <div class="flex-1 flex flex-col gap-2"> <span>Update MAL Status</span> <div class="relative w-full custom-select text-sm" data-select="status"> @@ -264,7 +271,32 @@ </div> </section> {% endif %} - <section class="w-full flex flex-col items-center justify-center p-2 relative detail-section"> + {% if seasons %} + <div class="my-8 w-fix"> + <h2 class="text-xl font-bold text-white uppercase flex flex-row items-center gap-1 mb-4 "> + Seasons + </h2> + <div class="flex flex-row gap-2 mt-4 overflow-x-auto no-scrollbar swiper seasonSwiper"> + <div class="swiper-wrapper"> + {% for season in seasons %} + <a href="{% url "watch:watch_via_zid" season.id %}" class="group rounded-lg aspect-video h-48 relative flex-shrink-0 overflow-hidden swiper-slide + {% if season.isCurrent %}border-4 border-{{ user.preferences.accent_colour }}-600{% endif %}"> + <div class="absolute inset-0 bg-center bg-cover transition-transform group-hover:scale-110" style="background-image: url('{{ season.poster }}')"></div> + <div class="absolute inset-0 bg-{{ user.preferences.accent_colour }}-600 opacity-0 group-hover:opacity-30 transition-opacity"></div> + <div class="absolute inset-0" style="background: linear-gradient(45deg, rgb(8, 8, 8) 15%, transparent 60%), linear-gradient(0deg, rgb(8, 8, 8) 0%, transparent 60%);"></div> + <div class="flex flex-col justify-end h-full p-2 relative z-10"> + <h1 class="text-xl font-bold truncate max-w-full overflow-hidden text-ellipsis whitespace-nowrap"> + {{ season.name }} + </h1> + <h2 class="font-bold truncate max-w-full overflow-hidden text-ellipsis whitespace-nowrap">{{ season.title }}</h2> + </div> + </a> + {% endfor %} + </div> + </div> + </div> + {% endif %} + <section class="w-full flex flex-col items-center justify-center p-2 relative detail-section" id="dss"> <section class="inline-flex w-max flex-row gap-4 rounded-full mb-8 bg-white bg-opacity-10 mx-auto"> <button class="flex flex-row items-center focus:outline-none gap-2 text-sm font-bold py-2 px-4 rounded-full category-switch" data-target="characters"> <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="currentColor" class="size-4"> @@ -325,23 +357,82 @@ </section> <section id="episodes" class="w-full flex-wrap flex justify-start"> {% for episode in episodes.episodes %} - <a href="{% url "watch:watch_episode" anime.id episode.number %}" class="w-full lg:w-1/2 px-2 mb-2"> + <a href="{% url "watch:watch_episode" anime.id episode.number %}" class="w-full lg:w-1/2 px-2 mb-2 episode-item"> <div class="flex flex-col lg:flex-row w-full bg-neutral-950 rounded hover:bg-{{ user.preferences.accent_colour }}-600 hover:bg-opacity-30 p-2 gap-4"> - <div class="flex-shrink-0"> - <img src="{% if episode.metadata.image %}{{ episode.metadata.image }}{% else %}{{ anime.cover}}{% endif %}" alt="{{ episode.title }}" class="w-48 h-28 object-cover rounded-lg"/> + <div class="w-full lg:w-auto lg:flex-shrink-0"> + <img src="{% if episode.metadata.image %}{{ episode.metadata.image }}{% else %}{{ anime.cover}}{% endif %}" alt="{{ episode.title }}" class="w-full lg:w-48 lg:h-28 object-cover rounded-lg"/> </div> <div class="flex flex-col gap-2"> - <h2 class="font-bold">{% if episode.metadata.title %}{{ episode.metadata.title }}{% else %}{{ episode.title }}{% endif %}</h2> + <h2 class="font-bold">{{episode.number}}. {% if episode.metadata.title %}{{ episode.metadata.title }}{% else %}{{ episode.title }}{% endif %}</h2> <p class="text-sm">{{ episode.metadata.description|truncatewords:50 }}</p> </div> </div> </a> {% endfor %} {% if not episodes %} - <div class="w-full h-96 flex items-center justify-center"> - <span>No Episodes Available</span> + <div class="w-full h-96 flex flex-col gap-2 items-center justify-center"> + <p>No Episodes Available</p> </div> {% endif %} + {% if nextAiringEpisode %} + <p class="w-full text-center my-2" id="nextAiringEpisodeMessage"></p> + <script> + const nextAiringEpisode = { + airingTime: {{ nextAiringEpisode.airingTime }}, + timeUntilAiring: {{ nextAiringEpisode.timeUntilAiring }}, + episode: {{ nextAiringEpisode.episode }} + }; + + function formatNextEpisode(nextAiringEpisode) { + const airingTime = nextAiringEpisode.airingTime * 1000; // Convert to milliseconds + const episodeNumber = nextAiringEpisode.episode; + + function updateCountdown() { + const now = new Date().getTime(); + const timeUntilAiring = airingTime - now; + + if (timeUntilAiring <= 0) { + return `Episode ${episodeNumber} has aired.`; + } + + const days = Math.floor(timeUntilAiring / (1000 * 60 * 60 * 24)); + const hours = Math.floor((timeUntilAiring % (1000 * 60 * 60 * 24)) / (1000 * 60 * 60)); + const minutes = Math.floor((timeUntilAiring % (1000 * 60 * 60)) / (1000 * 60)); + const seconds = Math.floor((timeUntilAiring % (1000 * 60)) / 1000); + + const airingDate = new Date(airingTime); + const formattedDate = airingDate.toLocaleDateString('en-US', { + weekday: 'short', + year: 'numeric', + month: 'short', + day: 'numeric', + }); + const formattedTime = airingDate.toLocaleTimeString('en-US', { + hour: 'numeric', + minute: '2-digit', + second: '2-digit', + hour12: true + }); + + return `Episode <strong>${episodeNumber}</strong> will air on ${formattedDate} at ${formattedTime} ` + + `<strong class="text-{{ user.preferences.accent_colour }}-600">(${days} days, ${hours} hours, ${minutes} minutes, ${seconds} seconds)</strong>`; + } + + const countdownElement = document.getElementById('nextAiringEpisodeMessage'); + countdownElement.innerHTML = updateCountdown(); + + // Update countdown every second + setInterval(() => { + countdownElement.innerHTML = updateCountdown(); + }, 1000); + } + + // Call the function when the page loads + document.addEventListener('DOMContentLoaded', function() { + formatNextEpisode(nextAiringEpisode); + }); + </script> + {% endif %} </section> <section id="trailer" class="w-full hidden"> {% if anime.trailer and anime.trailer.site == "youtube" %} @@ -353,6 +444,224 @@ {% endif %} </section> </section> + <div class="w-full w-fix"> + {% if related %} + <div class="text-xl font-bold text-white uppercase flex flex-row items-center gap-1 mb-4"> + <svg stroke="currentColor" fill="currentColor" stroke-width="0" viewBox="0 0 256 512" class="size-4" xmlns="http://www.w3.org/2000/svg"><path d="M224.3 273l-136 136c-9.4 9.4-24.6 9.4-33.9 0l-22.6-22.6c-9.4-9.4-9.4-24.6 0-33.9l96.4-96.4-96.4-96.4c-9.4-9.4-9.4-24.6 0-33.9L54.3 103c9.4-9.4 24.6-9.4 33.9 0l136 136c9.5 9.4 9.5 24.6.1 34z"></path></svg> + <span>Related</span> + </div> + <div class="grid grid-cols-1 lg:grid-cols-2 gap-2"> + {% for related in related|slice:":5" %} + {% if related.type == "MANGA" or related.type == "NOVEL" %} + <div onClick="showToast('{% if related.type == "MANGA" %}Manga{% else %}Novel{% endif %} reading is not supported yet!', false)" class="cursor-pointer flex flex-row w-full gap-4 bg-white bg-opacity-10 p-2 rounded hover:bg-{{ user.preferences.accent_colour }}-600 hover:bg-opacity-30"> + <img src="{{ related.image }}" alt="{{ related.title.english }}" class="rounded-lg w-12 h-16 object-cover"/> + <div class="flex flex-col gap-2"> + <span class="font-bold flex gap-2 flex-row items-center"> + {% if related.status == "Ongoing" %} + <span class="text-green-500"> + <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="currentColor" class="size-2 sm:size-3"> + <circle cx="12" cy="12" r="12" /> + </svg> + </span> + {% elif related.status == "Not yet aired" %} + <span class="text-yellow-500"> + <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="currentColor" class="size-2 sm:size-3"> + <circle cx="12" cy="12" r="12" /> + </svg> + </span> + {% else %} + <span class="text-blue-500"> + <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="currentColor" class="size-2 sm:size-3"> + <circle cx="12" cy="12" r="12" /> + </svg> + </span> + {% endif %} + <span> + {% if user.preferences.title_language == "english" and related.title.english %} + {{ related.title.english }} + {% elif user.preferences.title_language == "native" and related.title.native %} + {{ related.title.native }} + {% else %} + {{ related.title.romaji }} + {% endif %} + </span> + </span> + <span class="text-xs sm:text-sm font-bold flex gap-1 flex-row items-start"> + <span class="text-xs font-bold bg-white bg-opacity-10 p-1 rounded flex items-center gap-1"> + <svg stroke="currentColor" fill="none" stroke-width="2" viewBox="0 0 24 24" stroke-linecap="round" stroke-linejoin="round" height="1em" width="1em" xmlns="http://www.w3.org/2000/svg" class="mr-1"> + <path d="M3.604 7.197l7.138 -3.109a.96 .96 0 0 1 1.27 .527l4.924 11.902a1 1 0 0 1 -.514 1.304l-7.137 3.109a.96 .96 0 0 1 -1.271 -.527l-4.924 -11.903a1 1 0 0 1 .514 -1.304z"></path> + <path d="M15 4h1a1 1 0 0 1 1 1v3.5"></path> + <path d="M20 6c.264 .112 .52 .217 .768 .315a1 1 0 0 1 .53 1.311l-2.298 5.374"></path> + </svg> + {{ related.type }} + </span> + <span class="text-xs font-bold bg-white bg-opacity-10 p-1 rounded flex items-center gap-1"> + <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="currentColor" class="size-4"> + <path d="m11.645 20.91-.007-.003-.022-.012a15.247 15.247 0 0 1-.383-.218 25.18 25.18 0 0 1-4.244-3.17C4.688 15.36 2.25 12.174 2.25 8.25 2.25 5.322 4.714 3 7.688 3A5.5 5.5 0 0 1 12 5.052 5.5 5.5 0 0 1 16.313 3c2.973 0 5.437 2.322 5.437 5.25 0 3.925-2.438 7.111-4.739 9.256a25.175 25.175 0 0 1-4.244 3.17 15.247 15.247 0 0 1-.383.219l-.022.012-.007.004-.003.001a.752.752 0 0 1-.704 0l-.003-.001Z" /> + </svg> + {{ related.relationType }} + </span> + {% if related.rating %} + <span class="text-xs font-bold bg-white bg-opacity-10 p-1 rounded flex items-center gap-1"> + <svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke-width="1.5" stroke="currentColor" class="size-4"> + <path stroke-linecap="round" stroke-linejoin="round" d="M11.48 3.499a.562.562 0 0 1 1.04 0l2.125 5.111a.563.563 0 0 0 .475.345l5.518.442c.499.04.701.663.321.988l-4.204 3.602a.563.563 0 0 0-.182.557l1.285 5.385a.562.562 0 0 1-.84.61l-4.725-2.885a.562.562 0 0 0-.586 0L6.982 20.54a.562.562 0 0 1-.84-.61l1.285-5.386a.562.562 0 0 0-.182-.557l-4.204-3.602a.562.562 0 0 1 .321-.988l5.518-.442a.563.563 0 0 0 .475-.345L11.48 3.5Z"/> + </svg> + {{ related.rating }} + </span> + {% endif %} + </span> + </div> + </div> + {% endif %} + {% if related.type == "TV" or related.type == "MOVIE" or related.type == "OVA" or related.type == "ONA" or related.type == "SPECIAL" or related.type == "TV_SHORT" %} + <a href="{% if not viaMal %}{% url 'watch:watch' related.id %}{% else %}{% url "watch:watch_via_zid" related.zid %}{% endif %}" class="flex flex-row w-full gap-4 bg-white bg-opacity-10 p-2 rounded hover:bg-{{ user.preferences.accent_colour }}-600 hover:bg-opacity-30"> + <img src="{{ related.image }}" alt="{{ related.title.english }}" class="rounded-lg w-12 h-16 object-cover"/> + <div class="flex flex-col gap-2"> + <span class="font-bold flex gap-2 flex-row items-center"> + {% if related.status == "Ongoing" %} + <span class="text-green-500"> + <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="currentColor" class="size-2 sm:size-3"> + <circle cx="12" cy="12" r="12" /> + </svg> + </span> + {% elif related.status == "Not yet aired" %} + <span class="text-yellow-500"> + <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="currentColor" class="size-2 sm:size-3"> + <circle cx="12" cy="12" r="12" /> + </svg> + </span> + {% else %} + <span class="text-blue-500"> + <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="currentColor" class="size-2 sm:size-3"> + <circle cx="12" cy="12" r="12" /> + </svg> + </span> + {% endif %} + <span> + {% if user.preferences.title_language == "english" and related.title.english %} + {{ related.title.english }} + {% elif user.preferences.title_language == "native" and related.title.native %} + {{ related.title.native }} + {% else %} + {{ related.title.romaji }} + {% endif %} + </span> + </span> + <span class="text-xs sm:text-sm font-bold flex gap-1 flex-row items-start"> + <span class="text-xs font-bold bg-white bg-opacity-10 p-1 rounded flex items-center gap-1"> + <svg stroke="currentColor" fill="none" stroke-width="2" viewBox="0 0 24 24" stroke-linecap="round" stroke-linejoin="round" height="1em" width="1em" xmlns="http://www.w3.org/2000/svg" class="mr-1"> + <path d="M3.604 7.197l7.138 -3.109a.96 .96 0 0 1 1.27 .527l4.924 11.902a1 1 0 0 1 -.514 1.304l-7.137 3.109a.96 .96 0 0 1 -1.271 -.527l-4.924 -11.903a1 1 0 0 1 .514 -1.304z"></path> + <path d="M15 4h1a1 1 0 0 1 1 1v3.5"></path> + <path d="M20 6c.264 .112 .52 .217 .768 .315a1 1 0 0 1 .53 1.311l-2.298 5.374"></path> + </svg> + {{ related.type }} + </span> + {% if related.rating %} + <span class="text-xs font-bold bg-white bg-opacity-10 p-1 rounded flex items-center gap-1"> + <svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke-width="1.5" stroke="currentColor" class="size-4"> + <path stroke-linecap="round" stroke-linejoin="round" d="M11.48 3.499a.562.562 0 0 1 1.04 0l2.125 5.111a.563.563 0 0 0 .475.345l5.518.442c.499.04.701.663.321.988l-4.204 3.602a.563.563 0 0 0-.182.557l1.285 5.385a.562.562 0 0 1-.84.61l-4.725-2.885a.562.562 0 0 0-.586 0L6.982 20.54a.562.562 0 0 1-.84-.61l1.285-5.386a.562.562 0 0 0-.182-.557l-4.204-3.602a.562.562 0 0 1 .321-.988l5.518-.442a.563.563 0 0 0 .475-.345L11.48 3.5Z"/> + </svg> + {{ related.rating }} + </span> + {% endif %} + {% if related.relationType %} + <span class="text-xs font-bold bg-white bg-opacity-10 p-1 rounded flex items-center gap-1"> + <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="currentColor" class="size-4"> + <path d="m11.645 20.91-.007-.003-.022-.012a15.247 15.247 0 0 1-.383-.218 25.18 25.18 0 0 1-4.244-3.17C4.688 15.36 2.25 12.174 2.25 8.25 2.25 5.322 4.714 3 7.688 3A5.5 5.5 0 0 1 12 5.052 5.5 5.5 0 0 1 16.313 3c2.973 0 5.437 2.322 5.437 5.25 0 3.925-2.438 7.111-4.739 9.256a25.175 25.175 0 0 1-4.244 3.17 15.247 15.247 0 0 1-.383.219l-.022.012-.007.004-.003.001a.752.752 0 0 1-.704 0l-.003-.001Z" /> + </svg> + {{ related.relationType }} + </span> + {% endif %} + {% if related.episodes %} + <span class="text-xs font-bold bg-white bg-opacity-10 p-1 rounded flex items-center gap-1"> + <svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke-width="1.5" stroke="currentColor" class="size-4"> + <path stroke-linecap="round" stroke-linejoin="round" d="M5.25 8.25h15m-16.5 7.5h15m-1.8-13.5-3.9 19.5m-2.1-19.5-3.9 19.5"/> + </svg> + {{ related.episodes }} + </span> + {% endif %} + </span> + </div> + </a> + {% endif %} + {% endfor %} + </div> + {% endif %} + {% if recommendations %} + <div class="text-xl font-bold text-white uppercase flex flex-row items-center gap-1 my-4"> + <svg stroke="currentColor" fill="currentColor" stroke-width="0" viewBox="0 0 256 512" class="size-4" xmlns="http://www.w3.org/2000/svg"><path d="M224.3 273l-136 136c-9.4 9.4-24.6 9.4-33.9 0l-22.6-22.6c-9.4-9.4-9.4-24.6 0-33.9l96.4-96.4-96.4-96.4c-9.4-9.4-9.4-24.6 0-33.9L54.3 103c9.4-9.4 24.6-9.4 33.9 0l136 136c9.5 9.4 9.5 24.6.1 34z"></path></svg> + <span>Recommendations</span> + </div> + <div class="grid grid-cols-1 lg:grid-cols-2 gap-2"> + {% for recommendation in recommendations|slice:":10" %} + {% if recommendation.id or recommendation.zid %} + <a href="{% if not viaMal %}{% url 'watch:watch' recommendation.id %}{% else %}{% url "watch:watch_via_zid" recommendation.zid %}{% endif %}" class="flex flex-row w-full gap-4 bg-white bg-opacity-10 p-2 rounded hover:bg-{{ user.preferences.accent_colour }}-600 hover:bg-opacity-30"> + <img src="{{ recommendation.image }}" alt="{{ recommendation.title.english }}" class="rounded-lg w-12 h-16 object-cover"/> + <div class="flex flex-col gap-2 max-w-[calc(100%-4rem)]"> + <span class="font-bold flex gap-2 flex-row items-center"> + {% if recommendation.status == "Ongoing" %} + <span class="text-green-500"> + <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="currentColor" class="size-2 sm:size-3"> + <circle cx="12" cy="12" r="12" /> + </svg> + </span> + {% elif recommendation.status == "Not yet aired" %} + <span class="text-yellow-500"> + <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="currentColor" class="size-2 sm:size-3"> + <circle cx="12" cy="12" r="12" /> + </svg> + </span> + {% else %} + <span class="text-blue-500"> + <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="currentColor" class="size-2 sm:size-3"> + <circle cx="12" cy="12" r="12" /> + </svg> + </span> + {% endif %} + <span class="truncate max-w-full overflow-hidden text-ellipsis whitespace-nowrap"> + {% if user.preferences.title_language == "english" and recommendation.title.english %} + {{ recommendation.title.english }} + {% elif user.preferences.title_language == "native" and recommendation.title.native %} + {{ recommendation.title.native }} + {% else %} + {{ recommendation.title.romaji }} + {% endif %} + </span> + </span> + <span class="text-xs sm:text-sm font-bold flex gap-1 flex-row items-start"> + <span class="text-xs font-bold bg-white bg-opacity-10 p-1 rounded flex items-center gap-1"> + <svg stroke="currentColor" fill="none" stroke-width="2" viewBox="0 0 24 24" stroke-linecap="round" stroke-linejoin="round" height="1em" width="1em" xmlns="http://www.w3.org/2000/svg" class="mr-1"> + <path d="M3.604 7.197l7.138 -3.109a.96 .96 0 0 1 1.27 .527l4.924 11.902a1 1 0 0 1 -.514 1.304l-7.137 3.109a.96 .96 0 0 1 -1.271 -.527l-4.924 -11.903a1 1 0 0 1 .514 -1.304z"></path> + <path d="M15 4h1a1 1 0 0 1 1 1v3.5"></path> + <path d="M20 6c.264 .112 .52 .217 .768 .315a1 1 0 0 1 .53 1.311l-2.298 5.374"></path> + </svg> + {{ recommendation.type }} + </span> + {% if recommendation.rating %} + <span class="text-xs font-bold bg-white bg-opacity-10 p-1 rounded flex items-center gap-1"> + <svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke-width="1.5" stroke="currentColor" class="size-4"> + <path stroke-linecap="round" stroke-linejoin="round" d="M11.48 3.499a.562.562 0 0 1 1.04 0l2.125 5.111a.563.563 0 0 0 .475.345l5.518.442c.499.04.701.663.321.988l-4.204 3.602a + .563.563 0 0 0-.182.557l1.285 5.385a.562.562 0 0 1-.84.61l-4.725-2.885a.562.562 0 0 0-.586 0L6.982 20.54a.562.562 0 0 1-.84-.61l1.285-5.386a.562.562 0 0 0-.182-.557l-4.204-3.602a.562.562 0 0 1 .321-.988l5.518-.442a.563.563 0 0 0 .475-.345L11.48 3.5Z"/> + </svg> + {{ recommendation.rating }} + </span> + {% if recommendation.episodes %} + <span class="text-xs font-bold bg-white bg-opacity-10 p-1 rounded flex items-center gap-1"> + <svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke-width="1.5" stroke="currentColor" class="size-4"> + <path stroke-linecap="round" stroke-linejoin="round" d="M5.25 8.25h15m-16.5 7.5h15m-1.8-13.5-3.9 19.5m-2.1-19.5-3.9 19.5"/> + </svg> + {{ recommendation.episodes }} + </span> + {% endif %} + </span> + {% endif %} + </div> + </a> + {% endif %} + {% endfor %} + </div> + {% endif %} + </div> </div> </div> <div id="toastContainer" class="fixed bottom-4 left-1/2 transform -translate-x-1/2 z-50 flex flex-col space-y-2"></div> @@ -485,4 +794,127 @@ observer.observe(episodesSelect, {childList: true, characterData: true}); {% endif %} </script> +<script src="https://cdn.jsdelivr.net/npm/swiper@11/swiper-bundle.min.js"></script> +<script> + const seasonSwiper = new Swiper(".seasonSwiper", { + slidesPerView: 'auto', + spaceBetween: 8, + freeMode: true, + }); +</script> +<script> + // Configuration + const EPISODES_PER_PAGE = 10; + + // Get all episodes + const allEpisodes = Array.from(document.querySelectorAll('#episodes > a')); + let currentPage = 1; + let totalPages = Math.ceil(allEpisodes.length / EPISODES_PER_PAGE); + + function showEpisodesForPage(page) { + const startIndex = (page - 1) * EPISODES_PER_PAGE; + const endIndex = startIndex + EPISODES_PER_PAGE; + + allEpisodes.forEach((episode, index) => { + episode.style.display = (index >= startIndex && index < endIndex) ? '' : 'none'; + }); + } + + function createPaginationControls() { + // Create a container for pagination that's separate from the episodes + const paginationContainer = document.createElement('div'); + paginationContainer.className = 'w-full'; // Ensure it takes full width + + const section = document.createElement('section'); + section.className = 'flex justify-center items-center my-8'; + + const nav = document.createElement('nav'); + nav.className = 'inline-flex rounded-md shadow-sm transform scale-[0.80] sm:scale-100'; + nav.style.transformOrigin = 'center'; + nav.setAttribute('aria-label', 'Pagination'); + + const buttons = [ + { text: 'First', condition: () => currentPage > 1, page: 1 }, + { text: 'Previous', condition: () => currentPage > 1, page: currentPage - 1 }, + { text: `${currentPage} of ${totalPages}`, condition: null }, + { text: 'Next', condition: () => currentPage < totalPages, page: currentPage + 1 }, + { text: 'Last', condition: () => currentPage < totalPages, page: totalPages } + ]; + + buttons.forEach((button, index) => { + const element = button.condition !== null + ? createButton(button.text, button.condition(), button.page, index) + : createSpan(button.text, index); + nav.appendChild(element); + }); + + section.appendChild(nav); + paginationContainer.appendChild(section); + + // Insert the pagination container after the episodes container + const episodesSection = document.getElementById('episodes'); + episodesSection.parentNode.insertBefore(paginationContainer, episodesSection.nextSibling); + } + + function createButton(text, isEnabled, page, index) { + const element = isEnabled ? document.createElement('a') : document.createElement('span'); + let className = `w-24 px-3 py-2 text-sm font-medium flex items-center justify-center `; + + if (index === 0) className += 'rounded-l-md '; + if (index === 4) className += 'rounded-r-md '; + + if (isEnabled) { + className += `bg-{{ user.preferences.accent_colour }}-600 bg-opacity-30 text-white hover:bg-opacity-40 `; + if (index === 1 || index === 3) { + className += 'text-opacity-90 hover:text-opacity-100 '; + } + element.href = '#'; + element.addEventListener('click', (e) => { + e.preventDefault(); + goToPage(page); + }); + } else { + className += 'bg-{{ user.preferences.accent_colour }}-600 bg-opacity-10 text-white text-opacity-30 cursor-not-allowed'; + } + + element.className = className; + element.textContent = text; + return element; + } + + function createSpan(text) { + const span = document.createElement('span'); + span.className = 'w-28 px-3 py-2 bg-{{ user.preferences.accent_colour }}-600 bg-opacity-50 text-sm font-medium text-white flex items-center justify-center'; + span.textContent = text; + return span; + } + + function smoothScrollToEpisodes() { + const episodesSection = document.getElementById('dss'); + episodesSection.scrollIntoView({ behavior: 'smooth', block: 'start' }); + } + + function goToPage(page) { + if (page >= 1 && page <= totalPages && page !== currentPage) { + currentPage = page; + updatePagination(); + smoothScrollToEpisodes(); + } + } + + function updatePagination() { + showEpisodesForPage(currentPage); + const paginationContainer = document.querySelector('#episodes + div'); + if (paginationContainer) { + paginationContainer.remove(); + } + createPaginationControls(); + } + + // Initialize pagination + document.addEventListener('DOMContentLoaded', function() { + createPaginationControls(); + showEpisodesForPage(currentPage); + }); +</script> {% endblock scripts %}
\ No newline at end of file |
