2185 lines
64 KiB
HTML
2185 lines
64 KiB
HTML
<!DOCTYPE html>
|
|
<html lang="en">
|
|
<head>
|
|
<meta charset="UTF-8" />
|
|
<meta
|
|
name="viewport"
|
|
content="width=device-width, initial-scale=1.0, user-scalable=no"
|
|
/>
|
|
<title>Series Info - Media App</title>
|
|
<link rel="preconnect" href="https://fonts.googleapis.com" />
|
|
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin />
|
|
<link
|
|
href="https://fonts.googleapis.com/css2?family=Inter:wght@400;500;600;700;800&display=swap"
|
|
rel="stylesheet"
|
|
/>
|
|
<link
|
|
rel="stylesheet"
|
|
href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.5.1/css/all.min.css"
|
|
/>
|
|
<!-- csPlayer CSS -->
|
|
<link
|
|
rel="stylesheet"
|
|
href="https://cdn.jsdelivr.net/gh/abtp2/csPlayer/src/csPlayer.css"
|
|
/>
|
|
|
|
<!-- csPlayer JS -->
|
|
<script src="https://cdn.jsdelivr.net/gh/abtp2/csPlayer/src/csPlayer.js"></script>
|
|
|
|
<style>
|
|
/* =========================================
|
|
1. VARIABLES & RESET
|
|
========================================= */
|
|
:root {
|
|
--brand-accent: #ff9500;
|
|
--brand-accent-hover: #ffae40;
|
|
--background-primary: #0a0a0a;
|
|
--background-secondary: #121212;
|
|
--background-tertiary: #1a1a1a;
|
|
|
|
--text-primary: #eaeaea;
|
|
--text-secondary: #a0a0a0;
|
|
--text-muted: #666666;
|
|
|
|
--border-color: rgba(255, 255, 255, 0.08); /* Softened from solid gray */
|
|
--shadow-color: rgba(0, 0, 0, 0.6);
|
|
|
|
--border-radius: 16px; /* Modernized from 12px */
|
|
--border-radius-sm: 10px;
|
|
--transition-duration: 0.3s;
|
|
}
|
|
|
|
* {
|
|
margin: 0;
|
|
padding: 0;
|
|
box-sizing: border-box;
|
|
}
|
|
|
|
html {
|
|
-webkit-font-smoothing: antialiased;
|
|
scroll-behavior: smooth;
|
|
}
|
|
|
|
body {
|
|
background-color: var(--background-primary);
|
|
color: var(--text-primary);
|
|
font-family: "Inter", sans-serif;
|
|
line-height: 1.6;
|
|
overflow-x: hidden;
|
|
}
|
|
|
|
::-webkit-scrollbar {
|
|
width: 8px;
|
|
height: 8px;
|
|
}
|
|
::-webkit-scrollbar-track {
|
|
background: var(--background-primary);
|
|
}
|
|
::-webkit-scrollbar-thumb {
|
|
background: #333;
|
|
border-radius: 4px;
|
|
}
|
|
::-webkit-scrollbar-thumb:hover {
|
|
background: #555;
|
|
}
|
|
|
|
/* =========================================
|
|
2. SKELETON LOADERS (PHASE 6)
|
|
========================================= */
|
|
@keyframes shimmer {
|
|
0% { background-position: -1000px 0; }
|
|
100% { background-position: 1000px 0; }
|
|
}
|
|
|
|
.skeleton {
|
|
background: linear-gradient(
|
|
90deg,
|
|
rgba(255, 255, 255, 0.03) 25%,
|
|
rgba(255, 255, 255, 0.08) 50%,
|
|
rgba(255, 255, 255, 0.03) 75%
|
|
);
|
|
background-size: 1000px 100%;
|
|
animation: shimmer 2s infinite linear;
|
|
border-radius: 8px;
|
|
}
|
|
|
|
.skeleton-text {
|
|
height: 1em;
|
|
width: 100%;
|
|
margin-bottom: 0.5rem;
|
|
border-radius: 4px;
|
|
}
|
|
|
|
.skeleton-text.short { width: 50%; }
|
|
.skeleton-text.medium { width: 75%; }
|
|
.skeleton-title { height: 3rem; width: 60%; margin-bottom: 1.2rem; }
|
|
.skeleton-poster { width: 100%; height: 100%; position: absolute; inset: 0; }
|
|
.skeleton-ep-card { width: 100%; height: 90px; border-radius: 12px; margin-bottom: 12px; }
|
|
|
|
/* =========================================
|
|
3. APP STRUCTURE & HERO
|
|
========================================= */
|
|
.app-container {
|
|
width: 100%;
|
|
min-height: 100vh;
|
|
display: flex;
|
|
flex-direction: column;
|
|
}
|
|
|
|
.series-hero-section {
|
|
position: relative;
|
|
height: 65vh;
|
|
min-height: 550px;
|
|
background-position: top center;
|
|
background-size: cover;
|
|
display: flex;
|
|
flex-direction: column;
|
|
justify-content: flex-end;
|
|
padding: 80px 5% 3rem;
|
|
transition: background-image 0.3s ease;
|
|
}
|
|
|
|
.hero-overlay {
|
|
position: absolute;
|
|
inset: 0;
|
|
background: linear-gradient(
|
|
to top,
|
|
var(--background-primary) 5%,
|
|
rgba(10, 10, 10, 0.8) 45%,
|
|
rgba(10, 10, 10, 0.4) 75%,
|
|
rgba(0, 0, 0, 0.3) 100%
|
|
);
|
|
z-index: 1;
|
|
pointer-events: none;
|
|
}
|
|
|
|
.series-hero-content {
|
|
position: relative;
|
|
z-index: 2;
|
|
width: 100%;
|
|
display: flex;
|
|
gap: 3rem;
|
|
align-items: flex-end;
|
|
max-width: 1500px;
|
|
margin: 0 auto;
|
|
}
|
|
|
|
/* Poster */
|
|
.hero-poster-container {
|
|
flex-shrink: 0;
|
|
width: 240px;
|
|
border-radius: var(--border-radius);
|
|
overflow: hidden;
|
|
box-shadow: 0 20px 50px rgba(0, 0, 0, 0.8);
|
|
border: 1px solid var(--border-color);
|
|
background: var(--background-tertiary);
|
|
transform: translateY(60px);
|
|
display: none;
|
|
z-index: 5;
|
|
position: relative;
|
|
}
|
|
.hero-poster-img {
|
|
width: 100%;
|
|
height: auto;
|
|
display: block;
|
|
aspect-ratio: 2/3;
|
|
object-fit: cover;
|
|
position: relative;
|
|
z-index: 2;
|
|
}
|
|
|
|
.hero-text-content {
|
|
flex: 1;
|
|
display: flex;
|
|
flex-direction: column;
|
|
justify-content: flex-end;
|
|
padding-bottom: 20px;
|
|
min-width: 0;
|
|
}
|
|
|
|
.series-title-text {
|
|
font-size: clamp(2.2rem, 5vw, 4.5rem);
|
|
font-weight: 800;
|
|
color: var(--text-primary);
|
|
text-shadow: 0 4px 30px rgba(0, 0, 0, 0.9);
|
|
line-height: 1.1;
|
|
margin-bottom: 1.2rem;
|
|
letter-spacing: -0.03em;
|
|
}
|
|
|
|
/* Metadata Pills */
|
|
.hero-metadata-capsules {
|
|
display: flex;
|
|
flex-wrap: wrap;
|
|
gap: 12px;
|
|
align-items: center;
|
|
margin-bottom: 2rem;
|
|
font-size: 0.9rem;
|
|
font-weight: 600;
|
|
}
|
|
|
|
.meta-capsule {
|
|
background: rgba(255, 255, 255, 0.05);
|
|
border: 1px solid rgba(255, 255, 255, 0.1);
|
|
padding: 8px 16px;
|
|
border-radius: 50px;
|
|
color: #ddd;
|
|
backdrop-filter: blur(12px); /* Enhanced Glassmorphism */
|
|
display: flex;
|
|
align-items: center;
|
|
gap: 8px;
|
|
box-shadow: 0 4px 15px rgba(0, 0, 0, 0.2);
|
|
}
|
|
|
|
.rating-pill {
|
|
padding: 8px 14px;
|
|
border-radius: 50px;
|
|
font-weight: 800;
|
|
text-transform: uppercase;
|
|
font-size: 0.85rem;
|
|
background: #333;
|
|
color: #fff;
|
|
box-shadow: 0 4px 10px rgba(0, 0, 0, 0.3);
|
|
}
|
|
.rating-r { background: linear-gradient(135deg, #e53935, #b71c1c); }
|
|
.rating-pg { background: linear-gradient(135deg, #fb8c00, #e65100); }
|
|
.rating-g { background: linear-gradient(135deg, #43a047, #1b5e20); }
|
|
|
|
.studio-text {
|
|
color: var(--brand-accent);
|
|
font-weight: 700;
|
|
text-shadow: 0 2px 10px rgba(0, 0, 0, 0.8);
|
|
}
|
|
|
|
/* Hero Actions */
|
|
.hero-actions {
|
|
display: flex;
|
|
flex-wrap: wrap;
|
|
align-items: center;
|
|
gap: 14px;
|
|
}
|
|
|
|
.hero-watch-btn {
|
|
display: inline-flex;
|
|
align-items: center;
|
|
justify-content: center;
|
|
gap: 12px;
|
|
background-color: var(--brand-accent);
|
|
color: #000;
|
|
padding: 0 32px;
|
|
height: 52px;
|
|
border-radius: 14px;
|
|
border: none;
|
|
font-size: 1.05rem;
|
|
font-weight: 800;
|
|
cursor: pointer;
|
|
box-shadow: 0 6px 20px rgba(255, 149, 0, 0.25);
|
|
transition: all 0.3s cubic-bezier(0.25, 0.46, 0.45, 0.94);
|
|
text-transform: uppercase;
|
|
letter-spacing: 0.5px;
|
|
}
|
|
.hero-watch-btn:hover {
|
|
background-color: var(--brand-accent-hover);
|
|
transform: translateY(-3px) scale(1.02);
|
|
box-shadow: 0 10px 30px rgba(255, 149, 0, 0.4);
|
|
}
|
|
|
|
/* New Locate Episode Button (Phase 2) */
|
|
.hero-locate-btn {
|
|
display: inline-flex;
|
|
align-items: center;
|
|
justify-content: center;
|
|
width: 52px;
|
|
height: 52px;
|
|
border-radius: 14px;
|
|
background: rgba(255, 255, 255, 0.08);
|
|
border: 1px solid rgba(255, 255, 255, 0.15);
|
|
color: var(--text-primary);
|
|
font-size: 1.2rem;
|
|
cursor: pointer;
|
|
backdrop-filter: blur(12px);
|
|
transition: all 0.3s cubic-bezier(0.25, 0.46, 0.45, 0.94);
|
|
}
|
|
.hero-locate-btn:hover {
|
|
background: rgba(255, 255, 255, 0.15);
|
|
border-color: #fff;
|
|
transform: translateY(-3px) scale(1.05);
|
|
box-shadow: 0 8px 25px rgba(255, 255, 255, 0.1);
|
|
color: var(--brand-accent);
|
|
}
|
|
|
|
/* RX watch button states */
|
|
.hero-watch-btn.rx-rated {
|
|
transition: all 0.3s cubic-bezier(0.25, 0.46, 0.45, 0.94), background-color 0.15s ease;
|
|
}
|
|
.hero-watch-btn.rx-rated:hover {
|
|
cursor: not-allowed !important;
|
|
background-color: #ff9500;
|
|
box-shadow: 0 10px 30px rgba(192, 57, 43, 0.5);
|
|
transform: translateY(-3px) scale(1.02);
|
|
}
|
|
|
|
.hero-action-btn {
|
|
display: inline-flex;
|
|
align-items: center;
|
|
justify-content: center;
|
|
gap: 8px;
|
|
background: rgba(255, 255, 255, 0.05);
|
|
border: 1px solid rgba(255, 255, 255, 0.1);
|
|
color: var(--text-primary);
|
|
padding: 0 24px;
|
|
height: 52px;
|
|
border-radius: 14px;
|
|
font-size: 0.95rem;
|
|
font-weight: 600;
|
|
cursor: pointer;
|
|
backdrop-filter: blur(12px);
|
|
transition: all 0.3s cubic-bezier(0.25, 0.46, 0.45, 0.94);
|
|
}
|
|
.hero-action-btn:hover {
|
|
background: rgba(255, 255, 255, 0.15);
|
|
border-color: rgba(255, 255, 255, 0.3);
|
|
transform: translateY(-2px) scale(1.02);
|
|
}
|
|
|
|
.back-btn {
|
|
position: absolute;
|
|
top: 30px;
|
|
left: 30px;
|
|
z-index: 10;
|
|
background: rgba(0, 0, 0, 0.4);
|
|
border: 1px solid rgba(255, 255, 255, 0.1);
|
|
color: #fff;
|
|
width: 48px;
|
|
height: 48px;
|
|
border-radius: 50%;
|
|
display: flex;
|
|
align-items: center;
|
|
justify-content: center;
|
|
cursor: pointer;
|
|
backdrop-filter: blur(10px);
|
|
transition: all 0.3s cubic-bezier(0.25, 0.46, 0.45, 0.94);
|
|
}
|
|
.back-btn:hover {
|
|
transform: scale(1.1);
|
|
background: rgba(255, 255, 255, 0.1);
|
|
border-color: rgba(255, 255, 255, 0.3);
|
|
}
|
|
|
|
/* =========================================
|
|
4. MAIN CONTENT
|
|
========================================= */
|
|
.main-content {
|
|
position: relative;
|
|
padding: 2rem 5%;
|
|
background-color: var(--background-primary);
|
|
z-index: 3;
|
|
}
|
|
|
|
.desktop-center-wrapper {
|
|
max-width: 1500px;
|
|
margin: 0 auto;
|
|
}
|
|
|
|
.series-details-section {
|
|
margin-top: 2rem;
|
|
margin-bottom: 3rem;
|
|
}
|
|
|
|
.details-content-wrapper {
|
|
position: relative;
|
|
max-height: 140px;
|
|
overflow: hidden;
|
|
transition: max-height 0.5s cubic-bezier(0.4, 0, 0.2, 1);
|
|
}
|
|
.details-content-wrapper::after {
|
|
content: "";
|
|
position: absolute;
|
|
bottom: 0;
|
|
left: 0;
|
|
right: 0;
|
|
height: 80px;
|
|
background: linear-gradient(
|
|
to top,
|
|
var(--background-primary),
|
|
transparent
|
|
);
|
|
pointer-events: none;
|
|
transition: opacity 0.3s;
|
|
}
|
|
.details-content-wrapper.expanded {
|
|
max-height: 2000px;
|
|
}
|
|
.details-content-wrapper.expanded::after {
|
|
opacity: 0;
|
|
}
|
|
|
|
#series-synopsis {
|
|
font-size: 1.05rem;
|
|
color: var(--text-secondary);
|
|
margin-bottom: 1rem;
|
|
line-height: 1.8;
|
|
}
|
|
|
|
.series-genres {
|
|
display: flex;
|
|
flex-wrap: wrap;
|
|
gap: 8px;
|
|
margin-top: 1.5rem;
|
|
}
|
|
.genre-tag {
|
|
font-size: 0.85rem;
|
|
background: rgba(255, 255, 255, 0.03);
|
|
border: 1px solid var(--border-color);
|
|
padding: 6px 14px;
|
|
border-radius: 8px;
|
|
color: var(--text-secondary);
|
|
transition: all 0.2s;
|
|
}
|
|
.genre-tag:hover {
|
|
background: rgba(255, 255, 255, 0.08);
|
|
color: #fff;
|
|
}
|
|
|
|
.show-more-container {
|
|
text-align: center;
|
|
margin-top: 1rem;
|
|
}
|
|
.show-more-btn {
|
|
background: none;
|
|
border: none;
|
|
color: var(--text-muted);
|
|
cursor: pointer;
|
|
font-weight: 600;
|
|
font-size: 0.9rem;
|
|
transition: color 0.2s;
|
|
}
|
|
.show-more-btn:hover {
|
|
color: var(--brand-accent);
|
|
}
|
|
|
|
/* Trailer */
|
|
.trailer-section {
|
|
margin-top: 3rem;
|
|
border-top: 1px solid var(--border-color);
|
|
padding-top: 2rem;
|
|
margin-bottom: 3rem;
|
|
}
|
|
.trailer-player-container {
|
|
background: #000;
|
|
border-radius: var(--border-radius);
|
|
overflow: hidden;
|
|
aspect-ratio: 16/9;
|
|
max-width: 900px;
|
|
margin: 0 auto;
|
|
box-shadow: 0 10px 40px rgba(0, 0, 0, 0.5);
|
|
border: 1px solid var(--border-color);
|
|
}
|
|
|
|
/* ORANGE THEME #FF9500 */
|
|
#video .csPlayer {
|
|
--playerBg: #000;
|
|
--startBtnBg: #ff9500;
|
|
--startBtnIconColor: #fff;
|
|
--startBtnSize: 70px;
|
|
--sliderSeekTrackColor: #ff9500;
|
|
--sliderThumbColor: #ff9500;
|
|
--sliderLoadedTrackColor: rgba(255, 149, 0, 0.3);
|
|
--sliderBg: rgba(255, 255, 255, 0.2);
|
|
--playPauseIconColor: #ff9500;
|
|
--forwardIconColor: #ff9500;
|
|
--backwardIconColor: #ff9500;
|
|
--fullscreenBtnColor: #fff;
|
|
--settingsBtnColor: #fff;
|
|
--settingsBg: rgba(20, 20, 20, 0.95);
|
|
--settingsTextColor: #fff;
|
|
--settingsInputIconBg: #ff9500;
|
|
--settingsInputIconColor: #fff;
|
|
}
|
|
|
|
/* =========================================
|
|
5. EPISODES & SCROLLABLES
|
|
========================================= */
|
|
.section-header {
|
|
display: flex;
|
|
justify-content: space-between;
|
|
align-items: center;
|
|
margin-bottom: 1.5rem;
|
|
gap: 1rem;
|
|
flex-wrap: wrap;
|
|
}
|
|
.section-title {
|
|
font-size: 1.5rem;
|
|
font-weight: 800;
|
|
color: var(--text-primary);
|
|
margin-right: auto;
|
|
letter-spacing: -0.01em;
|
|
}
|
|
|
|
/* Episode List Controls */
|
|
.episode-controls {
|
|
display: flex;
|
|
align-items: center;
|
|
gap: 12px;
|
|
flex-shrink: 0;
|
|
}
|
|
|
|
.episode-controls .season-selector-container {
|
|
min-width: 0;
|
|
}
|
|
.episode-controls .season-selector {
|
|
width: 100%;
|
|
min-width: 0;
|
|
}
|
|
|
|
.layout-toggle-btn {
|
|
width: 44px;
|
|
height: 44px;
|
|
border-radius: 12px;
|
|
background: rgba(255, 255, 255, 0.03);
|
|
border: 1px solid var(--border-color);
|
|
color: var(--text-secondary);
|
|
display: flex;
|
|
align-items: center;
|
|
justify-content: center;
|
|
cursor: pointer;
|
|
transition: all 0.3s cubic-bezier(0.25, 0.46, 0.45, 0.94);
|
|
backdrop-filter: blur(5px);
|
|
}
|
|
.layout-toggle-btn:hover,
|
|
.layout-toggle-btn.active {
|
|
border-color: rgba(255, 255, 255, 0.3);
|
|
color: #fff;
|
|
background: rgba(255, 255, 255, 0.1);
|
|
transform: translateY(-2px);
|
|
}
|
|
|
|
/* Season Selector */
|
|
.season-selector-container {
|
|
position: relative;
|
|
min-width: 280px;
|
|
max-width: 390px;
|
|
}
|
|
.season-selector {
|
|
width: 100%;
|
|
padding: 12px 40px 12px 16px;
|
|
border-radius: 12px;
|
|
background: rgba(255, 255, 255, 0.03);
|
|
color: var(--text-primary);
|
|
border: 1px solid var(--border-color);
|
|
outline: none;
|
|
appearance: none;
|
|
font-family: inherit;
|
|
font-size: 0.95rem;
|
|
font-weight: 600;
|
|
cursor: pointer;
|
|
transition: all 0.3s;
|
|
backdrop-filter: blur(5px);
|
|
}
|
|
.season-selector:hover,
|
|
.season-selector:focus {
|
|
border-color: rgba(255, 255, 255, 0.3);
|
|
background: rgba(255, 255, 255, 0.08);
|
|
}
|
|
.season-selector-container::after {
|
|
content: "\f078";
|
|
font-family: "Font Awesome 6 Free";
|
|
font-weight: 900;
|
|
position: absolute;
|
|
right: 16px;
|
|
top: 50%;
|
|
transform: translateY(-50%);
|
|
color: var(--text-secondary);
|
|
pointer-events: none;
|
|
font-size: 0.8rem;
|
|
}
|
|
|
|
/* Episodes - Standard List View */
|
|
.episode-list {
|
|
display: flex;
|
|
flex-direction: column;
|
|
gap: 12px;
|
|
margin-bottom: 3rem;
|
|
}
|
|
|
|
.episode-item {
|
|
position: relative;
|
|
display: flex;
|
|
background: var(--background-secondary);
|
|
border: 1px solid var(--border-color);
|
|
border-radius: 12px;
|
|
overflow: hidden;
|
|
cursor: pointer;
|
|
transition: all 0.3s cubic-bezier(0.25, 0.46, 0.45, 0.94);
|
|
min-height: 90px;
|
|
padding-right: 10px;
|
|
}
|
|
.episode-item:hover {
|
|
background: var(--background-tertiary);
|
|
border-color: rgba(255, 255, 255, 0.2);
|
|
transform: translateY(-2px);
|
|
box-shadow: 0 8px 20px rgba(0, 0, 0, 0.4);
|
|
}
|
|
|
|
/* Episode Locate/Target Animation */
|
|
@keyframes target-pulse {
|
|
0% { box-shadow: 0 0 0 0 rgba(255, 149, 0, 0.5); border-color: var(--brand-accent); }
|
|
70% { box-shadow: 0 0 0 15px rgba(255, 149, 0, 0); border-color: rgba(255,255,255,0.3); }
|
|
100% { box-shadow: 0 0 0 0 rgba(255, 149, 0, 0); border-color: var(--border-color); }
|
|
}
|
|
.episode-item.target-highlight {
|
|
animation: target-pulse 1.5s ease-out 2;
|
|
border-color: var(--brand-accent);
|
|
background: rgba(255, 149, 0, 0.05);
|
|
}
|
|
|
|
/* Progress Bar Styles */
|
|
.episode-progress-wrapper {
|
|
margin-top: 6px;
|
|
width: 100%;
|
|
max-width: 220px;
|
|
}
|
|
.ep-progress-track {
|
|
width: 100%;
|
|
height: 4px;
|
|
background: rgba(255, 255, 255, 0.1);
|
|
border-radius: 2px;
|
|
overflow: hidden;
|
|
margin-bottom: 4px;
|
|
}
|
|
.ep-progress-fill {
|
|
height: 100%;
|
|
background: var(--brand-accent);
|
|
border-radius: 2px;
|
|
}
|
|
.ep-progress-text {
|
|
font-size: 0.75rem;
|
|
color: var(--brand-accent);
|
|
font-weight: 600;
|
|
letter-spacing: 0.5px;
|
|
}
|
|
|
|
.episode-thumbnail {
|
|
width: 160px;
|
|
background: #000;
|
|
position: relative;
|
|
overflow: hidden;
|
|
flex-shrink: 0;
|
|
transition: width 0.3s;
|
|
}
|
|
.episode-thumbnail img {
|
|
width: 100%;
|
|
height: 100%;
|
|
object-fit: cover;
|
|
opacity: 0.8;
|
|
transition: transform 0.4s cubic-bezier(0.25, 0.46, 0.45, 0.94);
|
|
}
|
|
.episode-item:hover .episode-thumbnail img {
|
|
transform: scale(1.08);
|
|
opacity: 1;
|
|
}
|
|
|
|
/* Text-only list fallback (Thumbnails completely hidden) */
|
|
.episode-list.thumbnails-hidden .episode-thumbnail {
|
|
display: none;
|
|
}
|
|
.episode-list.thumbnails-hidden .episode-item {
|
|
padding: 14px 20px;
|
|
align-items: center;
|
|
}
|
|
.episode-list.thumbnails-hidden .episode-info {
|
|
padding: 0;
|
|
}
|
|
|
|
.episode-info {
|
|
flex: 1;
|
|
display: flex;
|
|
flex-direction: column;
|
|
justify-content: center;
|
|
padding: 0 20px;
|
|
min-width: 0;
|
|
}
|
|
.episode-title {
|
|
font-weight: 700;
|
|
font-size: 1rem;
|
|
color: #fff;
|
|
white-space: nowrap;
|
|
overflow: hidden;
|
|
text-overflow: ellipsis;
|
|
margin-bottom: 4px;
|
|
}
|
|
|
|
/* Unwatched Badge */
|
|
.episode-item.unwatched .episode-title::after {
|
|
content: '';
|
|
display: inline-block;
|
|
width: 6px;
|
|
height: 6px;
|
|
background: var(--brand-accent);
|
|
border-radius: 50%;
|
|
margin-left: 8px;
|
|
vertical-align: middle;
|
|
box-shadow: 0 0 8px rgba(255, 149, 0, 0.6);
|
|
}
|
|
|
|
.episode-title-romanji {
|
|
font-size: 0.85rem;
|
|
color: var(--text-secondary);
|
|
font-weight: 500;
|
|
}
|
|
.episode-action-buttons {
|
|
display: flex;
|
|
align-items: center;
|
|
padding-right: 20px;
|
|
opacity: 0.4;
|
|
transition: all 0.3s;
|
|
}
|
|
.episode-item:hover .episode-action-buttons {
|
|
opacity: 1;
|
|
transform: scale(1.1);
|
|
color: var(--brand-accent);
|
|
}
|
|
|
|
.episode-item.watched .episode-title {
|
|
color: var(--text-muted);
|
|
}
|
|
.episode-item.watched::before {
|
|
content: "";
|
|
position: absolute;
|
|
left: 0;
|
|
top: 0;
|
|
bottom: 0;
|
|
width: 4px;
|
|
background: var(--brand-accent);
|
|
z-index: 2;
|
|
}
|
|
|
|
/* =========================================
|
|
6. EPISODES - GRID/TILE VIEW (PHASE 3)
|
|
========================================= */
|
|
.episode-list.grid-view {
|
|
display: grid;
|
|
grid-template-columns: repeat(auto-fill, minmax(260px, 1fr));
|
|
gap: 16px;
|
|
}
|
|
|
|
.episode-list.grid-view .episode-item {
|
|
flex-direction: column;
|
|
min-height: auto;
|
|
padding: 0;
|
|
border-radius: 14px;
|
|
}
|
|
.episode-list.grid-view .episode-thumbnail {
|
|
width: 100%;
|
|
aspect-ratio: 16/9;
|
|
}
|
|
.episode-list.grid-view .episode-info {
|
|
padding: 14px;
|
|
}
|
|
.episode-list.grid-view .episode-title {
|
|
white-space: normal;
|
|
display: -webkit-box;
|
|
-webkit-line-clamp: 2;
|
|
-webkit-box-orient: vertical;
|
|
}
|
|
.episode-list.grid-view .episode-action-buttons {
|
|
position: absolute;
|
|
top: 10px;
|
|
right: 10px;
|
|
padding: 0;
|
|
background: rgba(0, 0, 0, 0.6);
|
|
backdrop-filter: blur(5px);
|
|
width: 36px;
|
|
height: 36px;
|
|
border-radius: 50%;
|
|
justify-content: center;
|
|
opacity: 0;
|
|
color: #fff;
|
|
border: 1px solid rgba(255,255,255,0.1);
|
|
}
|
|
.episode-list.grid-view .episode-item:hover .episode-action-buttons {
|
|
opacity: 1;
|
|
background: var(--brand-accent);
|
|
color: #000;
|
|
border-color: var(--brand-accent);
|
|
}
|
|
.episode-list.grid-view .episode-item.watched::before {
|
|
width: 100%;
|
|
height: 4px;
|
|
bottom: auto;
|
|
left: 0;
|
|
top: 0;
|
|
}
|
|
|
|
/* Compact Grid View (Thumbnails Hidden) */
|
|
.episode-list.grid-view.thumbnails-hidden {
|
|
grid-template-columns: repeat(auto-fill, minmax(70px, 1fr));
|
|
gap: 10px;
|
|
}
|
|
.episode-list.grid-view.thumbnails-hidden .episode-item {
|
|
flex-shrink: 0; /* Prevent items from shrinking */
|
|
width: 70px; /* Set a fixed width based on minmax from grid */
|
|
justify-content: center;
|
|
align-items: center;
|
|
padding: 12px 6px;
|
|
min-height: 44px;
|
|
text-align: center;
|
|
border-radius: 10px;
|
|
}
|
|
.episode-list.grid-view.thumbnails-hidden .episode-info {
|
|
padding: 0;
|
|
}
|
|
.episode-list.grid-view.thumbnails-hidden .episode-title {
|
|
font-size: 0.9rem;
|
|
margin: 0;
|
|
white-space: nowrap;
|
|
}
|
|
.episode-list.grid-view.thumbnails-hidden .episode-title {
|
|
display: none; /* Hide the main title */
|
|
}
|
|
.episode-list.grid-view.thumbnails-hidden .episode-title-romanji {
|
|
font-size: 0.9rem; /* Match the desired font size */
|
|
margin: 0;
|
|
white-space: nowrap;
|
|
display: block; /* Make the romanji title visible */
|
|
color: var(--text-primary); /* Ensure it's visible, not muted */
|
|
font-weight: 700; /* Make it bolder */
|
|
}
|
|
.episode-list.grid-view.thumbnails-hidden .episode-progress-wrapper,
|
|
.episode-list.grid-view.thumbnails-hidden .episode-action-buttons {
|
|
display: none;
|
|
}
|
|
.episode-list.grid-view.thumbnails-hidden .episode-item.watched::before {
|
|
display: none;
|
|
}
|
|
.episode-list.grid-view.thumbnails-hidden .episode-item.watched {
|
|
opacity: 0.5;
|
|
background: transparent;
|
|
}
|
|
|
|
|
|
/* =========================================
|
|
7. THEMES SECTION
|
|
========================================= */
|
|
.theme-list {
|
|
display: flex;
|
|
flex-direction: column;
|
|
gap: 10px;
|
|
margin-bottom: 2rem;
|
|
}
|
|
.theme-item {
|
|
display: flex;
|
|
align-items: center;
|
|
justify-content: space-between;
|
|
background: var(--background-secondary);
|
|
padding: 14px 20px;
|
|
border-radius: 14px;
|
|
border: 1px solid var(--border-color);
|
|
transition: all 0.3s;
|
|
}
|
|
.theme-item:hover {
|
|
border-color: rgba(255, 255, 255, 0.2);
|
|
background: var(--background-tertiary);
|
|
transform: translateY(-2px);
|
|
box-shadow: 0 8px 20px rgba(0, 0, 0, 0.3);
|
|
}
|
|
|
|
.theme-info {
|
|
display: flex;
|
|
flex-direction: column;
|
|
gap: 4px;
|
|
flex: 1;
|
|
min-width: 0;
|
|
margin-right: 16px;
|
|
}
|
|
|
|
.theme-type {
|
|
font-size: 0.75rem;
|
|
color: var(--brand-accent);
|
|
font-weight: 800;
|
|
text-transform: uppercase;
|
|
letter-spacing: 0.5px;
|
|
}
|
|
|
|
.theme-title {
|
|
font-size: 1rem;
|
|
font-weight: 600;
|
|
color: #fff;
|
|
white-space: nowrap;
|
|
overflow: hidden;
|
|
text-overflow: ellipsis;
|
|
}
|
|
|
|
.theme-actions {
|
|
display: flex;
|
|
align-items: center;
|
|
gap: 10px;
|
|
flex-shrink: 0;
|
|
}
|
|
|
|
.theme-btn {
|
|
width: 38px;
|
|
height: 38px;
|
|
border-radius: 50%;
|
|
border: 1px solid rgba(255, 255, 255, 0.1);
|
|
background: rgba(255, 255, 255, 0.05);
|
|
color: var(--text-primary);
|
|
cursor: pointer;
|
|
display: flex;
|
|
align-items: center;
|
|
justify-content: center;
|
|
transition: all 0.3s;
|
|
font-size: 0.9rem;
|
|
backdrop-filter: blur(5px);
|
|
}
|
|
|
|
.theme-btn:hover {
|
|
background: #fff;
|
|
color: #000;
|
|
border-color: #fff;
|
|
transform: scale(1.1);
|
|
}
|
|
|
|
.theme-btn.play {
|
|
border-color: var(--brand-accent);
|
|
color: var(--brand-accent);
|
|
background: rgba(255, 149, 0, 0.1);
|
|
}
|
|
.theme-btn.play:hover {
|
|
background: var(--brand-accent);
|
|
color: #000;
|
|
border-color: var(--brand-accent);
|
|
box-shadow: 0 0 15px rgba(255, 149, 0, 0.4);
|
|
}
|
|
|
|
/* =========================================
|
|
8. MOVIE CARD
|
|
========================================= */
|
|
.movie-player-card {
|
|
background: var(--background-secondary);
|
|
border: 1px solid var(--border-color);
|
|
border-radius: 20px;
|
|
overflow: hidden;
|
|
position: relative;
|
|
aspect-ratio: 21 / 9;
|
|
max-height: 600px;
|
|
display: flex;
|
|
align-items: center;
|
|
justify-content: center;
|
|
cursor: pointer;
|
|
margin-bottom: 3rem;
|
|
box-shadow: 0 20px 50px rgba(0, 0, 0, 0.5);
|
|
transition: transform 0.4s cubic-bezier(0.25, 0.46, 0.45, 0.94);
|
|
}
|
|
.movie-player-card:hover {
|
|
transform: scale(1.02);
|
|
border-color: rgba(255, 255, 255, 0.2);
|
|
}
|
|
.movie-backdrop {
|
|
position: absolute;
|
|
inset: 0;
|
|
width: 100%;
|
|
height: 100%;
|
|
object-fit: cover;
|
|
opacity: 0.6;
|
|
transition: 0.5s;
|
|
}
|
|
.movie-player-card:hover .movie-backdrop {
|
|
opacity: 0.4;
|
|
transform: scale(1.05);
|
|
}
|
|
.movie-player-card::after {
|
|
content: "";
|
|
position: absolute;
|
|
inset: 0;
|
|
background: linear-gradient(
|
|
to top,
|
|
rgba(0, 0, 0, 0.9) 0%,
|
|
rgba(0, 0, 0, 0.2) 60%,
|
|
rgba(0, 0, 0, 0.4) 100%
|
|
);
|
|
}
|
|
.movie-play-overlay {
|
|
position: relative;
|
|
z-index: 2;
|
|
display: flex;
|
|
flex-direction: column;
|
|
align-items: center;
|
|
gap: 20px;
|
|
text-align: center;
|
|
}
|
|
.movie-play-btn {
|
|
width: 90px;
|
|
height: 90px;
|
|
border-radius: 50%;
|
|
background: rgba(255, 149, 0, 0.9);
|
|
color: #fff;
|
|
display: flex;
|
|
align-items: center;
|
|
justify-content: center;
|
|
font-size: 2.5rem;
|
|
padding-left: 8px;
|
|
backdrop-filter: blur(10px);
|
|
box-shadow: 0 0 0 0 rgba(255, 149, 0, 0.7);
|
|
animation: pulse-orange 2s infinite;
|
|
transition: transform 0.3s;
|
|
}
|
|
@keyframes pulse-orange {
|
|
0% { transform: scale(0.95); box-shadow: 0 0 0 0 rgba(255, 149, 0, 0.7); }
|
|
70% { transform: scale(1); box-shadow: 0 0 0 20px rgba(255, 149, 0, 0); }
|
|
100% { transform: scale(0.95); box-shadow: 0 0 0 0 rgba(255, 149, 0, 0); }
|
|
}
|
|
.movie-player-card:hover .movie-play-btn {
|
|
background: #fff;
|
|
color: var(--brand-accent);
|
|
animation: none;
|
|
transform: scale(1.1);
|
|
}
|
|
.movie-label {
|
|
font-size: 1.8rem;
|
|
font-weight: 800;
|
|
text-transform: uppercase;
|
|
letter-spacing: 2px;
|
|
text-shadow: 0 2px 10px rgba(0, 0, 0, 0.8);
|
|
pointer-events: none;
|
|
}
|
|
|
|
/* =========================================
|
|
9. HORIZONTAL SCROLL SECTIONS
|
|
========================================= */
|
|
.horizontal-scroll-section {
|
|
margin-bottom: 4rem;
|
|
padding-top: 1.5rem;
|
|
border-top: 1px solid var(--border-color);
|
|
}
|
|
.horizontal-list {
|
|
display: flex;
|
|
gap: 1.2rem;
|
|
overflow-x: auto;
|
|
padding-bottom: 1.5rem;
|
|
scroll-behavior: smooth;
|
|
-webkit-overflow-scrolling: touch;
|
|
padding-right: 5%;
|
|
}
|
|
|
|
/* Characters */
|
|
.character-card {
|
|
flex: 0 0 160px;
|
|
background: var(--background-secondary);
|
|
border: 1px solid var(--border-color);
|
|
border-radius: 14px;
|
|
overflow: hidden;
|
|
box-shadow: 0 8px 20px rgba(0, 0, 0, 0.3);
|
|
transition: transform 0.3s;
|
|
}
|
|
.character-card:hover {
|
|
transform: translateY(-4px);
|
|
border-color: rgba(255,255,255,0.2);
|
|
}
|
|
.char-card-imgs { display: flex; height: 150px; }
|
|
.char-card-imgs img { width: 50%; height: 100%; object-fit: cover; }
|
|
.char-info { padding: 12px; text-align: center; font-size: 0.85rem; border-top: 1px solid var(--border-color); }
|
|
.char-name { font-weight: 700; display: block; line-height: 1.2; margin-bottom: 4px; color: #fff; }
|
|
.va-name { color: var(--text-secondary); font-size: 0.75rem; font-weight: 500; }
|
|
|
|
.char-switcher { display: flex; gap: 8px; }
|
|
.char-lang-btn {
|
|
background: rgba(255, 255, 255, 0.05);
|
|
color: var(--text-secondary);
|
|
border: 1px solid rgba(255, 255, 255, 0.1);
|
|
padding: 8px 16px;
|
|
font-size: 0.85rem;
|
|
border-radius: 8px;
|
|
cursor: pointer;
|
|
font-weight: 700;
|
|
transition: all 0.3s;
|
|
backdrop-filter: blur(5px);
|
|
}
|
|
.char-lang-btn:hover { background: rgba(255, 255, 255, 0.15); color: #fff; }
|
|
.char-lang-btn.active { background: #fff; color: #000; border-color: #fff; }
|
|
|
|
/* TILE CARD */
|
|
.tile-card {
|
|
flex: 0 0 170px;
|
|
display: flex;
|
|
flex-direction: column;
|
|
gap: 12px;
|
|
text-decoration: none;
|
|
transition: transform 0.3s;
|
|
}
|
|
.tile-card:hover { transform: translateY(-6px); }
|
|
.tile-img-container {
|
|
width: 100%;
|
|
aspect-ratio: 2 / 3;
|
|
border-radius: 14px;
|
|
overflow: hidden;
|
|
background: var(--background-tertiary);
|
|
position: relative;
|
|
box-shadow: 0 8px 20px rgba(0, 0, 0, 0.4);
|
|
border: 1px solid rgba(255, 255, 255, 0.05);
|
|
}
|
|
.tile-img { width: 100%; height: 100%; object-fit: cover; transition: transform 0.4s; }
|
|
.tile-card:hover .tile-img { transform: scale(1.08); }
|
|
.tile-tag {
|
|
position: absolute;
|
|
top: 10px; left: 10px;
|
|
background: var(--brand-accent);
|
|
color: #000;
|
|
font-size: 0.7rem;
|
|
padding: 4px 10px;
|
|
border-radius: 6px;
|
|
font-weight: 800;
|
|
text-transform: uppercase;
|
|
box-shadow: 0 4px 10px rgba(0, 0, 0, 0.5);
|
|
}
|
|
.tile-title {
|
|
font-size: 0.95rem;
|
|
font-weight: 600;
|
|
color: #ddd;
|
|
line-height: 1.4;
|
|
display: -webkit-box;
|
|
-webkit-line-clamp: 2;
|
|
-webkit-box-orient: vertical;
|
|
overflow: hidden;
|
|
transition: color 0.3s;
|
|
}
|
|
.tile-card:hover .tile-title { color: #fff; }
|
|
|
|
/* Mobile Tabs */
|
|
.tabs-container { display: none; }
|
|
|
|
/* =========================================
|
|
10. MEDIA QUERIES
|
|
========================================= */
|
|
@media (min-width: 1024px) {
|
|
.series-hero-section { height: 70vh; padding-top: 120px; }
|
|
.hero-poster-container { display: block; margin-bottom: 100px; z-index: 5; }
|
|
.series-details-section { margin-top: 5rem; }
|
|
}
|
|
|
|
@media (max-width: 1023px) {
|
|
.series-hero-section { padding-top: 120px; height: auto; min-height: auto; padding-bottom: 2rem; }
|
|
.hero-poster-container {
|
|
display: block; width: 180px; transform: none;
|
|
box-shadow: 0 10px 30px rgba(0, 0, 0, 0.6);
|
|
border: 1px solid rgba(255, 255, 255, 0.1);
|
|
background: transparent;
|
|
margin: 0 auto 1.5rem auto;
|
|
}
|
|
.series-hero-content { flex-direction: column; align-items: center; gap: 1rem; text-align: center; }
|
|
.hero-text-content { width: 100%; align-items: center; }
|
|
.series-title-text { font-size: 2.2rem; margin-top: 0; text-align: center; line-height: 1.1; }
|
|
.hero-poster-img { border-radius: 14px; }
|
|
.hero-metadata-capsules { justify-content: center; gap: 8px; }
|
|
.meta-capsule { font-size: 0.8rem; padding: 6px 12px; }
|
|
|
|
.hero-actions { width: 100%; display: flex; flex-wrap: wrap; justify-content: center; gap: 12px; margin-top: 10px; }
|
|
.hero-watch-btn { width: 100%; order: 1; margin-bottom: 0; height: 56px; font-size: 1.1rem; }
|
|
.hero-locate-btn { flex: none; order: 2; height: 50px; width: 50px; }
|
|
.hero-action-btn { flex: 1; order: 3; height: 50px; font-size: 0.9rem; }
|
|
|
|
.episode-controls {
|
|
width: 100%;
|
|
flex-wrap: wrap;
|
|
justify-content: flex-end;
|
|
gap: 10px;
|
|
}
|
|
.episode-controls .season-selector-container {
|
|
flex: 1 1 170px;
|
|
max-width: 100%;
|
|
}
|
|
|
|
.tabs-container {
|
|
display: flex; gap: 1.5rem; border-bottom: 1px solid var(--border-color);
|
|
margin-bottom: 2rem; overflow-x: auto; padding-left: 5%;
|
|
}
|
|
.tab-btn {
|
|
padding: 1rem 0.5rem; font-size: 1rem; font-weight: 600;
|
|
color: var(--text-secondary); background: none; border: none;
|
|
white-space: nowrap; position: relative; transition: color 0.3s;
|
|
}
|
|
.tab-btn.active { color: var(--text-primary); }
|
|
.tab-btn.active::after {
|
|
content: ""; position: absolute; bottom: 0; left: 0; right: 0;
|
|
height: 3px; background: var(--brand-accent); border-radius: 3px 3px 0 0;
|
|
}
|
|
|
|
.horizontal-scroll-section.mobile-tab-content { display: none; margin-bottom: 2rem; }
|
|
.horizontal-scroll-section.mobile-tab-content.active { display: block; }
|
|
.episode-list-section.mobile-tab-content { display: none; }
|
|
.episode-list-section.mobile-tab-content.active { display: block; }
|
|
|
|
.movie-player-card { aspect-ratio: 16/9; }
|
|
.movie-play-btn { width: 70px; height: 70px; font-size: 2rem; }
|
|
.episode-popup-content { width: 100%; height: 100%; max-width: none; aspect-ratio: unset; border-radius: 0; border: none; box-shadow: none; }
|
|
.popup-close { top: 20px; right: 20px; }
|
|
}
|
|
|
|
/* =========================================
|
|
11. POPUP / MODALS
|
|
========================================= */
|
|
.episode-popup-overlay {
|
|
position: fixed; inset: 0; background: rgba(0,0,0,0.8); z-index: 9999;
|
|
display: none; align-items: center; justify-content: center;
|
|
backdrop-filter: blur(15px); margin: 0; border-radius: 0;
|
|
}
|
|
.episode-popup-overlay.active { display: flex; }
|
|
.episode-popup-content {
|
|
width: 95%; max-width: 1400px; aspect-ratio: 16/9;
|
|
background: #000; position: relative; border-radius: 20px;
|
|
overflow: hidden; box-shadow: 0 30px 80px rgba(0, 0, 0, 0.8);
|
|
border: 1px solid rgba(255,255,255,0.1);
|
|
}
|
|
.popup-close {
|
|
position: absolute; top: 20px; right: 20px; z-index: 20; color: #fff;
|
|
width: 44px; height: 44px; border-radius: 50%;
|
|
background: rgba(0, 0, 0, 0.6); border: 1px solid rgba(255, 255, 255, 0.2);
|
|
cursor: pointer; display: flex; align-items: center; justify-content: center;
|
|
font-size: 1.2rem; backdrop-filter: blur(8px); transition: all 0.3s;
|
|
}
|
|
.popup-close:hover { background: #fff; color: #000; transform: rotate(90deg) scale(1.1); }
|
|
iframe { width: 100%; height: 100%; border: none; }
|
|
|
|
/* =========================================
|
|
12. RX GATE MODAL (PHASE 1 MODERNIZATION)
|
|
========================================= */
|
|
.rx-gate-overlay {
|
|
position: fixed;
|
|
inset: 0;
|
|
z-index: 99999;
|
|
background: rgba(0, 0, 0, 0.4);
|
|
/* Heavy blur and dim */
|
|
backdrop-filter: blur(25px) brightness(0.3);
|
|
-webkit-backdrop-filter: blur(25px) brightness(0.3);
|
|
display: flex;
|
|
align-items: center;
|
|
justify-content: center;
|
|
padding: 2rem;
|
|
opacity: 0;
|
|
transition: opacity 0.4s ease;
|
|
pointer-events: none;
|
|
}
|
|
.rx-gate-overlay.visible {
|
|
opacity: 1;
|
|
pointer-events: all;
|
|
}
|
|
.rx-gate-card {
|
|
background: rgba(20, 20, 20, 0.6);
|
|
border: 1px solid rgba(255, 255, 255, 0.08);
|
|
border-radius: 24px;
|
|
padding: 3.5rem 2.5rem;
|
|
max-width: 480px;
|
|
width: 100%;
|
|
text-align: center;
|
|
/* Subtle crimson glow */
|
|
box-shadow: 0 20px 60px rgba(192, 57, 43, 0.15), 0 0 100px rgba(0,0,0,0.8);
|
|
backdrop-filter: blur(16px);
|
|
-webkit-backdrop-filter: blur(16px);
|
|
transform: translateY(40px) scale(0.9);
|
|
transition: transform 0.5s cubic-bezier(0.2, 0.8, 0.2, 1), opacity 0.4s ease;
|
|
opacity: 0;
|
|
}
|
|
.rx-gate-overlay.visible .rx-gate-card {
|
|
transform: translateY(0) scale(1);
|
|
opacity: 1;
|
|
}
|
|
.rx-gate-icon {
|
|
font-size: 3.5rem;
|
|
margin-bottom: 1.2rem;
|
|
display: block;
|
|
line-height: 1;
|
|
}
|
|
.rx-gate-badge {
|
|
display: inline-block;
|
|
background: linear-gradient(135deg, rgba(229, 57, 53, 0.2), rgba(183, 28, 28, 0.2));
|
|
border: 1px solid rgba(229, 57, 53, 0.3);
|
|
color: #ff5252;
|
|
font-size: 0.75rem;
|
|
font-weight: 800;
|
|
letter-spacing: 1.5px;
|
|
text-transform: uppercase;
|
|
padding: 6px 16px;
|
|
border-radius: 50px;
|
|
margin-bottom: 1.6rem;
|
|
}
|
|
.rx-gate-title {
|
|
font-size: 1.6rem;
|
|
font-weight: 800;
|
|
color: var(--text-primary);
|
|
margin-bottom: 0.8rem;
|
|
letter-spacing: -0.02em;
|
|
line-height: 1.2;
|
|
}
|
|
.rx-gate-subtitle {
|
|
font-size: 0.95rem;
|
|
color: var(--text-secondary);
|
|
line-height: 1.6;
|
|
margin-bottom: 2.5rem;
|
|
}
|
|
.rx-gate-actions {
|
|
display: flex;
|
|
gap: 14px;
|
|
justify-content: center;
|
|
flex-wrap: wrap;
|
|
}
|
|
.rx-gate-btn-yes {
|
|
flex: 1;
|
|
min-width: 140px;
|
|
padding: 14px 24px;
|
|
border-radius: 14px;
|
|
border: 1px solid rgba(192, 57, 43, 0.3);
|
|
background: rgba(192, 57, 43, 0.1);
|
|
color: #e57373;
|
|
font-size: 0.95rem;
|
|
font-weight: 700;
|
|
cursor: pointer;
|
|
transition: all 0.3s cubic-bezier(0.25, 0.46, 0.45, 0.94);
|
|
font-family: inherit;
|
|
}
|
|
.rx-gate-btn-yes:hover {
|
|
background: rgba(192, 57, 43, 0.25);
|
|
border-color: #e53935;
|
|
color: #fff;
|
|
transform: translateY(-2px);
|
|
}
|
|
.rx-gate-btn-no {
|
|
flex: 1;
|
|
min-width: 140px;
|
|
padding: 14px 24px;
|
|
border-radius: 14px;
|
|
border: none;
|
|
background: var(--brand-accent);
|
|
color: #000;
|
|
font-size: 0.95rem;
|
|
font-weight: 800;
|
|
cursor: pointer;
|
|
transition: all 0.3s cubic-bezier(0.25, 0.46, 0.45, 0.94);
|
|
font-family: inherit;
|
|
}
|
|
.rx-gate-btn-no:hover {
|
|
background: var(--brand-accent-hover);
|
|
transform: translateY(-2px);
|
|
box-shadow: 0 8px 25px rgba(255,149,0,0.35);
|
|
}
|
|
.rx-gate-disclaimer {
|
|
margin-top: 2rem;
|
|
font-size: 0.75rem;
|
|
color: var(--text-muted);
|
|
line-height: 1.5;
|
|
}
|
|
</style>
|
|
</head>
|
|
<body>
|
|
<div class="app-container">
|
|
<!-- Back Button -->
|
|
<button id="back-button" class="back-btn" style="display: none">
|
|
<i class="fas fa-arrow-left"></i>
|
|
</button>
|
|
|
|
<!-- Hero Section -->
|
|
<header class="series-hero-section" id="hero-section">
|
|
<div class="series-hero-content">
|
|
<!-- Poster with Skeleton -->
|
|
<div class="hero-poster-container" id="poster-container">
|
|
<div class="skeleton skeleton-poster" id="poster-skeleton"></div>
|
|
<img id="hero-poster-img" class="hero-poster-img" src="" alt="Poster" style="display: none;" />
|
|
</div>
|
|
|
|
<div class="hero-text-content">
|
|
<!-- Title Skeleton -->
|
|
<div id="title-skeleton" class="skeleton skeleton-title"></div>
|
|
<h1 class="series-title-text" id="series-title" style="display: none;"></h1>
|
|
|
|
<!-- Meta Skeletons -->
|
|
<div class="hero-metadata-capsules" id="meta-container">
|
|
<div class="skeleton skeleton-text" style="width: 300px; height: 35px; border-radius: 50px;"></div>
|
|
</div>
|
|
|
|
<!-- Hero Actions -->
|
|
<div class="hero-actions">
|
|
<!-- Main Play -->
|
|
<button id="hero-watch-btn" class="hero-watch-btn" style="display: none">
|
|
<i class="fas fa-play"></i> <span>Start Watching</span>
|
|
</button>
|
|
|
|
<!-- Locate Episode (Phase 2) -->
|
|
<button id="hero-locate-btn" class="hero-locate-btn" title="Jump to Current Episode" style="display: none;">
|
|
<i class="fas fa-crosshairs"></i>
|
|
</button>
|
|
|
|
<button class="hero-action-btn"><i class="fas fa-bookmark"></i> <span>Add to List</span></button>
|
|
<button class="hero-action-btn"><i class="fas fa-download"></i> <span>Export</span></button>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
<div class="hero-overlay"></div>
|
|
</header>
|
|
|
|
<main class="main-content">
|
|
<div class="desktop-center-wrapper">
|
|
|
|
<!-- Synopsis Section -->
|
|
<section class="series-details-section">
|
|
<div id="details-content-wrapper" class="details-content-wrapper">
|
|
<div id="synopsis-skeleton">
|
|
<div class="skeleton skeleton-text"></div>
|
|
<div class="skeleton skeleton-text"></div>
|
|
<div class="skeleton skeleton-text medium"></div>
|
|
</div>
|
|
<p id="series-synopsis" style="display: none;"></p>
|
|
<div class="series-genres" id="genres-container"></div>
|
|
</div>
|
|
<div class="show-more-container">
|
|
<button id="show-more-btn" class="show-more-btn">
|
|
Show More <i class="fas fa-chevron-down"></i>
|
|
</button>
|
|
</div>
|
|
|
|
<!-- Trailer -->
|
|
<div id="trailer-section" class="trailer-section" style="display: none;">
|
|
<h3 class="section-title" style="margin-bottom: 1.5rem; font-size: 1.2rem; opacity: 0.8;">Official Trailer</h3>
|
|
<div id="trailer-player" class="trailer-player-container">
|
|
<div id="video"></div>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- Tabs (Mobile Only) -->
|
|
<div class="tabs-container">
|
|
<button class="tab-btn active" data-tab="episodes">Episodes</button>
|
|
<button class="tab-btn" data-tab="themes">Music</button>
|
|
<button class="tab-btn" data-tab="related">Related</button>
|
|
<button class="tab-btn" data-tab="recommendations">Recs</button>
|
|
</div>
|
|
|
|
<div class="content-flow">
|
|
<!-- 1. Episodes List -->
|
|
<section id="tab-panel-episodes" class="episode-list-section mobile-tab-content active">
|
|
<div class="section-header">
|
|
<h2 class="section-title">Episodes</h2>
|
|
<div class="episode-controls">
|
|
<!-- Season Dropdown -->
|
|
<div class="season-selector-container" id="season-selector-wrapper" style="display: none">
|
|
<select id="season-selector" class="season-selector"></select>
|
|
</div>
|
|
|
|
<!-- Layout Toggle Buttons (Phase 3) -->
|
|
<button id="grid-toggle-btn" class="layout-toggle-btn" title="Toggle Grid/List View">
|
|
<i class="fas fa-th-large"></i>
|
|
</button>
|
|
<button id="thumbnail-toggle-btn" class="layout-toggle-btn" title="Show/Hide Thumbnails">
|
|
<i class="fas fa-image"></i>
|
|
</button>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- Episode List Container (Grid-view or List-view handled by JS) -->
|
|
<div class="episode-list" id="episode-list-container">
|
|
<!-- Skeleton placeholders for episodes -->
|
|
<div class="skeleton skeleton-ep-card"></div>
|
|
<div class="skeleton skeleton-ep-card"></div>
|
|
<div class="skeleton skeleton-ep-card"></div>
|
|
<div class="skeleton skeleton-ep-card"></div>
|
|
</div>
|
|
|
|
<!-- Movie Mode Display -->
|
|
<div id="movie-player-card" class="movie-player-card" style="display: none">
|
|
<img id="movie-card-backdrop" class="movie-backdrop" src="" alt="Backdrop" />
|
|
<div class="movie-play-overlay">
|
|
<div class="movie-play-btn"><i class="fas fa-play"></i></div>
|
|
<span class="movie-label">Play Movie</span>
|
|
</div>
|
|
</div>
|
|
|
|
<div id="episodes-pagination" style="display: flex; justify-content: center; margin-top: 2rem"></div>
|
|
</section>
|
|
|
|
<!-- 2. Characters -->
|
|
<section class="characters-section horizontal-scroll-section" id="characters-section" style="display: none">
|
|
<div class="section-header">
|
|
<h3 class="section-title">Characters</h3>
|
|
<div class="char-switcher">
|
|
<button class="char-lang-btn active" data-lang="JAPANESE">JP</button>
|
|
<button class="char-lang-btn" data-lang="ENGLISH">EN</button>
|
|
</div>
|
|
</div>
|
|
<div class="characters-list horizontal-list" id="characters-list"></div>
|
|
</section>
|
|
|
|
<!-- 3. Themes / Music -->
|
|
<section id="tab-panel-themes" class="horizontal-scroll-section mobile-tab-content">
|
|
<h3 class="section-title" style="margin-bottom: 1.5rem">Music Themes</h3>
|
|
<div id="themes-list-container" class="theme-list">
|
|
<div class="skeleton skeleton-ep-card" style="height: 60px;"></div>
|
|
<div class="skeleton skeleton-ep-card" style="height: 60px;"></div>
|
|
</div>
|
|
</section>
|
|
|
|
<!-- 4. Related Series -->
|
|
<section id="tab-panel-related" class="horizontal-scroll-section mobile-tab-content">
|
|
<h3 class="section-title" style="margin-bottom: 1.5rem">Related Content</h3>
|
|
<div id="related-container" class="horizontal-list"></div>
|
|
</section>
|
|
|
|
<!-- 5. Recommendations -->
|
|
<section id="tab-panel-recommendations" class="horizontal-scroll-section mobile-tab-content">
|
|
<h3 class="section-title" style="margin-bottom: 1.5rem">You Might Also Like</h3>
|
|
<div id="recommendations-grid" class="horizontal-list"></div>
|
|
</section>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</main>
|
|
</div>
|
|
|
|
<!-- Video Popup -->
|
|
<div id="episode-popup-overlay" class="episode-popup-overlay">
|
|
<div class="episode-popup-content">
|
|
<button class="popup-close" title="Close Player"><i class="fas fa-times"></i></button>
|
|
<iframe id="episode-popup-iframe" src="" allowfullscreen></iframe>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- RX Gate Modal (Phase 1 Modernized) -->
|
|
<div id="rx-gate-overlay" class="rx-gate-overlay">
|
|
<div class="rx-gate-card">
|
|
<div class="rx-gate-badge">Restricted Content</div>
|
|
<h2 class="rx-gate-title">Mature Audience Only</h2>
|
|
<p class="rx-gate-subtitle">This series contains adult themes. By continuing, you confirm you are of legal age and wish to proceed.</p>
|
|
<div class="rx-gate-actions">
|
|
<button class="rx-gate-btn-no" id="rx-gate-no">Take Me Back</button>
|
|
<button class="rx-gate-btn-yes" id="rx-gate-yes">Yes, I'm Sure</button>
|
|
</div>
|
|
<p class="rx-gate-disclaimer">I know, I really thought you would do better...</p>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- JS Scripts are placed here as usual -->
|
|
<script src="https://www.youtube.com/iframe_api"></script>
|
|
|
|
<script>
|
|
// --- CONFIG & STATE ---
|
|
const urlParams = new URLSearchParams(window.location.search);
|
|
const currentMalId = urlParams.get("id");
|
|
const extensionIp = localStorage.getItem("extension_server_ip") || "localhost";
|
|
const serverUrl = ``; // Assumes same-origin or configured proxy
|
|
const THEMES_API = serverUrl + "/api/themes";
|
|
|
|
let animeDetails = {};
|
|
let episodesData = [];
|
|
let characterData = [];
|
|
let isMovie = false;
|
|
let isRxRated = false;
|
|
let rxGateAccepted = false;
|
|
|
|
// Layout State (Phase 3)
|
|
let currentLayout = localStorage.getItem("animex_layout") || "list-view"; // list-view or grid-view
|
|
let hideThumbnails = localStorage.getItem("hideThumbnails") === "true";
|
|
let currentEpRange = [1, 100];
|
|
|
|
// Watch History
|
|
let localWatchHistory = JSON.parse(localStorage.getItem("animex_watch_history") || "{}");
|
|
|
|
// --- INITIALIZATION ---
|
|
document.addEventListener("DOMContentLoaded", () => {
|
|
if (!currentMalId) return;
|
|
|
|
// Back button visibility
|
|
const isAnime = urlParams.get("anime") === "true";
|
|
|
|
initLayoutButtons();
|
|
initTabSystem();
|
|
initRxGate();
|
|
loadData();
|
|
|
|
// Show More Synopsis
|
|
document.getElementById("show-more-btn").onclick = () => {
|
|
document.getElementById("details-content-wrapper").classList.toggle("expanded");
|
|
};
|
|
|
|
// Close Popup
|
|
document.querySelector(".popup-close").onclick = () => {
|
|
closePlayerPopup();
|
|
};
|
|
});
|
|
|
|
// --- CORE DATA LOADING ---
|
|
async function loadData() {
|
|
toggleSkeletons(true);
|
|
try {
|
|
// 1. Fetch Anime Details (Jikan)
|
|
const detRes = await fetch(`/proxy?url=https://api.jikan.moe/v4/anime/${currentMalId}/full`);
|
|
const resJson = await detRes.json();
|
|
animeDetails = resJson.data;
|
|
|
|
if (!animeDetails) throw new Error("Anime not found");
|
|
|
|
renderDetails(animeDetails);
|
|
|
|
// 2. Load parallel data
|
|
await Promise.all([
|
|
fetchAndRenderEpisodes(),
|
|
fetchSeasons(),
|
|
fetchCharacters(),
|
|
fetchThemes(),
|
|
fetchRecs(),
|
|
fetchRelated()
|
|
]);
|
|
|
|
toggleSkeletons(false);
|
|
} catch (e) {
|
|
console.error("Data Load Error:", e);
|
|
document.getElementById("series-synopsis").textContent = "Error loading content. Please refresh.";
|
|
document.getElementById("synopsis-skeleton").style.display = "none";
|
|
document.getElementById("series-synopsis").style.display = "block";
|
|
}
|
|
}
|
|
|
|
function toggleSkeletons(show) {
|
|
const skeletons = document.querySelectorAll(".skeleton");
|
|
const content = [
|
|
"hero-poster-img", "series-title", "series-synopsis",
|
|
"hero-watch-btn", "hero-locate-btn"
|
|
];
|
|
|
|
skeletons.forEach(s => s.style.display = show ? "block" : "none");
|
|
content.forEach(id => {
|
|
const el = document.getElementById(id);
|
|
if (el) el.style.display = show ? "none" : (id.includes('btn') ? "inline-flex" : "block");
|
|
});
|
|
|
|
if (!show) {
|
|
document.getElementById("poster-skeleton").style.display = "none";
|
|
document.getElementById("synopsis-skeleton").style.display = "none";
|
|
}
|
|
}
|
|
|
|
// --- UI RENDERING ---
|
|
function renderDetails(anime) {
|
|
isMovie = anime.type === "Movie";
|
|
const heroSection = document.getElementById("hero-section");
|
|
|
|
// Background Image logic
|
|
heroSection.style.backgroundImage = `url('${anime.images.jpg.large_image_url}')`;
|
|
fetch(`/anime/${currentMalId}/banner`)
|
|
.then(r => { if (!r.ok) throw new Error("no banner"); return r.blob(); })
|
|
.then(blob => {
|
|
heroSection.style.backgroundImage = `url('${URL.createObjectURL(blob)}')`;
|
|
})
|
|
.catch(() => {
|
|
// Fallback: for movies, try the movie thumbnail endpoint as hero background
|
|
if (isMovie) {
|
|
fetch(`${serverUrl}/anime/${currentMalId}/movie/thumbnail`)
|
|
.then(r => r.json())
|
|
.then(d => {
|
|
const fallback = d.cover_url || d.thumbnail_url;
|
|
if (fallback) heroSection.style.backgroundImage = `url('${fallback}')`;
|
|
})
|
|
.catch(() => {});
|
|
}
|
|
});
|
|
|
|
document.getElementById("hero-poster-img").src = anime.images.jpg.image_url;
|
|
document.getElementById("series-title").textContent = anime.title_english || anime.title;
|
|
|
|
// Meta Capsules
|
|
const metaContainer = document.getElementById("meta-container");
|
|
metaContainer.innerHTML = "";
|
|
|
|
if (anime.rating) {
|
|
const rating = anime.rating.split(" ")[0];
|
|
const rClass = rating.includes("R") ? "rating-r" : (rating.includes("PG") ? "rating-pg" : "rating-g");
|
|
metaContainer.innerHTML += `<span class="rating-pill ${rClass}">${rating}</span>`;
|
|
if (anime.rating.toLowerCase().includes("rx")) {
|
|
isRxRated = true;
|
|
if (!rxGateAccepted) showRxGate();
|
|
}
|
|
}
|
|
|
|
metaContainer.innerHTML += `
|
|
<span class="meta-capsule"><i class="fas fa-calendar"></i> ${anime.year || 'N/A'}</span>
|
|
<span class="meta-capsule"><i class="fas fa-tv"></i> ${anime.type}</span>
|
|
<span class="meta-capsule">${anime.status}</span>
|
|
${anime.studios.length ? `<span class="studio-text">${anime.studios[0].name}</span>` : ''}
|
|
`;
|
|
|
|
document.getElementById("series-synopsis").textContent = anime.synopsis;
|
|
|
|
// Genres
|
|
const genreWrap = document.getElementById("genres-container");
|
|
genreWrap.innerHTML = "";
|
|
anime.genres.forEach(g => {
|
|
const span = document.createElement("span");
|
|
span.className = "genre-tag";
|
|
span.textContent = g.name;
|
|
genreWrap.appendChild(span);
|
|
});
|
|
|
|
// Trailer
|
|
if (anime.trailer?.youtube_id) {
|
|
document.getElementById("trailer-section").style.display = "block";
|
|
initYoutubePlayer(anime.trailer.youtube_id);
|
|
}
|
|
}
|
|
|
|
// --- EPISODE LOGIC (Phase 3 & 4) ---
|
|
async function fetchAndRenderEpisodes() {
|
|
let episodes = [];
|
|
try {
|
|
const mapRes = await fetch(`${serverUrl}/map/mal/${currentMalId}`);
|
|
if (mapRes.ok) {
|
|
const mapData = await mapRes.json();
|
|
if (mapData.kitsu_id) {
|
|
// Optimized paging for kitsu (Handles 1000+ eps)
|
|
let url = `/proxy?url=https://kitsu.io/api/edge/anime/${mapData.kitsu_id}/episodes?page[limit]=20`;
|
|
let count = 0;
|
|
while (url && count < 60) { // Safety cap for non-cached sessions
|
|
const r = await fetch(url);
|
|
const d = await r.json();
|
|
episodes = [...episodes, ...d.data];
|
|
url = d.links?.next;
|
|
count++;
|
|
}
|
|
}
|
|
}
|
|
} catch (e) {}
|
|
|
|
// Fallback if Kitsu/Map fails
|
|
if (episodes.length === 0) {
|
|
const epCount = animeDetails.episodes || 1;
|
|
for (let i = 1; i <= epCount; i++) {
|
|
episodes.push({ attributes: { number: i, canonicalTitle: `Episode ${i}`, thumbnail: null } });
|
|
}
|
|
hideThumbnails = true; // Force hide broken placeholders
|
|
}
|
|
|
|
episodesData = episodes.sort((a, b) => a.attributes.number - b.attributes.number);
|
|
renderEpisodes();
|
|
setupLocateButton();
|
|
}
|
|
|
|
function formatTimestamp(seconds) {
|
|
seconds = Math.floor(seconds); // handle fractional seconds
|
|
|
|
if (seconds < 60) {
|
|
return `${seconds}s`;
|
|
}
|
|
|
|
const mins = Math.floor(seconds / 60);
|
|
const secs = seconds % 60;
|
|
|
|
if (seconds < 3600) { // less than an hour
|
|
return `${String(mins).padStart(2, '0')}:${String(secs).padStart(2, '0')}`;
|
|
}
|
|
|
|
const hrs = Math.floor(seconds / 3600);
|
|
const remainingMins = mins % 60;
|
|
return `${String(hrs).padStart(2, '0')}:${String(remainingMins).padStart(2, '0')}:${String(secs).padStart(2, '0')}`;
|
|
}
|
|
|
|
function renderEpisodes() {
|
|
const list = document.getElementById("episode-list-container");
|
|
const movieCard = document.getElementById("movie-player-card");
|
|
|
|
if (isMovie) {
|
|
list.style.display = "none";
|
|
movieCard.style.display = "flex";
|
|
|
|
// Progress label
|
|
const hist = localWatchHistory[currentMalId]?.[1];
|
|
const movieLabel = document.querySelector(".movie-label");
|
|
movieLabel.innerHTML = "Play Movie";
|
|
if (hist?.timestamp > 0 && hist?.state !== "finished") {
|
|
// Use saved duration first, then parse from Jikan's duration string, then fallback to 90min
|
|
const duration = hist.duration || parseAnimeDuration(animeDetails?.duration) || 5400;
|
|
movieLabel.innerHTML = `Resume <span style="font-size:0.6em; color:var(--brand-accent)">(${formatTimestamp(hist.timestamp)})</span>`;
|
|
}
|
|
|
|
// Fetch backdrop thumbnail from server
|
|
fetch(`${serverUrl}/anime/${currentMalId}/movie/thumbnail`)
|
|
.then(r => r.json())
|
|
.then(d => {
|
|
document.getElementById("movie-card-backdrop").src = d.thumbnail_url || d.cover_url || "";
|
|
})
|
|
.catch(() => {
|
|
// Fallback to hero background image
|
|
const heroBg = document.getElementById("hero-section").style.backgroundImage;
|
|
if (heroBg) document.getElementById("movie-card-backdrop").src = heroBg.slice(5, -2);
|
|
});
|
|
|
|
movieCard.onclick = () => openPlayer(1);
|
|
updateWatchButton("Movie");
|
|
return;
|
|
}
|
|
|
|
list.className = `episode-list ${currentLayout} ${hideThumbnails ? 'thumbnails-hidden' : ''}`;
|
|
list.innerHTML = "";
|
|
|
|
const maxEp = Math.max(...episodesData.map(e => e.attributes.number));
|
|
|
|
// Handle Range Dropdown for Long Series (Phase 4)
|
|
if (maxEp > 100) {
|
|
ensureRangeDropdown(maxEp);
|
|
}
|
|
|
|
const filtered = episodesData.filter(e => e.attributes.number >= currentEpRange[0] && e.attributes.number <= currentEpRange[1]);
|
|
|
|
filtered.forEach(ep => {
|
|
const num = ep.attributes.number;
|
|
const hist = localWatchHistory[currentMalId]?.[num];
|
|
const isFinished = hist?.state === "finished";
|
|
const isUnwatched = !hist;
|
|
|
|
const item = document.createElement("div");
|
|
item.className = `episode-item ${isFinished ? 'watched' : ''} ${isUnwatched ? 'unwatched' : ''}`;
|
|
item.id = `ep-card-${num}`;
|
|
|
|
let progressHtml = "";
|
|
if (hist?.timestamp > 0 && !isFinished) {
|
|
const pct = (hist.timestamp / (hist.duration || 1440)) * 100;
|
|
progressHtml = `
|
|
<div class="episode-progress-wrapper">
|
|
<div class="ep-progress-track"><div class="ep-progress-fill" style="width:${pct}%"></div></div>
|
|
</div>`;
|
|
}
|
|
|
|
const thumbUrl = ep.attributes.thumbnail?.original ? `/proxy-image?url=${ep.attributes.thumbnail.original}` : 'https://placehold.co/320x180/111/333?text=No+Image';
|
|
|
|
item.innerHTML = `
|
|
<div class="episode-thumbnail"><img src="${thumbUrl}" loading="lazy"></div>
|
|
<div class="episode-info">
|
|
<div class="episode-title">${ep.attributes.canonicalTitle || 'Episode ' + num}</div>
|
|
<div class="episode-title-romanji">Episode ${num}</div>
|
|
${progressHtml}
|
|
</div>
|
|
<div class="episode-action-buttons"><i class="fas ${isFinished ? 'fa-rotate-right' : 'fa-play'}"></i></div>
|
|
`;
|
|
|
|
item.onclick = () => openPlayer(num);
|
|
list.appendChild(item);
|
|
});
|
|
|
|
updateWatchButton("Anime");
|
|
}
|
|
|
|
// --- PHASE 2: LOCATE LOGIC ---
|
|
function setupLocateButton() {
|
|
const btn = document.getElementById("hero-locate-btn");
|
|
const hist = localWatchHistory[currentMalId] || {};
|
|
let lastEp = 0;
|
|
Object.keys(hist).forEach(k => { if(parseInt(k) > lastEp) lastEp = parseInt(k); });
|
|
|
|
if (lastEp === 0) {
|
|
btn.style.display = "none";
|
|
return;
|
|
}
|
|
|
|
btn.style.display = "inline-flex";
|
|
btn.onclick = () => {
|
|
let target = lastEp;
|
|
if (hist[target]?.state === "finished") target++;
|
|
|
|
// 1. Calculate and switch range if needed
|
|
const rangeStart = Math.floor((target - 1) / 100) * 100 + 1;
|
|
const rangeEnd = rangeStart + 99;
|
|
|
|
if (currentEpRange[0] !== rangeStart) {
|
|
currentEpRange = [rangeStart, rangeEnd];
|
|
const selector = document.getElementById("ep-range-select");
|
|
if (selector) selector.value = `${rangeStart}-${rangeEnd}`;
|
|
renderEpisodes();
|
|
}
|
|
|
|
// 2. Scroll and Highlight
|
|
setTimeout(() => {
|
|
const el = document.getElementById(`ep-card-${target}`);
|
|
if (el) {
|
|
el.scrollIntoView({ behavior: 'smooth', block: 'center' });
|
|
el.classList.add("target-highlight");
|
|
setTimeout(() => el.classList.remove("target-highlight"), 3000);
|
|
}
|
|
}, 100);
|
|
};
|
|
}
|
|
|
|
// --- PHASE 3: LAYOUT TOGGLES ---
|
|
function initLayoutButtons() {
|
|
const gridBtn = document.getElementById("grid-toggle-btn");
|
|
const thumbBtn = document.getElementById("thumbnail-toggle-btn");
|
|
|
|
const updateBtns = () => {
|
|
gridBtn.innerHTML = currentLayout === "grid-view" ? '<i class="fas fa-list"></i>' : '<i class="fas fa-th-large"></i>';
|
|
gridBtn.classList.toggle("active", currentLayout === "grid-view");
|
|
thumbBtn.classList.toggle("active", !hideThumbnails);
|
|
};
|
|
|
|
gridBtn.onclick = () => {
|
|
currentLayout = currentLayout === "list-view" ? "grid-view" : "list-view";
|
|
localStorage.setItem("animex_layout", currentLayout);
|
|
updateBtns();
|
|
renderEpisodes();
|
|
};
|
|
|
|
thumbBtn.onclick = () => {
|
|
hideThumbnails = !hideThumbnails;
|
|
localStorage.setItem("hideThumbnails", hideThumbnails);
|
|
updateBtns();
|
|
renderEpisodes();
|
|
};
|
|
|
|
updateBtns();
|
|
}
|
|
|
|
function ensureRangeDropdown(maxEp) {
|
|
let dropdown = document.getElementById("ep-range-select");
|
|
if (!dropdown) {
|
|
dropdown = document.createElement("select");
|
|
dropdown.id = "ep-range-select";
|
|
dropdown.className = "season-selector"; // Reuse styles
|
|
dropdown.style.marginBottom = "1.5rem";
|
|
dropdown.style.width = "auto";
|
|
document.getElementById("episode-list-container").before(dropdown);
|
|
dropdown.onchange = (e) => {
|
|
currentEpRange = e.target.value.split("-").map(Number);
|
|
renderEpisodes();
|
|
};
|
|
}
|
|
dropdown.innerHTML = "";
|
|
for (let i = 1; i <= maxEp; i += 100) {
|
|
const end = Math.min(i + 99, maxEp);
|
|
const opt = document.createElement("option");
|
|
opt.value = `${i}-${end}`;
|
|
opt.textContent = `Episodes ${i} - ${end}`;
|
|
if (currentEpRange[0] === i) opt.selected = true;
|
|
dropdown.appendChild(opt);
|
|
}
|
|
}
|
|
|
|
// --- WATCH HISTORY & PLAYER ---
|
|
function updateWatchButton(mode) {
|
|
const btn = document.getElementById("hero-watch-btn");
|
|
const hist = localWatchHistory[currentMalId] || {};
|
|
let lastEp = 0;
|
|
Object.keys(hist).forEach(k => { if(parseInt(k) > lastEp) lastEp = parseInt(k); });
|
|
|
|
let targetEp = 1;
|
|
let label = "Start Watching";
|
|
|
|
if (mode === "Movie") {
|
|
targetEp = 1;
|
|
label = lastEp === 1 ? "Resume Movie" : "Watch Movie";
|
|
} else {
|
|
targetEp = lastEp > 0 ? lastEp : 1;
|
|
if (hist[targetEp]?.state === "finished") targetEp++;
|
|
label = lastEp > 0 ? `Continue Ep ${targetEp}` : `Start Ep 1`;
|
|
}
|
|
|
|
btn.innerHTML = `<i class="fas fa-play"></i> <span>${label}</span>`;
|
|
btn.onclick = () => {
|
|
if (isRxRated && !rxGateAccepted) {
|
|
showRxGate();
|
|
document.getElementById("rx-gate-yes").onclick = () => {
|
|
rxGateAccepted = true;
|
|
dismissRxGate();
|
|
openPlayer(targetEp);
|
|
};
|
|
} else {
|
|
openPlayer(targetEp);
|
|
}
|
|
};
|
|
}
|
|
|
|
function openPlayer(ep) {
|
|
const url = `view.html?id=${currentMalId}&ep=${ep}`;
|
|
document.getElementById("episode-popup-iframe").src = url;
|
|
document.getElementById("episode-popup-overlay").classList.add("active");
|
|
}
|
|
|
|
function closePlayerPopup() {
|
|
document.getElementById("episode-popup-overlay").classList.remove("active");
|
|
document.getElementById("episode-popup-iframe").src = "";
|
|
// Sync history on close
|
|
localWatchHistory = JSON.parse(localStorage.getItem("animex_watch_history") || "{}");
|
|
renderEpisodes();
|
|
setupLocateButton();
|
|
}
|
|
|
|
// --- PHASE 1: MODERNIZED RX GATE ---
|
|
function initRxGate() {
|
|
document.getElementById("rx-gate-no").onclick = () => window.history.back();
|
|
document.getElementById("rx-gate-yes").onclick = () => {
|
|
rxGateAccepted = true;
|
|
dismissRxGate();
|
|
};
|
|
}
|
|
|
|
function showRxGate() {
|
|
const overlay = document.getElementById("rx-gate-overlay");
|
|
overlay.style.display = "flex";
|
|
setTimeout(() => overlay.classList.add("visible"), 50);
|
|
}
|
|
|
|
function dismissRxGate() {
|
|
const overlay = document.getElementById("rx-gate-overlay");
|
|
overlay.classList.remove("visible");
|
|
setTimeout(() => overlay.style.display = "none", 400);
|
|
}
|
|
|
|
// --- THEMES & MUSIC ---
|
|
async function fetchThemes() {
|
|
const container = document.getElementById("themes-list-container");
|
|
try {
|
|
const res = await fetch(`${THEMES_API}/${currentMalId}`);
|
|
const themes = await res.json();
|
|
if (!themes.length) {
|
|
container.innerHTML = "<p class='text-muted'>No themes found.</p>";
|
|
return;
|
|
}
|
|
container.innerHTML = "";
|
|
themes.forEach(t => {
|
|
const div = document.createElement("div");
|
|
div.className = "theme-item";
|
|
div.innerHTML = `
|
|
<div class="theme-info">
|
|
<span class="theme-type">${t.slug}</span>
|
|
<span class="theme-title">${t.title}</span>
|
|
</div>
|
|
<div class="theme-actions">
|
|
<button class="theme-btn play" onclick="playTheme('${t.url}', '${t.title.replace(/'/g, "\\'")}')"><i class="fas fa-play"></i></button>
|
|
<button class="theme-btn" onclick="downloadTheme('${t.url}')"><i class="fas fa-download"></i></button>
|
|
</div>
|
|
`;
|
|
container.appendChild(div);
|
|
});
|
|
} catch (e) { container.innerHTML = ""; }
|
|
}
|
|
|
|
window.playTheme = (url, title) => {
|
|
const track = { url, title, anime: animeDetails.title, image: animeDetails.images.jpg.large_image_url };
|
|
localStorage.setItem('animex_music_queue', JSON.stringify([track]));
|
|
localStorage.setItem('animex_music_cmd', Date.now());
|
|
if (window.parent?.showToast) window.parent.showToast(`Playing: ${title}`, true);
|
|
};
|
|
|
|
window.downloadTheme = (url) => window.open(url, '_blank');
|
|
|
|
// --- OTHER DATA FETCHERS ---
|
|
async function fetchCharacters() {
|
|
try {
|
|
const res = await fetch(`${serverUrl}/anime/${currentMalId}/characters`);
|
|
const data = await res.json();
|
|
characterData = data.characters || [];
|
|
renderCharacters("JAPANESE");
|
|
|
|
document.querySelectorAll(".char-lang-btn").forEach(btn => {
|
|
btn.onclick = () => {
|
|
document.querySelectorAll(".char-lang-btn").forEach(b => b.classList.remove("active"));
|
|
btn.classList.add("active");
|
|
renderCharacters(btn.dataset.lang);
|
|
};
|
|
});
|
|
} catch (e) {}
|
|
}
|
|
|
|
function renderCharacters(lang) {
|
|
const list = document.getElementById("characters-list");
|
|
list.innerHTML = "";
|
|
characterData.forEach(c => {
|
|
const va = c.voice_actors.find(v => v.language === lang) || { name: "N/A", image: "" };
|
|
const div = document.createElement("div");
|
|
div.className = "character-card";
|
|
div.innerHTML = `
|
|
<div class="char-card-imgs"><img src="${c.character.image}"><img src="${va.image || 'https://placehold.co/100x100/111/333?text=No+VA'}"></div>
|
|
<div class="char-info"><span class="char-name">${c.character.name}</span><span class="va-name">${va.name}</span></div>
|
|
`;
|
|
list.appendChild(div);
|
|
});
|
|
document.getElementById("characters-section").style.display = characterData.length ? "block" : "none";
|
|
}
|
|
|
|
async function fetchRecs() {
|
|
const res = await fetch(`/proxy?url=https://api.jikan.moe/v4/anime/${currentMalId}/recommendations`);
|
|
const data = (await res.json()).data;
|
|
const grid = document.getElementById("recommendations-grid");
|
|
data?.slice(0, 15).forEach(r => {
|
|
const a = document.createElement("a");
|
|
a.className = "tile-card";
|
|
a.href = `series-info.html?id=${r.entry.mal_id}`;
|
|
a.innerHTML = `<div class="tile-img-container"><img src="${r.entry.images.jpg.image_url}" class="tile-img" loading="lazy"></div><div class="tile-title">${r.entry.title}</div>`;
|
|
grid.appendChild(a);
|
|
});
|
|
}
|
|
|
|
async function fetchRelated() {
|
|
const res = await fetch(`/proxy?url=https://api.jikan.moe/v4/anime/${currentMalId}/relations`);
|
|
const data = (await res.json()).data;
|
|
const container = document.getElementById("related-container");
|
|
data?.forEach(rel => {
|
|
rel.entry.forEach(entry => {
|
|
if (entry.type !== 'anime') return;
|
|
const a = document.createElement("a");
|
|
a.className = "tile-card";
|
|
a.href = `series-info.html?id=${entry.mal_id}`;
|
|
a.innerHTML = `<div class="tile-img-container"><img src="https://placehold.co/200x300/111/333?text=..." class="tile-img"><div class="tile-tag">${rel.relation}</div></div><div class="tile-title">${entry.name}</div>`;
|
|
container.appendChild(a);
|
|
fetch(`/proxy?url=https://api.jikan.moe/v4/anime/${entry.mal_id}`).then(r => r.json()).then(d => {
|
|
if (d.data?.images?.jpg?.image_url) a.querySelector("img").src = d.data.images.jpg.image_url;
|
|
});
|
|
});
|
|
});
|
|
}
|
|
|
|
async function fetchSeasons() {
|
|
try {
|
|
const res = await fetch(`${serverUrl}/anime/${currentMalId}/seasons`);
|
|
const data = await res.json();
|
|
const entries = data.season_groups || [];
|
|
if (entries.length > 1 || (entries[0]?.parts.length > 1)) {
|
|
const sel = document.getElementById("season-selector");
|
|
document.getElementById("season-selector-wrapper").style.display = "block";
|
|
sel.innerHTML = "";
|
|
entries.forEach(g => {
|
|
g.parts.forEach(p => {
|
|
const opt = document.createElement("option");
|
|
opt.value = p.mal_id;
|
|
opt.textContent = `${g.group_label} - ${p.short_label}`;
|
|
if (String(p.mal_id) === currentMalId) opt.selected = true;
|
|
sel.appendChild(opt);
|
|
});
|
|
});
|
|
sel.onchange = (e) => window.location.href = `series-info.html?id=${e.target.value}`;
|
|
}
|
|
} catch (e) {}
|
|
}
|
|
|
|
// --- HELPERS ---
|
|
function initTabSystem() {
|
|
document.querySelectorAll(".tab-btn").forEach(btn => {
|
|
btn.onclick = () => {
|
|
if (window.innerWidth >= 1024) return;
|
|
document.querySelectorAll(".tab-btn").forEach(b => b.classList.remove("active"));
|
|
document.querySelectorAll(".mobile-tab-content").forEach(c => c.classList.remove("active"));
|
|
btn.classList.add("active");
|
|
document.getElementById(`tab-panel-${btn.dataset.tab}`).classList.add("active");
|
|
};
|
|
});
|
|
}
|
|
|
|
// Parses Jikan duration strings like "1 hr 54 min", "24 min", "2 hr" into seconds
|
|
function parseAnimeDuration(str) {
|
|
if (!str) return null;
|
|
let seconds = 0;
|
|
const hrs = str.match(/(\d+)\s*hr/);
|
|
const mins = str.match(/(\d+)\s*min/);
|
|
if (hrs) seconds += parseInt(hrs[1]) * 3600;
|
|
if (mins) seconds += parseInt(mins[1]) * 60;
|
|
return seconds > 0 ? seconds : null;
|
|
}
|
|
|
|
function initYoutubePlayer(id) {
|
|
if (window.csPlayer) {
|
|
csPlayer.init("video", { defaultId: id, thumbnail: true, theme: "default" }).catch(() => {});
|
|
}
|
|
}
|
|
</script>
|
|
</body>
|
|
</html> |