diff options
| -rw-r--r-- | homepage/urls.py | 1 | ||||
| -rw-r--r-- | homepage/utils.py | 110 | ||||
| -rw-r--r-- | homepage/views.py | 57 | ||||
| -rw-r--r-- | requirements.txt | 1 | ||||
| -rw-r--r-- | static/css/main.css | 366 | ||||
| -rw-r--r-- | templates/home/schedule.html | 192 | ||||
| -rw-r--r-- | templates/partials/navbar.html | 8 |
7 files changed, 699 insertions, 36 deletions
diff --git a/homepage/urls.py b/homepage/urls.py index 9fab287..d187864 100644 --- a/homepage/urls.py +++ b/homepage/urls.py @@ -6,5 +6,6 @@ app_name = "home" urlpatterns = [ path("", views.index, name="index"), path("search", views.search, name="search"), + path("schedule", views.schedule, name="schedule"), path("q_search", views.search_json, name="q_search"), ] diff --git a/homepage/utils.py b/homepage/utils.py index 2c238bb..336a80d 100644 --- a/homepage/utils.py +++ b/homepage/utils.py @@ -1,7 +1,12 @@ +from collections import defaultdict import os import dotenv import requests import time +import datetime +import pytz +import requests +from django.utils import timezone dotenv.load_dotenv() @@ -80,3 +85,108 @@ def get_upcoming_anime(page=1, per_page=6): request_url = f"{CONSUMET_URL}/meta/anilist/advanced-search?type=ANIME&status=NOT_YET_RELEASED&sort=[%22POPULARITY_DESC%22]&season={season}&year={year}&?page={page}&perPage={per_page}" response = requests.get(request_url) return response.json() + +def get_start_end_times(): + now = datetime.datetime.now(pytz.UTC) + + start_time = now.replace(hour=4, minute=0, second=0, microsecond=0) + if now.hour < 4: + start_time -= datetime.timedelta(days=1) + + times = [] + for i in range(7): + start = start_time + datetime.timedelta(days=i) + end = start + datetime.timedelta(days=1) + times.append({ + "start": int(start.timestamp()), + "end": int(end.timestamp()), + "day": start.strftime("%A"), + "date": start.strftime("%Y-%m-%d"), + "today": i == 0 + }) + + return times + +def get_anime_schedule(week_start, week_end): + # GraphQL query + query = """ + query ($weekStart: Int, $weekEnd: Int, $page: Int, $perPage: Int) { + Page(page: $page, perPage: $perPage) { + pageInfo { + hasNextPage + total + currentPage + lastPage + perPage + } + airingSchedules( + airingAt_greater: $weekStart + airingAt_lesser: $weekEnd + sort: TIME + ) { + id + episode + timeUntilAiring + airingAt + mediaId + media { + isAdult + title { + romaji + native + english + } + coverImage { + extraLarge + medium + color + } + bannerImage + siteUrl + } + } + } + } + """ + + # Variables for the query + variables = { + "weekStart": week_start, + "weekEnd": week_end, + "page": 1, + "perPage": 50 # Adjust as needed + } + + # Make the request to the AniList API + url = 'https://graphql.anilist.co' + response = requests.post(url, json={'query': query, 'variables': variables}) + + if response.status_code == 200: + return response.json()['data']['Page']['airingSchedules'] + else: + return None + +def group_anime_schedules(schedules): + grouped = defaultdict(list) + for schedule in schedules: + airing_time = datetime.datetime.fromtimestamp(schedule['airingAt']) + grouped[airing_time].append(schedule) + return dict(sorted(grouped.items())) + +def find_target_anime(grouped_schedules): + flat_list = [ + (time, anime) + for time, animes in grouped_schedules.items() + for anime in animes + ] + flat_list.sort(key=lambda x: x[0]) # Sort by airing time + + first_positive = next((i for i, (_, anime) in enumerate(flat_list) if anime['timeUntilAiring'] > 0), None) + + if first_positive is None or first_positive < 2: + return None, [] + + target = flat_list[first_positive][1] + last_two = [anime for _, anime in flat_list[first_positive-2:first_positive]] + + return target, last_two
\ No newline at end of file diff --git a/homepage/views.py b/homepage/views.py index ddceb56..ee94790 100644 --- a/homepage/views.py +++ b/homepage/views.py @@ -6,12 +6,16 @@ from datetime import datetime from watch.utils import get_from_redis_cache, store_in_redis_cache import json from homepage.utils import ( + find_target_anime, + get_anime_schedule, + get_start_end_times, get_trending_anime, get_popular_anime, get_top_anime, get_top_airing_anime, get_upcoming_anime, get_next_season, + group_anime_schedules, ) from functools import lru_cache from user_profile.models import UserHistory @@ -120,3 +124,56 @@ def search_json(request): search_results = response.json() return JsonResponse(search_results) + +def schedule(request): + start = request.GET.get("start") + end = request.GET.get("end") + + times = get_start_end_times() + today = times[ + next( + ( + index + for index, time in enumerate(times) + if time["today"] + ), + None, + ) + ] + + schedule_data = get_anime_schedule(today["start"], today["end"]) + grouped_schedule_data = group_anime_schedules(schedule_data) + target, last_two = find_target_anime(grouped_schedule_data) + + if start and end: + today = next( + ( + time + for time in times + if time["start"] == int(start) and time["end"] == int(end) + ), + None, + ) + + if today: + schedule_data = get_anime_schedule(today["start"], today["end"]) + grouped_schedule_data = group_anime_schedules(schedule_data) + + # change today in times + for time in times: + time["today"] = False + today["today"] = True + else: + start = today["start"] + end = today["end"] + + context = { + "schedule": grouped_schedule_data, + "next_airing": target, + "recently_aired": last_two, + "times": times, + "start": start, + "end": end, + } + + return render(request, "home/schedule.html", context) diff --git a/requirements.txt b/requirements.txt index 0e69157..fd7b072 100644 --- a/requirements.txt +++ b/requirements.txt @@ -5,3 +5,4 @@ requests django-cors-headers redis gunicorn +pytz diff --git a/static/css/main.css b/static/css/main.css index 12d8fa4..4223bdf 100644 --- a/static/css/main.css +++ b/static/css/main.css @@ -677,6 +677,34 @@ video { top: 100%; } +.left-\[-0\.5rem\] { + left: -0.5rem; +} + +.top-\[-0\.35rem\] { + top: -0.35rem; +} + +.-left-\[0\.5rem\] { + left: -0.5rem; +} + +.-top-\[0\.35rem\] { + top: -0.35rem; +} + +.-left-\[0\.35rem\] { + left: -0.35rem; +} + +.top-1 { + top: 0.25rem; +} + +.z-0 { + z-index: 0; +} + .z-10 { z-index: 10; } @@ -689,6 +717,14 @@ video { z-index: 50; } +.z-30 { + z-index: 30; +} + +.z-\[3\] { + z-index: 3; +} + .-mx-4 { margin-left: -1rem; margin-right: -1rem; @@ -719,9 +755,8 @@ video { margin-bottom: 2rem; } -.mx-2 { - margin-left: 0.5rem; - margin-right: 0.5rem; +.-ml-5 { + margin-left: -1.25rem; } .mb-2 { @@ -833,10 +868,6 @@ video { height: 6rem; } -.h-28 { - height: 7rem; -} - .h-32 { height: 8rem; } @@ -877,6 +908,10 @@ video { height: 100%; } +.h-\[2rem\] { + height: 2rem; +} + .max-h-24 { max-height: 6rem; } @@ -921,10 +956,6 @@ video { width: 33.333333%; } -.w-48 { - width: 12rem; -} - .w-5 { width: 1.25rem; } @@ -958,10 +989,30 @@ video { width: max-content; } +.w-20 { + width: 5rem; +} + +.w-\[2\.5rem\] { + width: 2.5rem; +} + +.w-1\/4 { + width: 25%; +} + +.w-60 { + width: 15rem; +} + .min-w-32 { min-width: 8rem; } +.min-w-0 { + min-width: 0px; +} + .max-w-7xl { max-width: 80rem; } @@ -987,18 +1038,50 @@ video { max-width: max-content; } +.max-w-\[calc\(100\%-4\.5rem\)\] { + max-width: calc(100% - 4.5rem); +} + +.max-w-\[calc\(100\%-8rem\)\] { + max-width: calc(100% - 8rem); +} + +.max-w-96 { + max-width: 24rem; +} + +.max-w-\[calc\(100\%-2rem\)\] { + max-width: calc(100% - 2rem); +} + .flex-1 { flex: 1 1 0%; } +.flex-none { + flex: none; +} + .flex-shrink-0 { flex-shrink: 0; } +.flex-grow { + flex-grow: 1; +} + .origin-left { transform-origin: left; } +.origin-top-left { + transform-origin: top left; +} + +.origin-\[0px_0px\] { + transform-origin: 0px 0px; +} + .-translate-x-1\/2 { --tw-translate-x: -50%; transform: translate(var(--tw-translate-x), var(--tw-translate-y)) rotate(var(--tw-rotate)) skewX(var(--tw-skew-x)) skewY(var(--tw-skew-y)) scaleX(var(--tw-scale-x)) scaleY(var(--tw-scale-y)); @@ -1009,6 +1092,11 @@ video { transform: translate(var(--tw-translate-x), var(--tw-translate-y)) rotate(var(--tw-rotate)) skewX(var(--tw-skew-x)) skewY(var(--tw-skew-y)) scaleX(var(--tw-scale-x)) scaleY(var(--tw-scale-y)); } +.rotate-\[-45deg\] { + --tw-rotate: -45deg; + transform: translate(var(--tw-translate-x), var(--tw-translate-y)) rotate(var(--tw-rotate)) skewX(var(--tw-skew-x)) skewY(var(--tw-skew-y)) scaleX(var(--tw-scale-x)) scaleY(var(--tw-scale-y)); +} + .scale-\[0\.80\] { --tw-scale-x: 0.80; --tw-scale-y: 0.80; @@ -1039,6 +1127,16 @@ video { animation: spin 1s linear infinite; } +@keyframes pulse { + 50% { + opacity: .5; + } +} + +.animate-pulse { + animation: pulse 2s cubic-bezier(0.4, 0, 0.6, 1) infinite; +} + .cursor-not-allowed { cursor: not-allowed; } @@ -1127,12 +1225,6 @@ 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; } @@ -1149,6 +1241,10 @@ video { overflow-y: auto; } +.overflow-x-scroll { + overflow-x: scroll; +} + .overflow-y-scroll { overflow-y: scroll; } @@ -1201,6 +1297,23 @@ video { border-bottom-right-radius: 0.375rem; } +.rounded-b-lg { + border-bottom-right-radius: 0.5rem; + border-bottom-left-radius: 0.5rem; +} + +.rounded-br-lg { + border-bottom-right-radius: 0.5rem; +} + +.rounded-tr-\[0\.5rem\] { + border-top-right-radius: 0.5rem; +} + +.rounded-br-\[0\.5rem\] { + border-bottom-right-radius: 0.5rem; +} + .border { border-width: 1px; } @@ -1209,6 +1322,10 @@ video { border-width: 4px; } +.border-2 { + border-width: 2px; +} + .border-b { border-bottom-width: 1px; } @@ -1348,6 +1465,11 @@ video { border-color: rgb(202 138 4 / var(--tw-border-opacity)); } +.border-\[\#ff8da1\] { + --tw-border-opacity: 1; + border-color: rgb(255 141 161 / var(--tw-border-opacity)); +} + .border-opacity-10 { --tw-border-opacity: 0.1; } @@ -1612,9 +1734,39 @@ video { background-color: rgb(161 98 7 / var(--tw-bg-opacity)); } -.bg-gray-800 { +.bg-pink-200 { + --tw-bg-opacity: 1; + background-color: rgb(251 207 232 / var(--tw-bg-opacity)); +} + +.bg-pink-300 { + --tw-bg-opacity: 1; + background-color: rgb(249 168 212 / var(--tw-bg-opacity)); +} + +.bg-pink-500 { + --tw-bg-opacity: 1; + background-color: rgb(236 72 153 / var(--tw-bg-opacity)); +} + +.bg-red-300 { + --tw-bg-opacity: 1; + background-color: rgb(252 165 165 / var(--tw-bg-opacity)); +} + +.bg-yellow-300 { --tw-bg-opacity: 1; - background-color: rgb(31 41 55 / var(--tw-bg-opacity)); + background-color: rgb(253 224 71 / var(--tw-bg-opacity)); +} + +.bg-orange-300 { + --tw-bg-opacity: 1; + background-color: rgb(253 186 116 / var(--tw-bg-opacity)); +} + +.bg-gray-500 { + --tw-bg-opacity: 1; + background-color: rgb(107 114 128 / var(--tw-bg-opacity)); } .bg-opacity-10 { @@ -1676,6 +1828,10 @@ video { padding: 1rem; } +.p-\[0\.2rem\] { + padding: 0.2rem; +} + .px-1 { padding-left: 0.25rem; padding-right: 0.25rem; @@ -1726,10 +1882,23 @@ video { padding-bottom: 2rem; } +.px-8 { + padding-left: 2rem; + padding-right: 2rem; +} + +.pb-2 { + padding-bottom: 0.5rem; +} + .pb-4 { padding-bottom: 1rem; } +.pl-2 { + padding-left: 0.5rem; +} + .pl-4 { padding-left: 1rem; } @@ -1750,10 +1919,38 @@ video { padding-top: 0.25rem; } +.pt-2 { + padding-top: 0.5rem; +} + .pt-\[140\%\] { padding-top: 140%; } +.pr-16 { + padding-right: 4rem; +} + +.pt-4 { + padding-top: 1rem; +} + +.pb-3 { + padding-bottom: 0.75rem; +} + +.pl-3 { + padding-left: 0.75rem; +} + +.pr-9 { + padding-right: 2.25rem; +} + +.pt-3 { + padding-top: 0.75rem; +} + .text-center { text-align: center; } @@ -2095,6 +2292,16 @@ video { color: rgb(161 98 7 / var(--tw-text-opacity)); } +.text-pink-500 { + --tw-text-opacity: 1; + color: rgb(236 72 153 / var(--tw-text-opacity)); +} + +.text-gray-300 { + --tw-text-opacity: 1; + color: rgb(209 213 219 / var(--tw-text-opacity)); +} + .text-opacity-30 { --tw-text-opacity: 0.3; } @@ -2129,6 +2336,24 @@ video { box-shadow: var(--tw-ring-offset-shadow, 0 0 #0000), var(--tw-ring-shadow, 0 0 #0000), var(--tw-shadow); } +.shadow-\[2px_2px_4px_var\(--global-shadow\)\] { + --tw-shadow: 2px 2px 4px var(--global-shadow); + --tw-shadow-colored: 2px 2px 4px var(--tw-shadow-color); + box-shadow: var(--tw-ring-offset-shadow, 0 0 #0000), var(--tw-ring-shadow, 0 0 #0000), var(--tw-shadow); +} + +.shadow-\[2px_2px_4px_rgba\(0\2c 0\2c 0\2c 0\.2\)\] { + --tw-shadow: 2px 2px 4px rgba(0,0,0,0.2); + --tw-shadow-colored: 2px 2px 4px var(--tw-shadow-color); + box-shadow: var(--tw-ring-offset-shadow, 0 0 #0000), var(--tw-ring-shadow, 0 0 #0000), var(--tw-shadow); +} + +.shadow-\[-2px_2px_4px_rgba\(0\2c 0\2c 0\2c 0\.2\)\] { + --tw-shadow: -2px 2px 4px rgba(0,0,0,0.2); + --tw-shadow-colored: -2px 2px 4px var(--tw-shadow-color); + box-shadow: var(--tw-ring-offset-shadow, 0 0 #0000), var(--tw-ring-shadow, 0 0 #0000), var(--tw-shadow); +} + .outline-none { outline: 2px solid transparent; outline-offset: 2px; @@ -2156,6 +2381,14 @@ video { transition-duration: 150ms; } +.transition { + transition-property: color, background-color, border-color, text-decoration-color, fill, stroke, opacity, box-shadow, transform, filter, -webkit-backdrop-filter; + transition-property: color, background-color, border-color, text-decoration-color, fill, stroke, opacity, box-shadow, transform, filter, backdrop-filter; + transition-property: color, background-color, border-color, text-decoration-color, fill, stroke, opacity, box-shadow, transform, filter, backdrop-filter, -webkit-backdrop-filter; + transition-timing-function: cubic-bezier(0.4, 0, 0.2, 1); + transition-duration: 150ms; +} + .duration-100 { transition-duration: 100ms; } @@ -2260,6 +2493,28 @@ main { animation: fadeInUp 0.5s ease-in-out forwards; } +.hover\:scale-50:hover { + --tw-scale-x: .5; + --tw-scale-y: .5; + transform: translate(var(--tw-translate-x), var(--tw-translate-y)) rotate(var(--tw-rotate)) skewX(var(--tw-skew-x)) skewY(var(--tw-skew-y)) scaleX(var(--tw-scale-x)) scaleY(var(--tw-scale-y)); +} + +.hover\:scale-105:hover { + --tw-scale-x: 1.05; + --tw-scale-y: 1.05; + transform: translate(var(--tw-translate-x), var(--tw-translate-y)) rotate(var(--tw-rotate)) skewX(var(--tw-skew-x)) skewY(var(--tw-skew-y)) scaleX(var(--tw-scale-x)) scaleY(var(--tw-scale-y)); +} + +.hover\:scale-x-105:hover { + --tw-scale-x: 1.05; + transform: translate(var(--tw-translate-x), var(--tw-translate-y)) rotate(var(--tw-rotate)) skewX(var(--tw-skew-x)) skewY(var(--tw-skew-y)) scaleX(var(--tw-scale-x)) scaleY(var(--tw-scale-y)); +} + +.hover\:scale-y-105:hover { + --tw-scale-y: 1.05; + transform: translate(var(--tw-translate-x), var(--tw-translate-y)) rotate(var(--tw-rotate)) skewX(var(--tw-skew-x)) skewY(var(--tw-skew-y)) scaleX(var(--tw-scale-x)) scaleY(var(--tw-scale-y)); +} + .hover\:rounded-b-lg:hover { border-bottom-right-radius: 0.5rem; border-bottom-left-radius: 0.5rem; @@ -2460,11 +2715,6 @@ 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; } @@ -2491,6 +2741,12 @@ main { --tw-text-opacity: 1; } +.hover\:shadow-lg:hover { + --tw-shadow: 0 10px 15px -3px rgb(0 0 0 / 0.1), 0 4px 6px -4px rgb(0 0 0 / 0.1); + --tw-shadow-colored: 0 10px 15px -3px var(--tw-shadow-color), 0 4px 6px -4px var(--tw-shadow-color); + box-shadow: var(--tw-ring-offset-shadow, 0 0 #0000), var(--tw-ring-shadow, 0 0 #0000), var(--tw-shadow); +} + .focus\:outline-none:focus { outline: 2px solid transparent; outline-offset: 2px; @@ -2581,6 +2837,26 @@ main { height: 1rem; } + .sm\:w-full { + width: 100%; + } + + .sm\:w-96 { + width: 24rem; + } + + .sm\:max-w-96 { + max-width: 24rem; + } + + .sm\:max-w-none { + max-width: none; + } + + .sm\:max-w-\[24rem\] { + max-width: 24rem; + } + .sm\:scale-100 { --tw-scale-x: 1; --tw-scale-y: 1; @@ -2591,6 +2867,14 @@ main { gap: 0.5rem; } + .sm\:overflow-x-auto { + overflow-x: auto; + } + + .sm\:overflow-x-hidden { + overflow-x: hidden; + } + .sm\:p-2 { padding: 0.5rem; } @@ -2610,6 +2894,11 @@ main { padding-bottom: 0.5rem; } + .sm\:px-0 { + padding-left: 0px; + padding-right: 0px; + } + .sm\:text-sm { font-size: 0.875rem; line-height: 1.25rem; @@ -2629,6 +2918,10 @@ main { .md\:w-64 { width: 16rem; } + + .md\:w-auto { + width: auto; + } } @media (min-width: 1024px) { @@ -2652,6 +2945,10 @@ main { height: 4rem; } + .lg\:h-28 { + height: 7rem; + } + .lg\:h-96 { height: 24rem; } @@ -2660,10 +2957,6 @@ main { height: 39vw; } - .lg\:h-28 { - height: 7rem; - } - .lg\:max-h-24 { max-height: 6rem; } @@ -2696,18 +2989,27 @@ main { width: 75%; } - .lg\:w-60 { - width: 15rem; - } - .lg\:w-48 { width: 12rem; } + .lg\:w-60 { + width: 15rem; + } + .lg\:w-auto { width: auto; } + .lg\:max-w-full { + max-width: 100%; + } + + .lg\:max-w-fit { + max-width: -moz-fit-content; + max-width: fit-content; + } + .lg\:flex-shrink-0 { flex-shrink: 0; } diff --git a/templates/home/schedule.html b/templates/home/schedule.html new file mode 100644 index 0000000..c4ccbf5 --- /dev/null +++ b/templates/home/schedule.html @@ -0,0 +1,192 @@ +{% extends "partials/base.html" %} +{% block css %} +<style> + .anime-card::after { + content: ""; + position: absolute; + top: 0; + right: 0; + width: 9.5rem; + height: 12.25rem; + background-position: center center; + background-size: cover; + background-repeat: no-repeat; + clip-path: polygon(100% 0, 100% 100%, 0 0); + z-index: 1; + filter: brightness(50%); + } + .sticker { + transform-origin: 0px 0px; + box-shadow: -2px 2px 4px var(--global-shadow); + transition: transform 0.2s; + display: flex; + align-items: flex-end; + justify-content: flex-start; + padding: 0.2rem; + } +</style> +<script> + function convertTime(unixTimestamp) { + const date = new Date(unixTimestamp * 1000); + return date.toLocaleTimeString('en-US', { hour: 'numeric', minute: 'numeric', hour12: true }); + } + + function convertDay(unixTimestamp) { + const date = new Date(unixTimestamp * 1000); + return date.toLocaleDateString('en-US', { weekday: 'long', month: 'long', day: 'numeric' }); + } +</script> +{% endblock css %} +{% block content %} +<div class="text-2xl font-bold mt-4"> + <span id="todayTime"></span> +</div> +<section class="mt-4 flex flex-col lg:flex-row gap-2"> + {% for anime in recently_aired %} + <style> + .anime-card-{{ forloop.counter }}::after { + background-image: url('{{ anime.media.bannerImage }}'); + } + </style> + <a href="{% url "watch:watch_episode" anime.mediaId anime.episode %}" class="flex flex-1 bg-neutral-950 rounded pt-2 pb-2 pl-2 pr-8 relative overflow-hidden anime-card anime-card-{{ forloop.counter }}"> + {% if forloop.counter == 1 %} + <div class="sticker text-pink-500 absolute top-1 -left-[0.35rem] w-10 h-8 rounded-b-lg bg-pink-300 border-2 border-pink-400 z-10 transform rotate-[-45deg]"> + <svg stroke="currentColor" fill="currentColor" stroke-width="0" viewBox="0 0 512 512" height="1em" width="1em" xmlns="http://www.w3.org/2000/svg"><path d="M173.898 439.404l-166.4-166.4c-9.997-9.997-9.997-26.206 0-36.204l36.203-36.204c9.997-9.998 26.207-9.998 36.204 0L192 312.69 432.095 72.596c9.997-9.997 26.207-9.997 36.204 0l36.203 36.204c9.997 9.997 9.997 26.206 0 36.204l-294.4 294.401c-9.998 9.997-26.207 9.997-36.204-.001z"></path></svg> + </div> + {% else %} + <div class="sticker text-red-500 absolute top-1 -left-[0.35rem] w-10 h-8 rounded-b-lg bg-red-300 border-2 border-red-400 z-10 transform rotate-[-45deg]"> + <svg stroke="currentColor" fill="currentColor" stroke-width="0" viewBox="0 0 192 512" height="1em" width="1em" xmlns="http://www.w3.org/2000/svg"><path d="M176 432c0 44.112-35.888 80-80 80s-80-35.888-80-80 35.888-80 80-80 80 35.888 80 80zM25.26 25.199l13.6 272C39.499 309.972 50.041 320 62.83 320h66.34c12.789 0 23.331-10.028 23.97-22.801l13.6-272C167.425 11.49 156.496 0 142.77 0H49.23C35.504 0 24.575 11.49 25.26 25.199z"></path></svg> + </div> + {% endif %} + <div class="flex justify-between items-center w-full z-10"> + <div class="flex flex-col gap-1 max-w-[calc(100%-8rem)]"> + <span class="mt-4 truncate max-w-full overflow-hidden text-ellipsis whitespace-nowrap font-bold text-xl text-white"> + {% if user.preferences.title_language == "english" and anime.media.title.english %} + {{ anime.media.title.english }} + {% elif user.preferences.title_language == "native" and anime.media.title.native %} + {{ anime.media.title.native }} + {% else %} + {{ anime.media.title.romaji }} + {% endif %} + </span> + <div class="flex flex-row gap-2 items-center text-sm text-gray-300"> + <svg stroke="currentColor" fill="currentColor" stroke-width="0" viewBox="0 0 512 512" height="1em" width="1em" xmlns="http://www.w3.org/2000/svg"><path d="M371.7 238l-176-107c-15.8-8.8-35.7 2.5-35.7 21v208c0 18.4 19.8 29.8 35.7 21l176-101c16.4-9.1 16.4-32.8 0-42zM504 256C504 119 393 8 256 8S8 119 8 256s111 248 248 248 248-111 248-248zm-448 0c0-110.5 89.5-200 200-200s200 89.5 200 200-89.5 200-200 200S56 366.5 56 256z"></path></svg> + <span>Episode {{ anime.episode }}</span> + </div> + <div class="flex flex-row gap-2 items-center text-sm text-gray-300"> + <svg stroke="currentColor" fill="currentColor" stroke-width="0" viewBox="0 0 512 512" height="1em" width="1em" xmlns="http://www.w3.org/2000/svg"><path d="M256 8C119 8 8 119 8 256s111 248 248 248 248-111 248-248S393 8 256 8zm0 448c-110.5 0-200-89.5-200-200S145.5 56 256 56s200 89.5 200 200-89.5 200-200 200zm61.8-104.4l-84.9-61.7c-3.1-2.3-4.9-5.9-4.9-9.7V116c0-6.6 5.4-12 12-12h32c6.6 0 12 5.4 12 12v141.7l66.8 48.6c5.4 3.9 6.5 11.4 2.6 16.8L334.6 349c-3.9 5.3-11.4 6.5-16.8 2.6z"></path></svg> + <span> + {% if anime.timeUntilAiring > 0 %} + Airing + {% else %} + Aired + {% endif %} + at <script>document.write(convertTime({{ anime.airingAt }}))</script> + </span> + </div> + </div> + <img src="{{ anime.media.coverImage.medium }}" alt="{{ anime.media.title.romaji }}" class="rounded w-16 h-24" /> + </div> + </a> + {% endfor %} + <style> + .anime-card-next-airing::after { + background-image: url('{{ next_airing.media.bannerImage }}'); + } + </style> + <a href="{% url "watch:watch_episode" next_airing.mediaId next_airing.episode %}" class="flex flex-1 bg-neutral-950 rounded pt-2 pb-2 pl-2 pr-8 relative overflow-hidden anime-card anime-card-next-airing"> + <div class="sticker text-orange-500 absolute top-1 -left-[0.35rem] w-10 h-8 rounded-b-lg bg-orange-300 border-2 border-orange-400 z-10 transform rotate-[-45deg]"> + <svg stroke="currentColor" fill="currentColor" stroke-width="0" viewBox="0 0 24 24" height="1rem" width="1rem" xmlns="http://www.w3.org/2000/svg"><path fill="none" d="M0 0h24v24H0z"></path><path d="m23 12-2.44-2.78.34-3.68-3.61-.82-1.89-3.18L12 3 8.6 1.54 6.71 4.72l-3.61.81.34 3.68L1 12l2.44 2.78-.34 3.69 3.61.82 1.89 3.18L12 21l3.4 1.46 1.89-3.18 3.61-.82-.34-3.68L23 12zm-10 5h-2v-2h2v2zm0-4h-2V7h2v6z"></path></svg> + </div> + <div class="flex justify-between items-center w-full z-10"> + <div class="flex flex-col gap-1 mr-2 max-w-[calc(100%-8rem)]"> + <span class="truncate mt-4 max-w-full overflow-hidden text-ellipsis whitespace-nowrap font-bold text-xl text-white"> + {% if user.preferences.title_language == "english" and next_airing.media.title.english %} + {{ next_airing.media.title.english }} + {% elif user.preferences.title_language == "native" and next_airing.media.title.native %} + {{ next_airing.media.title.native }} + {% else %} + {{ next_airing.media.title.romaji }} + {% endif %} + </span> + <div class="flex flex-row gap-2 items-center text-sm text-gray-300"> + <svg stroke="currentColor" fill="currentColor" stroke-width="0" viewBox="0 0 512 512" height="1em" width="1em" xmlns="http://www.w3.org/2000/svg"><path d="M371.7 238l-176-107c-15.8-8.8-35.7 2.5-35.7 21v208c0 18.4 19.8 29.8 35.7 21l176-101c16.4-9.1 16.4-32.8 0-42zM504 256C504 119 393 8 256 8S8 119 8 256s111 248 248 248 248-111 248-248zm-448 0c0-110.5 89.5-200 200-200s200 89.5 200 200-89.5 200-200 200S56 366.5 56 256z"></path></svg> + <span>Episode {{ next_airing.episode }}</span> + </div> + <div class="flex flex-row gap-2 items-center text-sm text-gray-300"> + <svg stroke="currentColor" fill="currentColor" stroke-width="0" viewBox="0 0 512 512" height="1em" width="1em" xmlns="http://www.w3.org/2000/svg"><path d="M256 8C119 8 8 119 8 256s111 248 248 248 248-111 248-248S393 8 256 8zm0 448c-110.5 0-200-89.5-200-200S145.5 56 256 56s200 89.5 200 200-89.5 200-200 200zm61.8-104.4l-84.9-61.7c-3.1-2.3-4.9-5.9-4.9-9.7V116c0-6.6 5.4-12 12-12h32c6.6 0 12 5.4 12 12v141.7l66.8 48.6c5.4 3.9 6.5 11.4 2.6 16.8L334.6 349c-3.9 5.3-11.4 6.5-16.8 2.6z"></path></svg> + <span> + {% if next_airing.timeUntilAiring > 0 %} + Airing + {% else %} + Aired + {% endif %} + at <script>document.write(convertTime({{ next_airing.airingAt }}))</script> + </span> + </div> + </div> + <img src="{{ next_airing.media.coverImage.medium }}" alt="{{ next_airing.media.title.romaji }}" class="rounded w-16 h-24" /> + </div> + </a> +</section> +<section class="mt-8 mx-auto flex justify-center px-4"> + <section class="inline-flex w-full lg:max-w-fit sm:max-w-96 flex-row gap-4 rounded-full bg-white bg-opacity-10 overflow-x-auto"> + {% for time in times %} + <a href="{% url "home:schedule" %}?start={{ time.start }}&end={{ time.end }}" class="flex flex-row items-center focus:outline-none gap-2 text-sm font-bold py-2 px-4 rounded-full {% if time.today %}bg-{{ user.preferences.accent_colour }}-600{% else %}{% endif %}"> + <span>{{ time.day }}</span> + </a> + {% endfor %} + </section> +</section> +<h1 class="mt-8 text-2xl font-bold"><script>document.write(convertDay({{ start }}))</script></h1> +{% for time, items in schedule.items %} +<section class="my-4"> + <h1 class="text-xl my-2 font-bold"><script>document.write(convertTime({{ time.timestamp }}))</script></h1> + <div class="flex flex-col lg:flex-row gap-2"> + {% for anime in items %} + <a href="{% url "watch:watch_episode" anime.mediaId anime.episode %}" class="flex flex-none bg-neutral-950 rounded p-2 gap-4 items-center max-w-96 w-full {% if anime.timeUntilAiring < 0 %}border-2 border-{{ user.preferences.accent_colour }}-600{% endif %}"> + <img src="{{ anime.media.coverImage.medium }}" alt="{{ anime.media.title.romaji }}" class="rounded w-16 h-24" /> + <div class="flex flex-col gap-1 max-w-[calc(100%-8rem)]"> + <span class="truncate max-w-full overflow-hidden text-ellipsis whitespace-nowrap font-bold text-xl text-white"> + {% if user.preferences.title_language == "english" and anime.media.title.english %} + {{ anime.media.title.english }} + {% elif user.preferences.title_language == "native" and anime.media.title.native %} + {{ anime.media.title.native }} + {% else %} + {{ anime.media.title.romaji }} + {% endif %} + </span> + <div class="flex flex-row gap-2 items-center text-sm text-gray-300"> + <svg stroke="currentColor" fill="currentColor" stroke-width="0" viewBox="0 0 512 512" height="1em" width="1em" xmlns="http://www.w3.org/2000/svg"><path d="M371.7 238l-176-107c-15.8-8.8-35.7 2.5-35.7 21v208c0 18.4 19.8 29.8 35.7 21l176-101c16.4-9.1 16.4-32.8 0-42zM504 256C504 119 393 8 256 8S8 119 8 256s111 248 248 248 248-111 248-248zm-448 0c0-110.5 89.5-200 200-200s200 89.5 200 200-89.5 200-200 200S56 366.5 56 256z"></path></svg> + <span>Episode {{ anime.episode }}</span> + </div> + <div class="flex flex-row gap-2 items-center text-sm text-gray-300"> + <svg stroke="currentColor" fill="currentColor" stroke-width="0" viewBox="0 0 512 512" height="1em" width="1em" xmlns="http://www.w3.org/2000/svg"><path d="M256 8C119 8 8 119 8 256s111 248 248 248 248-111 248-248S393 8 256 8zm0 448c-110.5 0-200-89.5-200-200S145.5 56 256 56s200 89.5 200 200-89.5 200-200 200zm61.8-104.4l-84.9-61.7c-3.1-2.3-4.9-5.9-4.9-9.7V116c0-6.6 5.4-12 12-12h32c6.6 0 12 5.4 12 12v141.7l66.8 48.6c5.4 3.9 6.5 11.4 2.6 16.8L334.6 349c-3.9 5.3-11.4 6.5-16.8 2.6z"></path></svg> + <span> + {% if anime.timeUntilAiring > 0 %} + Airing + {% else %} + Aired + {% endif %} + at <script>document.write(convertTime({{ anime.airingAt }}))</script> + </span> + </div> + </div> + </a> + {% endfor %} + </div> +{% endfor %} +</section> +{% endblock content %} +{% block scripts %} +<script> + function updateTime() { + const today = new Date(); + const time = today.toLocaleTimeString('en-US', { hour: 'numeric', minute: 'numeric', second: 'numeric', hour12: true }); + document.getElementById('todayTime').innerText = time; + } + + setInterval(updateTime, 1000); + updateTime(); +</script> +{% endblock scripts %}
\ No newline at end of file diff --git a/templates/partials/navbar.html b/templates/partials/navbar.html index 342175e..6c49fd2 100644 --- a/templates/partials/navbar.html +++ b/templates/partials/navbar.html @@ -81,8 +81,8 @@ </a> <!-- Schedule icon --> - <a href="#schedule" class="flex flex-col gap-2 items-center"> - {% if request.path == '/schedule/' %} + <a href="{% url "home:schedule" %}" class="flex flex-col gap-2 items-center"> + {% if request.path == '/schedule' %} <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" @@ -332,8 +332,8 @@ </a> <!-- Schedule icon --> - <a href="#schedule" class="flex flex-col gap-2 items-center"> - {% if request.path == '/schedule/' %} + <a href="{% url "home:schedule" %}" class="flex flex-col gap-2 items-center"> + {% if request.path == '/schedule' %} <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" |
