aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorBobby <[email protected]>2024-09-01 05:37:38 -0400
committerBobby <[email protected]>2024-09-01 05:37:38 -0400
commit1afd44cda6331a3fbd2a2a83cc1a3850452f8e14 (patch)
tree2382e01fbdbac9d21bc02d3a0279616ab78205b4
parent6346d1913bcd54f4402b7ce640fd10c752084c83 (diff)
downloadyugen-1afd44cda6331a3fbd2a2a83cc1a3850452f8e14.tar.xz
yugen-1afd44cda6331a3fbd2a2a83cc1a3850452f8e14.zip
fix list buttons. anime detail page
-rw-r--r--static/css/main.css71
-rw-r--r--templates/user_profile/user_anime_list.html8
-rw-r--r--templates/watch/watch.html529
-rw-r--r--watch/utils.py6
-rw-r--r--watch/views.py51
5 files changed, 618 insertions, 47 deletions
diff --git a/static/css/main.css b/static/css/main.css
index d40ada8..c6ee6a9 100644
--- a/static/css/main.css
+++ b/static/css/main.css
@@ -705,6 +705,11 @@ video {
margin-bottom: 2rem;
}
+.-mx-2 {
+ margin-left: -0.5rem;
+ margin-right: -0.5rem;
+}
+
.mb-2 {
margin-bottom: 0.5rem;
}
@@ -745,6 +750,14 @@ video {
margin-top: 2rem;
}
+.mt-\[0\.35rem\] {
+ margin-top: 0.35rem;
+}
+
+.ml-1 {
+ margin-left: 0.25rem;
+}
+
.block {
display: block;
}
@@ -789,6 +802,11 @@ video {
height: 1.5rem;
}
+.size-5 {
+ width: 1.25rem;
+ height: 1.25rem;
+}
+
.h-24 {
height: 6rem;
}
@@ -829,6 +847,10 @@ video {
height: 100%;
}
+.h-16 {
+ height: 4rem;
+}
+
.max-h-24 {
max-height: 6rem;
}
@@ -894,6 +916,14 @@ video {
width: max-content;
}
+.w-48 {
+ width: 12rem;
+}
+
+.w-12 {
+ width: 3rem;
+}
+
.min-w-32 {
min-width: 8rem;
}
@@ -931,6 +961,23 @@ video {
max-width: calc(100% - 10rem);
}
+.max-w-fit {
+ max-width: -moz-fit-content;
+ max-width: fit-content;
+}
+
+.max-w-\[calc\(100\%-4rem\)\] {
+ max-width: calc(100% - 4rem);
+}
+
+.max-w-0\.5 {
+ max-width: 0.125rem;
+}
+
+.flex-1 {
+ flex: 1 1 0%;
+}
+
.origin-left {
transform-origin: left;
}
@@ -1031,6 +1078,10 @@ video {
justify-content: space-around;
}
+.justify-evenly {
+ justify-content: space-evenly;
+}
+
.gap-1 {
gap: 0.25rem;
}
@@ -1047,6 +1098,10 @@ video {
gap: 2rem;
}
+.gap-11 {
+ gap: 2.75rem;
+}
+
.space-y-2 > :not([hidden]) ~ :not([hidden]) {
--tw-space-y-reverse: 0;
margin-top: calc(0.5rem * calc(1 - var(--tw-space-y-reverse)));
@@ -1514,6 +1569,10 @@ video {
text-align: center;
}
+.text-right {
+ text-align: right;
+}
+
.text-2xl {
font-size: 1.5rem;
line-height: 2rem;
@@ -2246,6 +2305,14 @@ main {
flex-wrap: wrap;
}
+ .sm\:place-content-center {
+ place-content: center;
+ }
+
+ .sm\:items-center {
+ align-items: center;
+ }
+
.sm\:gap-2 {
gap: 0.5rem;
}
@@ -2341,6 +2408,10 @@ main {
flex-wrap: nowrap;
}
+ .lg\:items-start {
+ align-items: flex-start;
+ }
+
.lg\:items-center {
align-items: center;
}
diff --git a/templates/user_profile/user_anime_list.html b/templates/user_profile/user_anime_list.html
index fc7ab3d..c31be13 100644
--- a/templates/user_profile/user_anime_list.html
+++ b/templates/user_profile/user_anime_list.html
@@ -140,20 +140,20 @@
</section>
<section class="flex flex-row justify-between my-4">
{% if prev_offset %}
- <a href="{% url 'user_profile:user_profile' %}?category=anime_list&offset={{ prev_offset }}{% if filter %}&filter={{ filter }}{% endif %}" class="bg-purple-600 text-sm font-bold py-3 px-6 rounded-full flex items-center gap-2">
+ <a href="{% url 'user_profile:user_profile' %}?category=anime_list&offset={{ prev_offset }}{% if filter %}&filter={{ filter }}{% endif %}" class="bg-{{ user.preferences.accent_colour }}-600 text-sm font-bold py-3 px-6 rounded-full flex items-center gap-2">
<span>Load Previous</span>
</a>
{% else %}
- <a class="bg-purple-600 bg-opacity-20 text-sm font-bold py-3 px-6 rounded-full flex items-center gap-2 cursor-not-allowed">
+ <a class="bg-{{ user.preferences.accent_colour }}-600 bg-opacity-20 text-sm font-bold py-3 px-6 rounded-full flex items-center gap-2 cursor-not-allowed">
<span>Load Previous</span>
</a>
{% endif %}
{% if next_offset %}
- <a href="{% url 'user_profile:user_profile' %}?category=anime_list&offset={{ next_offset }}{% if filter %}&filter={{ filter }}{% endif %}" class="bg-purple-600 text-sm font-bold py-3 px-6 rounded-full flex items-center gap-2">
+ <a href="{% url 'user_profile:user_profile' %}?category=anime_list&offset={{ next_offset }}{% if filter %}&filter={{ filter }}{% endif %}" class="bg-{{ user.preferences.accent_colour }}-600 text-sm font-bold py-3 px-6 rounded-full flex items-center gap-2">
<span>Load Next</span>
</a>
{% else %}
- <a class="bg-purple-600 bg-opacity-20 text-sm font-bold py-3 px-6 rounded-full flex items-center gap-2 cursor-not-allowed">
+ <a class="bg-{{ user.preferences.accent_colour }}-600 bg-opacity-20 text-sm font-bold py-3 px-6 rounded-full flex items-center gap-2 cursor-not-allowed">
<span>Load Next</span>
</a>
{% endif %}
diff --git a/templates/watch/watch.html b/templates/watch/watch.html
index 2ab3cc3..47fcf6c 100644
--- a/templates/watch/watch.html
+++ b/templates/watch/watch.html
@@ -1,8 +1,10 @@
{% extends "partials/base.html" %}
+{% load custom_filters %}
{% block css %}
<link rel="stylesheet" href="https://cdn.vidstack.io/player/theme.css" />
<link rel="stylesheet" href="https://cdn.vidstack.io/player/audio.css" />
<link rel="stylesheet" href="https://cdn.vidstack.io/player/video.css" />
+<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/animate.css/4.1.1/animate.min.css"/>
{% endblock css %}
{% block content %}
<div class="flex flex-col lg:flex-row mt-4 gap-2">
@@ -25,18 +27,32 @@
<div class="flex flex-col gap-2 h-full max-h-96 lg:h-[39vw] lg:max-h-[761px] overflow-y-auto">
{% for episode in anime_episodes.episodes %}
{% if episode.number == current_episode %}
- <a id="selected-episode" href="{% url "watch:watch_episode" anime_id episode.number %}" class="flex flex-row justify-between items-center w-full gap-4 bg-{{ user.preferences.accent_colour }}-600 p-2 rounded hover:bg-{{ user.preferences.accent_colour }}-600 hover:bg-opacity-30">
+ <a id="selected-episode" href="{% url "watch:watch_episode" anime_id episode.number %}{% if request.GET.mode %}?mode={{ request.GET.mode }}{% endif %}" class="flex flex-row gap-4 justify-between items-center w-full bg-{{ user.preferences.accent_colour }}-600 p-2 rounded hover:bg-{{ user.preferences.accent_colour }}-600 hover:bg-opacity-30">
<span class="truncate max-w-full overflow-hidden text-ellipsis whitespace-nowrap">{{ episode.number }}. {{ episode.title }}</span>
<span class="flex flex-row item-center gap-2">
- {% if anime_selected.episodes.sub >= episode.number %}
- <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="currentColor" class="size-6 bg-green-700 p-1 rounded" title="Available in Sub">
- <path fill-rule="evenodd" d="M4.848 2.771A49.144 49.144 0 0 1 12 2.25c2.43 0 4.817.178 7.152.52 1.978.292 3.348 2.024 3.348 3.97v6.02c0 1.946-1.37 3.678-3.348 3.97a48.901 48.901 0 0 1-3.476.383.39.39 0 0 0-.297.17l-2.755 4.133a.75.75 0 0 1-1.248 0l-2.755-4.133a.39.39 0 0 0-.297-.17 48.9 48.9 0 0 1-3.476-.384c-1.978-.29-3.348-2.024-3.348-3.97V6.741c0-1.946 1.37-3.68 3.348-3.97ZM6.75 8.25a.75.75 0 0 1 .75-.75h9a.75.75 0 0 1 0 1.5h-9a.75.75 0 0 1-.75-.75Zm.75 2.25a.75.75 0 0 0 0 1.5H12a.75.75 0 0 0 0-1.5H7.5Z" clip-rule="evenodd" />
+ {% if episode.isFiller %}
+ <svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" fill="#fff" class="size-3" style="margin-top:0.35rem;" version="1.1" id="Capa_1" viewBox="0 0 23.758 23.758" xml:space="preserve">
+ <g>
+ <g>
+ <path d="M4.523,23.758V0h14.712v4.021H9.319v5.625h9.916v4.016H9.319v10.096H4.523z"/>
+ </g>
+ </g>
+ </svg>
+ {% endif %}
+
+ {% if anime_selected.anime.info.stats.episodes.sub >= episode.number %}
+ <svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" class="size-6" viewBox="0 0 24 24" version="1.1">
+ <g stroke="none" stroke-width="1" fill="none" fill-rule="evenodd">
+ <g fill="#fff" fill-rule="nonzero">
+ <path d="M18.75,4 C20.5449254,4 22,5.45507456 22,7.25 L22,16.754591 C22,18.5495164 20.5449254,20.004591 18.75,20.004591 L5.25,20.004591 C3.45507456,20.004591 2,18.5495164 2,16.754591 L2,7.25 C2,5.51696854 3.35645477,4.10075407 5.06557609,4.00514479 L5.25,4 L18.75,4 Z M10.6216203,8.59854135 C8.21322176,7.22468635 5.5,8.85441664 5.5,12 C5.5,15.1433285 8.21538655,16.7747125 10.6208022,15.4065583 C10.9808502,15.2017699 11.106713,14.7438795 10.9019246,14.3838314 C10.6971362,14.0237834 10.2392458,13.8979206 9.8791978,14.102709 C8.48410774,14.8962094 7,14.0045685 7,12 C7,9.9935733 8.48070939,9.10416685 9.87837972,9.90145865 C10.2381704,10.1066989 10.6962184,9.98141095 10.9014586,9.62162028 C11.1066989,9.2618296 10.981411,8.80378156 10.6216203,8.59854135 Z M18.1216203,8.59854135 C15.7132218,7.22468635 13,8.85441664 13,12 C13,15.1433285 15.7153866,16.7747125 18.1208022,15.4065583 C18.4808502,15.2017699 18.606713,14.7438795 18.4019246,14.3838314 C18.1971362,14.0237834 17.7392458,13.8979206 17.3791978,14.102709 C15.9841077,14.8962094 14.5,14.0045685 14.5,12 C14.5,9.9935733 15.9807094,9.10416685 17.3783797,9.90145865 C17.7381704,10.1066989 18.1962184,9.98141095 18.4014586,9.62162028 C18.6066989,9.2618296 18.481411,8.80378156 18.1216203,8.59854135 Z"></path>
+ </g>
+ </g>
</svg>
{% endif %}
- {% if anime_selected.episodes.dub >= episode.number %}
- <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="currentColor" class="size-6 bg-blue-700 p-1 rounded" title="Available in Dub">
+ {% if anime_selected.anime.info.stats.episodes.dub >= episode.number %}
+ <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="currentColor" class="size-4 mt-1" title="Available in Dub">
<path d="M8.25 4.5a3.75 3.75 0 1 1 7.5 0v8.25a3.75 3.75 0 1 1-7.5 0V4.5Z" />
<path d="M6 10.5a.75.75 0 0 1 .75.75v1.5a5.25 5.25 0 1 0 10.5 0v-1.5a.75.75 0 0 1 1.5 0v1.5a6.751 6.751 0 0 1-6 6.709v2.291h3a.75.75 0 0 1 0 1.5h-7.5a.75.75 0 0 1 0-1.5h3v-2.291a6.751 6.751 0 0 1-6-6.709v-1.5A.75.75 0 0 1 6 10.5Z" />
</svg>
@@ -45,17 +61,31 @@
</a>
{% else %}
- <a href="{% url "watch:watch_episode" anime_id episode.number %}" class="flex flex-row justify-between w-full gap-4 {% if episode.number in watched_episodes %}bg-{{ user.preferences.accent_colour }}-600 bg-opacity-20{% else %}bg-white bg-opacity-10{% endif %} p-2 rounded hover:bg-{{ user.preferences.accent_colour }}-600 hover:bg-opacity-30">
+ <a href="{% url "watch:watch_episode" anime_id episode.number %}{% if request.GET.mode %}?mode={{ request.GET.mode }}{% endif %}" class="flex flex-row justify-between w-full gap-4 {% if episode.number in watched_episodes %}bg-{{ user.preferences.accent_colour }}-600 bg-opacity-20{% else %}bg-white bg-opacity-10{% endif %} p-2 rounded hover:bg-{{ user.preferences.accent_colour }}-600 hover:bg-opacity-30">
<span class="truncate max-w-full overflow-hidden text-ellipsis whitespace-nowrap">{{ episode.number }}. {{ episode.title }}</span>
<span class="flex flex-row item-center gap-2">
- {% if anime_selected.episodes.sub >= episode.number %}
- <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="currentColor" class="size-6 bg-green-700 p-1 rounded" title="Available in Sub">
- <path fill-rule="evenodd" d="M4.848 2.771A49.144 49.144 0 0 1 12 2.25c2.43 0 4.817.178 7.152.52 1.978.292 3.348 2.024 3.348 3.97v6.02c0 1.946-1.37 3.678-3.348 3.97a48.901 48.901 0 0 1-3.476.383.39.39 0 0 0-.297.17l-2.755 4.133a.75.75 0 0 1-1.248 0l-2.755-4.133a.39.39 0 0 0-.297-.17 48.9 48.9 0 0 1-3.476-.384c-1.978-.29-3.348-2.024-3.348-3.97V6.741c0-1.946 1.37-3.68 3.348-3.97ZM6.75 8.25a.75.75 0 0 1 .75-.75h9a.75.75 0 0 1 0 1.5h-9a.75.75 0 0 1-.75-.75Zm.75 2.25a.75.75 0 0 0 0 1.5H12a.75.75 0 0 0 0-1.5H7.5Z" clip-rule="evenodd" />
+ {% if episode.isFiller %}
+ <svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" fill="#fff" class="size-3" style="margin-top:0.35rem;" version="1.1" id="Capa_1" viewBox="0 0 23.758 23.758" xml:space="preserve">
+ <g>
+ <g>
+ <path d="M4.523,23.758V0h14.712v4.021H9.319v5.625h9.916v4.016H9.319v10.096H4.523z"/>
+ </g>
+ </g>
+ </svg>
+ {% endif %}
+
+ {% if anime_selected.anime.info.stats.episodes.sub >= episode.number %}
+ <svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" class="size-6" viewBox="0 0 24 24" version="1.1">
+ <g stroke="none" stroke-width="1" fill="none" fill-rule="evenodd">
+ <g fill="#fff" fill-rule="nonzero">
+ <path d="M18.75,4 C20.5449254,4 22,5.45507456 22,7.25 L22,16.754591 C22,18.5495164 20.5449254,20.004591 18.75,20.004591 L5.25,20.004591 C3.45507456,20.004591 2,18.5495164 2,16.754591 L2,7.25 C2,5.51696854 3.35645477,4.10075407 5.06557609,4.00514479 L5.25,4 L18.75,4 Z M10.6216203,8.59854135 C8.21322176,7.22468635 5.5,8.85441664 5.5,12 C5.5,15.1433285 8.21538655,16.7747125 10.6208022,15.4065583 C10.9808502,15.2017699 11.106713,14.7438795 10.9019246,14.3838314 C10.6971362,14.0237834 10.2392458,13.8979206 9.8791978,14.102709 C8.48410774,14.8962094 7,14.0045685 7,12 C7,9.9935733 8.48070939,9.10416685 9.87837972,9.90145865 C10.2381704,10.1066989 10.6962184,9.98141095 10.9014586,9.62162028 C11.1066989,9.2618296 10.981411,8.80378156 10.6216203,8.59854135 Z M18.1216203,8.59854135 C15.7132218,7.22468635 13,8.85441664 13,12 C13,15.1433285 15.7153866,16.7747125 18.1208022,15.4065583 C18.4808502,15.2017699 18.606713,14.7438795 18.4019246,14.3838314 C18.1971362,14.0237834 17.7392458,13.8979206 17.3791978,14.102709 C15.9841077,14.8962094 14.5,14.0045685 14.5,12 C14.5,9.9935733 15.9807094,9.10416685 17.3783797,9.90145865 C17.7381704,10.1066989 18.1962184,9.98141095 18.4014586,9.62162028 C18.6066989,9.2618296 18.481411,8.80378156 18.1216203,8.59854135 Z"></path>
+ </g>
+ </g>
</svg>
{% endif %}
- {% if anime_selected.episodes.dub >= episode.number %}
- <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="currentColor" class="size-6 bg-blue-700 p-1 rounded" title="Available in Dub">
+ {% if anime_selected.anime.info.stats.episodes.dub >= episode.number %}
+ <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="currentColor" class="size-4 mt-1" title="Available in Dub">
<path d="M8.25 4.5a3.75 3.75 0 1 1 7.5 0v8.25a3.75 3.75 0 1 1-7.5 0V4.5Z" />
<path d="M6 10.5a.75.75 0 0 1 .75.75v1.5a5.25 5.25 0 1 0 10.5 0v-1.5a.75.75 0 0 1 1.5 0v1.5a6.751 6.751 0 0 1-6 6.709v2.291h3a.75.75 0 0 1 0 1.5h-7.5a.75.75 0 0 1 0-1.5h3v-2.291a6.751 6.751 0 0 1-6-6.709v-1.5A.75.75 0 0 1 6 10.5Z" />
</svg>
@@ -67,9 +97,484 @@
</div>
</div>
</div>
+<div class="flex flex-col lg:flex-row my-4 gap-2">
+ <div class="w-full lg:w-3/4">
+ <div class="flex flex-row gap-2 items-center justify-between">
+ <h2 class="text-xl font-bold truncate max-w-full overflow-hidden text-ellipsis whitespace-nowrap">Episode {{ current_episode_data.number }} — {{ current_episode_data.title }}</h2>
+ <div class="flex flex-row gap-1 items-center">
+ <a href="{% url "watch:watch_episode" anime_id current_episode_data.number %}?mode=sub" class="{% if mode == "sub" %}bg-{{ user.preferences.accent_colour }}-600{% else %}bg-white bg-opacity-10{% endif %} text-white text-sm font-bold px-4 py-2 rounded">Sub</a>
+ <a href="{% url "watch:watch_episode" anime_id current_episode_data.number %}?mode=dub" class="{% if mode == "dub" %}bg-{{ user.preferences.accent_colour }}-600{% else %}bg-white bg-opacity-10{% endif %} text-white text-sm font-bold px-4 py-2 rounded">Dub</a>
+ </div>
+ </div>
+ <div class="flex flex-col lg:flex-row w-full my-4 bg-neutral-950 rounded p-2 gap-4">
+ <div class="flex flex-col items-center lg:items-start gap-2 min-w-32">
+ <img src="{{ anime_data.image }}" alt="{{ anime_data.title.english }}" class="rounded-lg w-48 h-72 object-cover"/>
+ <div class="flex flex-row gap-2">
+ <a href="https://anilist.co/anime/{{ anime_data.id }}" target="_blank" class="text-xs font-bold bg-white bg-opacity-10 rounded px-2 py-1">
+ <svg stroke="currentColor" fill="currentColor" stroke-width="0" role="img" viewBox="0 0 24 24" height="1.5rem" width="1.5rem" xmlns="http://www.w3.org/2000/svg"><path d="M24 17.53v2.421c0 .71-.391 1.101-1.1 1.101h-5l-.057-.165L11.84 3.736c.106-.502.46-.788 1.053-.788h2.422c.71 0 1.1.391 1.1 1.1v12.38H22.9c.71 0 1.1.392 1.1 1.101zM11.034 2.947l6.337 18.104h-4.918l-1.052-3.131H6.019l-1.077 3.131H0L6.361 2.948h4.673zm-.66 10.96-1.69-5.014-1.541 5.015h3.23z"></path></svg>
+ </a>
+ <a href="https://myanimelist.net/anime/{{ anime_data.malId }}" target="_blank" class="ext-xs font-bold bg-white bg-opacity-10 rounded px-2 py-1">
+ <svg stroke="currentColor" fill="currentColor" stroke-width="0" role="img" viewBox="0 0 24 24" height="1.5rem" width="1.5rem" xmlns="http://www.w3.org/2000/svg"><path d="M8.273 7.247v8.423l-2.103-.003v-5.216l-2.03 2.404-1.989-2.458-.02 5.285H.001L0 7.247h2.203l1.865 2.545 2.015-2.546 2.19.001zm8.628 2.069l.025 6.335h-2.365l-.008-2.871h-2.8c.07.499.21 1.266.417 1.779.155.381.298.751.583 1.128l-1.705 1.125c-.349-.636-.622-1.337-.878-2.082a9.296 9.296 0 0 1-.507-2.179c-.085-.75-.097-1.471.107-2.212a3.908 3.908 0 0 1 1.161-1.866c.313-.293.749-.5 1.1-.687.351-.187.743-.264 1.107-.359a7.405 7.405 0 0 1 1.191-.183c.398-.034 1.107-.066 2.39-.028l.545 1.749H14.51c-.593.008-.878.001-1.341.209a2.236 2.236 0 0 0-1.278 1.92l2.663.033.038-1.81h2.309zm3.992-2.099v6.627l3.107.032-.43 1.775h-4.807V7.187l2.13.03z"></path></svg>
+ </a>
+ </div>
+ </div>
+ <div class="flex flex-col gap-2 w-full">
+ <h2 class="text-2xl font-bold text-transparent bg-clip-text block w-full truncate overflow-hidden text-ellipsis whitespace-nowrap" style="background: linear-gradient(-45deg, {% if anime_data.color %}{{ anime_data.color }}{% else %}white{% endif %}, white); -webkit-background-clip: text; background-clip: text;">
+ {% if user.preferences.title_language == "english" and anime_data.title.english %}
+ {{ anime_data.title.english }}
+ {% elif user.preferences.title_language == "native" and anime_data.title.native %}
+ {{ anime_data.title.native }}
+ {% else %}
+ {{ anime_data.title.romaji }}
+ {% endif %}
+ </h2>
+ <p class="max-h-24 overflow-auto text-sm text-white mb-4 no-scrollbar">
+ {{ anime_data.description|strip_html }}
+ </p>
+ <div class="flex flex-col gap-2 mb-4">
+ <div class="flex flex-row gap-4">
+ <div class="flex-1">
+ <span class="font-bold">Format: </span>{{ anime_data.type }}
+ </div>
+ <div class="flex-1">
+ <span class="font-bold">Episodes: </span>{{ anime_data.totalEpisodes }}
+ </div>
+ </div>
+ <div class="flex flex-row gap-4">
+ <div class="flex-1">
+ <span class="font-bold">Year: </span>{{ anime_data.releaseDate }}
+ </div>
+ <div class="flex-1">
+ <span class="font-bold">Duration: </span>{{ anime_data.duration }} mins
+ </div>
+ </div>
+ <div class="flex flex-row gap-4">
+ <div class="flex-1">
+ <span class="font-bold">Status: </span>{{ anime_data.status }}
+ </div>
+ <div class="flex-1 capitalize">
+ <span class="font-bold">Season: </span>{{ anime_data.season }}
+ </div>
+ </div>
+ <div class="flex flex-row gap-4">
+ <div class="flex-1">
+ <span class="font-bold">Rating: </span>{{ anime_data.rating }} / 100
+ </div>
+ <div class="flex-1">
+ <span class="font-bold">Popularity: </span>{{ anime_data.popularity }}
+ </div>
+ </div>
+ <div class="flex flex-row gap-4">
+ <div class="flex-1">
+ <span class="font-bold">Country: </span>{{ anime_data.countryOfOrigin }}
+ </div>
+ <div class="flex-1">
+ <span class="font-bold">Studios: </span>
+ {% for studio in anime_data.studios %}
+ <span>{{ studio }}</span>{% if not forloop.last %}, {% endif %}
+ {% endfor %}
+ </div>
+ </div>
+ </div>
+ <span class="text-xs sm:text-sm font-bold flex gap-2 flex-row flex-wrap items-center">
+ {% if anime_data.status == "Ongoing" %}
+ <span class="text-xs font-bold bg-white bg-opacity-10 p-1 rounded flex items-center gap-1">
+ <span class="text-green-500 pt-1">
+ <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>
+ Ongoing
+ </span>
+ {% elif anime_data.status == "Not yet aired" %}
+ <span class="text-xs font-bold bg-white bg-opacity-10 p-1 rounded flex items-center gap-1">
+ <span class="text-yellow-500 pt-1">
+ <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>
+ Not yet aired
+ </span>
+ {% else %}
+ <span class="text-xs font-bold bg-white bg-opacity-10 p-1 rounded flex items-center gap-1">
+ <span class="text-blue-500 pt-1">
+ <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>
+ Finished
+ </span>
+ {% endif %}
+ <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>
+ {{ anime_data.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 fill-rule="evenodd" d="M6.75 2.25A.75.75 0 0 1 7.5 3v1.5h9V3A.75.75 0 0 1 18 3v1.5h.75a3 3 0 0 1 3 3v11.25a3 3 0 0 1-3 3H5.25a3 3 0 0 1-3-3V7.5a3 3 0 0 1 3-3H6V3a.75.75 0 0 1 .75-.75Zm13.5 9a1.5 1.5 0 0 0-1.5-1.5H5.25a1.5 1.5 0 0 0-1.5 1.5v7.5a1.5 1.5 0 0 0 1.5 1.5h13.5a1.5 1.5 0 0 0 1.5-1.5v-7.5Z" clip-rule="evenodd" />
+ </svg>
+ {{ anime_data.releaseDate }}
+ </span>
+ {% if anime_data.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>
+ {{ anime_data.rating }}
+ </span>
+ {% endif %}
+ {% if anime_data.totalEpisodes %}
+ <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>
+ {{ anime_data.totalEpisodes }}
+ </span>
+ {% endif %}
+ </span>
+ <span class="text-xs sm:text-sm font-bold flex gap-2 flex-row flex-wrap items-center">
+ {% for genre in anime_data.genres %}
+ {% if genre == "Action" %}
+ <span class="text-xs font-bold bg-green-100 bg-opacity-10 text-green-300 py-1 px-2 rounded-full">
+ {% elif genre == "Adventure" %}
+ <span class="text-xs font-bold bg-pink-100 bg-opacity-10 text-pink-300 py-1 px-2 rounded-full">
+ {% elif genre == "Cars" %}
+ <span class="text-xs font-bold bg-orange-100 bg-opacity-10 text-orange-300 py-1 px-2 rounded-full">
+ {% elif genre == "Comedy" %}
+ <span class="text-xs font-bold bg-purple-100 bg-opacity-10 text-purple-300 py-1 px-2 rounded-full">
+ {% elif genre == "Drama" %}
+ <span class="text-xs font-bold bg-blue-100 bg-opacity-10 text-blue-300 py-1 px-2 rounded-full">
+ {% elif genre == "Fantasy" %}
+ <span class="text-xs font-bold bg-yellow-100 bg-opacity-10 text-yellow-300 py-1 px-2 rounded-full">
+ {% elif genre == "Horror" %}
+ <span class="text-xs font-bold bg-red-100 bg-opacity-10 text-red-300 py-1 px-2 rounded-full">
+ {% elif genre == "Mahou Shoujo" %}
+ <span class="text-xs font-bold bg-teal-100 bg-opacity-10 text-teal-300 py-1 px-2 rounded-full">
+ {% elif genre == "Mecha" %}
+ <span class="text-xs font-bold bg-indigo-100 bg-opacity-10 text-indigo-300 py-1 px-2 rounded-full">
+ {% elif genre == "Music" %}
+ <span class="text-xs font-bold bg-pink-100 bg-opacity-10 text-pink-300 py-1 px-2 rounded-full">
+ {% elif genre == "Mystery" %}
+ <span class="text-xs font-bold bg-purple-100 bg-opacity-10 text-purple-300 py-1 px-2 rounded-full">
+ {% elif genre == "Psychological" %}
+ <span class="text-xs font-bold bg-blue-100 bg-opacity-10 text-blue-300 py-1 px-2 rounded-full">
+ {% elif genre == "Romance" %}
+ <span class="text-xs font-bold bg-yellow-100 bg-opacity-10 text-yellow-300 py-1 px-2 rounded-full">
+ {% elif genre == "Sci-Fi" %}
+ <span class="text-xs font-bold bg-red-100 bg-opacity-10 text-red-300 py-1 px-2 rounded-full">
+ {% elif genre == "Slice of Life" %}
+ <span class="text-xs font-bold bg-teal-100 bg-opacity-10 text-teal-300 py-1 px-2 rounded-full">
+ {% elif genre == "Sports" %}
+ <span class="text-xs font-bold bg-indigo-100 bg-opacity-10 text-indigo-300 py-1 px-2 rounded-full">
+ {% elif genre == "Supernatural" %}
+ <span class="text-xs font-bold bg-green-100 bg-opacity-10 text-green-300 py-1 px-2 rounded-full">
+ {% elif genre == "Thriller" %}
+ <span class="text-xs font-bold bg-orange-100 bg-opacity-10 text-orange-300 py-1 px-2 rounded-full">
+ {% else %}
+ <span class="text-xs font-bold bg-white bg-opacity-10 text-white py-1 px-2 rounded-full">
+ {% endif %}
+ {{ genre }}
+ </span>
+ {% endfor %}
+ </span>
+ </div>
+ </div>
+ {% if anime_data.characters %}
+ <div class="my-4">
+ <h2 class="text-xl font-bold text-white uppercase flex flex-row items-center gap-1 mb-4">
+ Characters &amp; Voice Actors
+ </h2>
+ <div class="flex flex-wrap">
+ {% for character in anime_data.characters %}
+ <div class="w-1/2 p-2 flex justify-between">
+ <div class="flex flex-row gap-2 items-center">
+ <img src="{{ character.image }}" alt="{{ character.name }}" class="rounded-full w-16 h-16 object-cover"/>
+ <div class="flex flex-col gap-2">
+ <span class="font-bold">
+ {% if user.preferences.character_name_language == "romaji" %}
+ {{ character.name.full }}
+ {% else %}
+ {{ character.name.native }}
+ {% endif %}
+ </span>
+ <span class="capitalize">{{ character.role }}</span>
+ </div>
+ </div>
+ <div class="flex flex-col items-end">
+ {% for voice_actor in character.voiceActors|slice:":1" %}
+ <div class="flex flex-row gap-2 items-center mb-2">
+ <div class="flex flex-col gap-2 text-right">
+ <span class="font-bold">
+ {% if user.preferences.character_name_language == "romaji" %}
+ {{ voice_actor.name.full }}
+ {% else %}
+ {{ voice_actor.name.native }}
+ {% endif %}
+ </span>
+ <span class="capitalize">{{ voice_actor.language }}</span>
+ </div>
+ <img src="{{ voice_actor.image }}" alt="{{ voice_actor.name }}" class="rounded-full w-16 h-16 object-cover"/>
+ </div>
+ {% endfor %}
+ </div>
+ </div>
+ {% endfor %}
+ </div>
+ </div>
+ {% endif %}
+ </div>
+ <div class="w-full lg:w-1/4">
+ {% if anime_data.relations %}
+ <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="flex flex-col gap-2">
+ {% for related in anime_data.relations %}
+ {% 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="{% url 'watch:watch' related.id %}" 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 %}
+ <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.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 anime_data.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="flex flex-col gap-2">
+ {% for recommendation in anime_data.recommendations %}
+ <a href="{% url 'watch:watch' recommendation.id %}" 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>
+ {% endfor %}
+ </div>
+ {% endif %}
+ </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>
{% endblock content %}
{% block scripts %}
<script>
+ function showToast(message, isSuccess) {
+ const toast = document.createElement('div');
+ toast.className = `flex items-center p-4 rounded-md shadow-lg transition-opacity duration-500 ease-in-out animate__animated ${
+ isSuccess ? 'bg-green-100 text-green-700 animate__fadeInUp' : 'bg-red-100 text-red-700 animate__fadeInUp'
+ }`;
+
+ const checkSVG = `<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="m4.5 12.75 6 6 9-13.5" /></svg>`
+
+ const errorSVG = `<svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke-width="1.5" stroke="currentColor" class="size-6"> <path stroke-linecap="round" stroke-linejoin="round" d="M12 9v3.75m-9.303 3.376c-.866 1.5.217 3.374 1.948 3.374h14.71c1.73 0 2.813-1.874 1.948-3.374L13.949 3.378c-.866-1.5-3.032-1.5-3.898 0L2.697 16.126ZM12 15.75h.007v.008H12v-.008Z" /></svg>`
+
+ toast.innerHTML = `
+ <div class="flex items-center">
+ ${isSuccess ? checkSVG : errorSVG}
+ <span class="ml-2">${message}</span>
+ </div>
+ `;
+
+ // Append the toast to the container
+ toastContainer.appendChild(toast);
+
+ // Remove the toast after 3 seconds
+ setTimeout(() => {
+ toast.classList.add('animate__fadeOutDown');
+ setTimeout(() => {
+ toastContainer.removeChild(toast);
+ }, 500);
+ }, 3000);
+ }
+
window.addEventListener('DOMContentLoaded', (event) => {
const selectedEpisode = document.getElementById('selected-episode');
if (selectedEpisode) {
diff --git a/watch/utils.py b/watch/utils.py
index 1b0435f..9603c63 100644
--- a/watch/utils.py
+++ b/watch/utils.py
@@ -33,7 +33,11 @@ def get_anime_user_history(user, anime_id):
def store_in_redis_cache(anime_id, data):
- r.set(anime_id, data, ex=60*60) # 1 hour
+ try:
+ r.set(anime_id, data, ex=60*60) # 1 hour
+ except Exception as e:
+ print(e)
+ pass
def get_from_redis_cache(anime_id):
data = r.get(anime_id)
diff --git a/watch/views.py b/watch/views.py
index 3b95946..2074903 100644
--- a/watch/views.py
+++ b/watch/views.py
@@ -9,7 +9,6 @@ import json
dotenv.load_dotenv()
def watch(request, anime_id, episode=None):
- # store anime history
anime_history = get_anime_user_history(request.user, anime_id)
watched_episodes = [h.episode for h in anime_history]
@@ -23,44 +22,34 @@ def watch(request, anime_id, episode=None):
mode = request.GET.get("mode", request.user.preferences.default_language)
- anime_data_cached = get_from_redis_cache(anime_id)
+ anime_data_cached = get_from_redis_cache(f"anime_{anime_id}_anime_data")
+ anime_selected_cached = get_from_redis_cache(f"anime_{anime_id}_anime_selected")
+ anime_episodes_cached = get_from_redis_cache(f"anime_{anime_id}_anime_episodes")
if not anime_data_cached:
- base_url = f"{os.getenv("CONSUMET_URL")}/meta/anilist/data/{anime_id}?provider=zoro"
+ base_url = f"{os.getenv("CONSUMET_URL")}/meta/anilist/info/{anime_id}?provider=zoro"
response = requests.get(base_url)
anime_data = response.json()
+ store_in_redis_cache(f"anime_{anime_id}_anime_data", json.dumps(anime_data))
+ else:
+ anime_data = json.loads(anime_data_cached)
- base_url = f"{os.getenv("ZORO_URL")}/anime/search?q={anime_data["title"]["english"]}&page=1"
+ if not anime_selected_cached:
+ z_anime_id = anime_data["episodes"][0]["id"].split("$")[0]
+ base_url = f"{os.getenv("ZORO_URL")}/anime/info?id={z_anime_id}"
response = requests.get(base_url)
- anime_search_result = response.json()
- anime_selected = [a for a in anime_search_result["animes"] if a["name"].lower() == anime_data["title"]["english"].lower()]
-
- if not anime_selected:
- anime_selected = anime_search_result["animes"][0]
- else:
- anime_selected = anime_selected[0]
+ anime_selected = response.json()
+ store_in_redis_cache(f"anime_{anime_id}_anime_selected", json.dumps(anime_selected))
+ else:
+ anime_selected = json.loads(anime_selected_cached)
- base_url = f"{os.getenv("ZORO_URL")}/anime/episodes/{anime_selected["id"]}"
+ if not anime_episodes_cached:
+ base_url = f"{os.getenv("ZORO_URL")}/anime/episodes/{anime_selected["anime"]["info"]["id"]}"
response = requests.get(base_url)
anime_episodes = response.json()
+ store_in_redis_cache(f"anime_{anime_id}_anime_episodes", json.dumps(anime_episodes))
- anime_data_to_cache = {
- "anime_data": anime_data,
- "anime_selected": anime_selected,
- "anime_episodes": anime_episodes,
- }
-
- store_in_redis_cache(anime_id, json.dumps(anime_data_to_cache))
- else:
- anime_data_cached = json.loads(anime_data_cached)
- anime_data = anime_data_cached["anime_data"]
- anime_selected = anime_data_cached["anime_selected"]
- anime_episodes = anime_data_cached["anime_episodes"]
-
- if mode == "dub" and not anime_selected["episodes"]["dub"]:
- mode = "sub"
-
- if not anime_selected["episodes"][mode] or anime_selected["episodes"][mode] < episode:
+ if not anime_selected["anime"]["info"]["stats"]["episodes"][mode] or anime_selected["anime"]["info"]["stats"]["episodes"][mode] < episode:
mode = "sub"
if episode > anime_episodes["totalEpisodes"]:
@@ -81,7 +70,6 @@ def watch(request, anime_id, episode=None):
if not any(t["kind"] == "captions" for t in episode_data["tracks"]) and mode == "dub" and request.user.preferences.ingrain_sub_subtitles_in_dub:
base_url = f"{os.getenv("ZORO_URL")}/anime/episode-srcs?id={episode_d["episodeId"]}?server&category=sub"
response = requests.get(base_url).json()
- # attach the sub captions to the dub episode data, append - do not replace
captions = [t for t in response["tracks"] if t["kind"] == "captions"]
if captions:
episode_data["tracks"].extend(captions)
@@ -90,12 +78,15 @@ def watch(request, anime_id, episode=None):
update_anime_user_history(request.user, anime_id, episode, current_watched_time)
+ current_episode_data = [e for e in anime_episodes["episodes"] if e["number"] == episode][0]
+
context = {
"anime_data": anime_data,
"anime_selected": anime_selected,
"anime_episodes": anime_episodes,
"episode_data": episode_data,
"current_episode": episode,
+ "current_episode_data": current_episode_data,
"stream_url": episode_data["sources"][0]["url"],
"anime_id": anime_id,
"current_episode_name": current_episode_name,