js another wed
This commit is contained in:
@@ -10,7 +10,7 @@
|
|||||||
<link rel="preconnect" href="https://fonts.googleapis.com" />
|
<link rel="preconnect" href="https://fonts.googleapis.com" />
|
||||||
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin />
|
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin />
|
||||||
<link
|
<link
|
||||||
href="https://fonts.googleapis.com/css2?family=Inter:wght@400;500;600;700&display=swap"
|
href="https://fonts.googleapis.com/css2?family=Outfit:wght@400;500;600;700;800&family=DM+Sans:wght@400;500;600&display=swap"
|
||||||
rel="stylesheet"
|
rel="stylesheet"
|
||||||
/>
|
/>
|
||||||
<link
|
<link
|
||||||
@@ -19,20 +19,22 @@
|
|||||||
/>
|
/>
|
||||||
<style>
|
<style>
|
||||||
:root {
|
:root {
|
||||||
--accent-color: #ff9500; /* Distinct Orange */
|
--accent-color: #ff9500;
|
||||||
--background-color: #121212;
|
--accent-glow: rgba(255, 149, 0, 0.35);
|
||||||
--background-color-secondary: #1e1e1e;
|
--background-color: #0e0e0e;
|
||||||
|
--background-color-secondary: #181818;
|
||||||
|
--surface: #1c1c1c;
|
||||||
--text-primary: #ffffff;
|
--text-primary: #ffffff;
|
||||||
--text-secondary: #aaaaaa;
|
--text-secondary: #999999;
|
||||||
--border-color: rgba(255, 255, 255, 0.15);
|
--border-color: rgba(255, 255, 255, 0.08);
|
||||||
--shadow-medium: 0 10px 30px rgba(0, 0, 0, 0.5);
|
--shadow-medium: 0 10px 30px rgba(0, 0, 0, 0.5);
|
||||||
--shadow-heavy: 0 20px 50px rgba(0, 0, 0, 0.7);
|
--shadow-heavy: 0 20px 50px rgba(0, 0, 0, 0.7);
|
||||||
--transition-smooth: cubic-bezier(0.25, 0.46, 0.45, 0.94);
|
--transition-smooth: cubic-bezier(0.25, 0.46, 0.45, 0.94);
|
||||||
|
--radius-card: 14px;
|
||||||
}
|
}
|
||||||
|
|
||||||
body {
|
body {
|
||||||
font-family: "Inter", -apple-system, BlinkMacSystemFont, "Segoe UI",
|
font-family: "Outfit", -apple-system, BlinkMacSystemFont, sans-serif;
|
||||||
Roboto, sans-serif;
|
|
||||||
background-color: var(--background-color);
|
background-color: var(--background-color);
|
||||||
margin: 0;
|
margin: 0;
|
||||||
color: var(--text-primary);
|
color: var(--text-primary);
|
||||||
@@ -44,17 +46,17 @@
|
|||||||
overflow: hidden;
|
overflow: hidden;
|
||||||
}
|
}
|
||||||
|
|
||||||
/* --- Liquid Glass Navbar --- */
|
/* --- Navbar --- */
|
||||||
.navbar {
|
.navbar {
|
||||||
position: fixed;
|
position: fixed;
|
||||||
top: 0;
|
top: 0;
|
||||||
left: 0;
|
left: 0;
|
||||||
width: 100%;
|
width: 100%;
|
||||||
z-index: 1000;
|
z-index: 1000;
|
||||||
padding: 20px 6%;
|
padding: 22px 6%;
|
||||||
background: linear-gradient(
|
background: linear-gradient(
|
||||||
to bottom,
|
to bottom,
|
||||||
rgba(0, 0, 0, 0.8) 0%,
|
rgba(0, 0, 0, 0.85) 0%,
|
||||||
transparent 100%
|
transparent 100%
|
||||||
);
|
);
|
||||||
backdrop-filter: blur(0px);
|
backdrop-filter: blur(0px);
|
||||||
@@ -68,19 +70,20 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
.navbar.scrolled {
|
.navbar.scrolled {
|
||||||
background: rgba(18, 18, 18, 0.85);
|
background: rgba(14, 14, 14, 0.92);
|
||||||
backdrop-filter: blur(16px);
|
backdrop-filter: blur(20px);
|
||||||
-webkit-backdrop-filter: blur(16px);
|
-webkit-backdrop-filter: blur(20px);
|
||||||
border-bottom: 1px solid rgba(255, 255, 255, 0.05);
|
border-bottom: 1px solid rgba(255, 149, 0, 0.08);
|
||||||
padding: 15px 6%;
|
padding: 14px 6%;
|
||||||
|
box-shadow: 0 1px 0 rgba(255,255,255,0.04);
|
||||||
}
|
}
|
||||||
|
|
||||||
.navbar svg {
|
.navbar svg {
|
||||||
display: block;
|
display: block;
|
||||||
margin: 0;
|
margin: 0;
|
||||||
height: 40px;
|
height: 38px;
|
||||||
width: auto;
|
width: auto;
|
||||||
opacity: 0.9;
|
opacity: 0.92;
|
||||||
transition: opacity 0.4s ease-out;
|
transition: opacity 0.4s ease-out;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -91,8 +94,8 @@
|
|||||||
/* --- Hero Section Cinematic Carousel --- */
|
/* --- Hero Section Cinematic Carousel --- */
|
||||||
#hero-section {
|
#hero-section {
|
||||||
position: relative;
|
position: relative;
|
||||||
height: 65vh;
|
height: 68vh;
|
||||||
min-height: 600px;
|
min-height: 580px;
|
||||||
display: flex;
|
display: flex;
|
||||||
flex-direction: column;
|
flex-direction: column;
|
||||||
justify-content: center;
|
justify-content: center;
|
||||||
@@ -197,39 +200,41 @@
|
|||||||
display: inline-flex;
|
display: inline-flex;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
justify-content: center;
|
justify-content: center;
|
||||||
gap: 12px;
|
gap: 10px;
|
||||||
padding: 14px 32px;
|
padding: 13px 30px;
|
||||||
border-radius: 8px;
|
border-radius: 10px;
|
||||||
|
font-family: "Outfit", sans-serif;
|
||||||
font-weight: 700;
|
font-weight: 700;
|
||||||
text-decoration: none;
|
text-decoration: none;
|
||||||
font-size: 1.1rem;
|
font-size: 1rem;
|
||||||
cursor: pointer;
|
cursor: pointer;
|
||||||
transition: all 0.3s var(--transition-smooth);
|
transition: all 0.3s var(--transition-smooth);
|
||||||
border: 1px solid transparent;
|
border: 1px solid transparent;
|
||||||
|
letter-spacing: 0.02em;
|
||||||
}
|
}
|
||||||
|
|
||||||
.hero-btn.play-btn {
|
.hero-btn.play-btn {
|
||||||
background-color: #ff9500;
|
background-color: var(--accent-color);
|
||||||
color: #fff;
|
color: #fff;
|
||||||
|
box-shadow: 0 4px 20px var(--accent-glow);
|
||||||
}
|
}
|
||||||
|
|
||||||
.hero-btn.play-btn:hover {
|
.hero-btn.play-btn:hover {
|
||||||
transform: scale(1.05);
|
transform: scale(1.05) translateY(-1px);
|
||||||
background-color: var(--accent-color);
|
box-shadow: 0 8px 32px rgba(255, 149, 0, 0.55);
|
||||||
color: #fff;
|
|
||||||
box-shadow: 0 0 30px rgba(255, 149, 0, 0.4);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
.hero-btn.info-btn {
|
.hero-btn.info-btn {
|
||||||
background-color: rgba(100, 100, 100, 0.4);
|
background-color: rgba(255, 255, 255, 0.1);
|
||||||
color: #ffffff;
|
color: #ffffff;
|
||||||
backdrop-filter: blur(10px);
|
backdrop-filter: blur(12px);
|
||||||
|
border-color: rgba(255, 255, 255, 0.18);
|
||||||
}
|
}
|
||||||
|
|
||||||
.hero-btn.info-btn:hover {
|
.hero-btn.info-btn:hover {
|
||||||
background-color: rgba(255, 255, 255, 0.2);
|
background-color: rgba(255, 255, 255, 0.18);
|
||||||
border-color: rgba(255, 255, 255, 0.5);
|
border-color: rgba(255, 255, 255, 0.4);
|
||||||
transform: scale(1.05);
|
transform: scale(1.05) translateY(-1px);
|
||||||
}
|
}
|
||||||
|
|
||||||
.hero-dots {
|
.hero-dots {
|
||||||
@@ -266,13 +271,13 @@
|
|||||||
@media (max-width: 768px) {
|
@media (max-width: 768px) {
|
||||||
.navbar {
|
.navbar {
|
||||||
justify-content: center;
|
justify-content: center;
|
||||||
padding: 15px 6%;
|
padding: 14px 6%;
|
||||||
display: none;
|
display: none;
|
||||||
}
|
}
|
||||||
|
|
||||||
#hero-section {
|
#hero-section {
|
||||||
height: 55vh;
|
height: 58vh;
|
||||||
min-height: 400px;
|
min-height: 380px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.hero-content {
|
.hero-content {
|
||||||
@@ -283,25 +288,25 @@
|
|||||||
|
|
||||||
.hero-logo {
|
.hero-logo {
|
||||||
object-position: center bottom;
|
object-position: center bottom;
|
||||||
max-width: 80%;
|
max-width: 78%;
|
||||||
margin-bottom: 20px;
|
margin-bottom: 16px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.hero-buttons {
|
.hero-buttons {
|
||||||
margin-top: -10px;
|
margin-top: -6px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.hero-background::after {
|
.hero-background::after {
|
||||||
background: radial-gradient(
|
background: radial-gradient(
|
||||||
circle at 50% 35%,
|
circle at 50% 35%,
|
||||||
transparent 0%,
|
transparent 0%,
|
||||||
rgba(18, 18, 18, 0.2) 40%,
|
rgba(14, 14, 14, 0.2) 40%,
|
||||||
var(--background-color) 120%
|
var(--background-color) 120%
|
||||||
),
|
),
|
||||||
linear-gradient(
|
linear-gradient(
|
||||||
to right,
|
to right,
|
||||||
var(--background-color) 0%,
|
var(--background-color) 0%,
|
||||||
rgba(18, 18, 18, 0.4) 50%,
|
rgba(14, 14, 14, 0.4) 50%,
|
||||||
transparent 100%
|
transparent 100%
|
||||||
),
|
),
|
||||||
linear-gradient(to top, var(--background-color) 0%, transparent 40%);
|
linear-gradient(to top, var(--background-color) 0%, transparent 40%);
|
||||||
@@ -311,40 +316,38 @@
|
|||||||
display: none;
|
display: none;
|
||||||
}
|
}
|
||||||
|
|
||||||
/* --- UPDATED MOBILE CARD SIZES --- */
|
|
||||||
.horizontal-scroll-container {
|
.horizontal-scroll-container {
|
||||||
gap: 12px !important; /* Tighter gap so more fit on screen */
|
gap: 10px !important;
|
||||||
margin-bottom: -40px;
|
margin-bottom: -40px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.poster-container {
|
.poster-container {
|
||||||
max-width: 150px; /* Reduced Width (Fits ~3-4 cards per row) */
|
max-width: 140px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.poster-title {
|
.poster-title {
|
||||||
font-size: 0.8rem; /* Smaller Font */
|
font-size: 0.78rem;
|
||||||
margin-top: 8px;
|
margin-top: 7px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.episode-indicator {
|
.episode-indicator {
|
||||||
font-size: 0.65rem; /* Smaller badge */
|
font-size: 0.62rem;
|
||||||
padding: 2px 6px;
|
padding: 2px 5px;
|
||||||
top: 6px;
|
top: 5px;
|
||||||
right: 6px;
|
right: 5px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.poster-image-wrapper {
|
.poster-image-wrapper {
|
||||||
max-width: 150px;
|
max-width: 140px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.poster-info-btn {
|
.poster-info-btn {
|
||||||
width: 28px;
|
width: 26px;
|
||||||
height: 28px;
|
height: 26px;
|
||||||
font-size: 0.8rem;
|
font-size: 0.75rem;
|
||||||
top: 6px;
|
top: 5px;
|
||||||
left: 6px;
|
left: 5px;
|
||||||
}
|
}
|
||||||
/* -------------------------------- */
|
|
||||||
|
|
||||||
.hero-buttons {
|
.hero-buttons {
|
||||||
justify-content: center;
|
justify-content: center;
|
||||||
@@ -356,6 +359,31 @@
|
|||||||
transform: translateX(-50%) translateY(-450%);
|
transform: translateX(-50%) translateY(-450%);
|
||||||
bottom: 20px;
|
bottom: 20px;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.section-title {
|
||||||
|
font-size: 1.1rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.content-section {
|
||||||
|
margin-bottom: 36px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/* 1440p desktop tweaks */
|
||||||
|
@media (min-width: 1400px) {
|
||||||
|
.poster-container {
|
||||||
|
width: 210px;
|
||||||
|
}
|
||||||
|
#hero-section {
|
||||||
|
height: 62vh;
|
||||||
|
min-height: 640px;
|
||||||
|
}
|
||||||
|
.hero-content {
|
||||||
|
max-width: 44%;
|
||||||
|
}
|
||||||
|
.hero-logo {
|
||||||
|
max-width: 420px;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
.loader {
|
.loader {
|
||||||
@@ -379,33 +407,36 @@
|
|||||||
|
|
||||||
/* --- Content Sections --- */
|
/* --- Content Sections --- */
|
||||||
.content-section {
|
.content-section {
|
||||||
margin-bottom: 50px;
|
margin-bottom: 48px;
|
||||||
padding: 0 0 0 6%;
|
padding: 0 0 0 6%;
|
||||||
position: relative;
|
position: relative;
|
||||||
z-index: 5;
|
z-index: 5;
|
||||||
}
|
}
|
||||||
|
|
||||||
.content-section:first-of-type {
|
.content-section:first-of-type {
|
||||||
margin-top: -60px;
|
margin-top: -70px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.section-title {
|
.section-title {
|
||||||
font-size: 1.5rem;
|
font-size: 1.35rem;
|
||||||
font-weight: 700;
|
font-weight: 700;
|
||||||
|
letter-spacing: 0.01em;
|
||||||
color: var(--text-primary);
|
color: var(--text-primary);
|
||||||
margin-bottom: 25px;
|
margin-bottom: 20px;
|
||||||
display: flex;
|
display: flex;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
gap: 10px;
|
gap: 12px;
|
||||||
|
font-family: "Outfit", sans-serif;
|
||||||
}
|
}
|
||||||
|
|
||||||
.section-title::before {
|
.section-title::before {
|
||||||
content: "";
|
content: "";
|
||||||
display: block;
|
display: block;
|
||||||
width: 4px;
|
width: 3px;
|
||||||
height: 24px;
|
height: 20px;
|
||||||
background-color: var(--accent-color);
|
background: linear-gradient(to bottom, var(--accent-color), rgba(255,149,0,0.3));
|
||||||
border-radius: 2px;
|
border-radius: 3px;
|
||||||
|
flex-shrink: 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
.horizontal-scroll-container {
|
.horizontal-scroll-container {
|
||||||
@@ -434,28 +465,28 @@
|
|||||||
/* --- Poster Cards --- */
|
/* --- Poster Cards --- */
|
||||||
.poster-container {
|
.poster-container {
|
||||||
flex-shrink: 0;
|
flex-shrink: 0;
|
||||||
width: 220px; /* Desktop Width */
|
width: 200px;
|
||||||
position: relative;
|
position: relative;
|
||||||
transition: z-index 0.3s, transform 0.3s;
|
transition: z-index 0.3s, transform 0.3s;
|
||||||
}
|
}
|
||||||
|
|
||||||
.poster-image-wrapper {
|
.poster-image-wrapper {
|
||||||
position: relative;
|
position: relative;
|
||||||
border-radius: 12px;
|
border-radius: var(--radius-card);
|
||||||
overflow: hidden;
|
overflow: hidden;
|
||||||
aspect-ratio: 2/3;
|
aspect-ratio: 2/3;
|
||||||
box-shadow: 0 4px 10px rgba(0, 0, 0, 0.3);
|
box-shadow: 0 6px 18px rgba(0, 0, 0, 0.4);
|
||||||
transition: transform 0.4s var(--transition-smooth),
|
transition: transform 0.4s var(--transition-smooth),
|
||||||
box-shadow 0.4s var(--transition-smooth), border-color 0.3s;
|
box-shadow 0.4s var(--transition-smooth), border-color 0.3s;
|
||||||
border: 2px solid transparent;
|
border: 1.5px solid transparent;
|
||||||
background-color: #2a2a2a;
|
background-color: var(--surface);
|
||||||
}
|
}
|
||||||
|
|
||||||
.poster-image-wrapper img {
|
.poster-image-wrapper img {
|
||||||
width: 100%;
|
width: 100%;
|
||||||
height: 100%;
|
height: 100%;
|
||||||
object-fit: cover;
|
object-fit: cover;
|
||||||
transition: filter 0.3s;
|
transition: filter 0.3s, transform 0.4s var(--transition-smooth);
|
||||||
}
|
}
|
||||||
|
|
||||||
.poster-container:hover {
|
.poster-container:hover {
|
||||||
@@ -463,28 +494,31 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
.poster-container:hover .poster-image-wrapper {
|
.poster-container:hover .poster-image-wrapper {
|
||||||
transform: scale(1) translateY(-5px);
|
transform: scale(1) translateY(-6px);
|
||||||
box-shadow: 0 15px 40px rgba(0, 0, 0, 0.6);
|
box-shadow: 0 18px 45px rgba(0, 0, 0, 0.65), 0 0 0 1px rgba(255,149,0,0.15);
|
||||||
border-color: rgba(255, 255, 255, 0.2);
|
border-color: rgba(255, 149, 0, 0.2);
|
||||||
|
}
|
||||||
|
|
||||||
|
.poster-container:hover .poster-image-wrapper img {
|
||||||
|
transform: scale(1.03);
|
||||||
}
|
}
|
||||||
|
|
||||||
.poster-title {
|
.poster-title {
|
||||||
margin-top: 15px;
|
margin-top: 12px;
|
||||||
font-size: 1rem;
|
font-size: 0.88rem;
|
||||||
font-weight: 600;
|
font-weight: 600;
|
||||||
color: var(--text-secondary);
|
color: var(--text-secondary);
|
||||||
white-space: nowrap;
|
white-space: nowrap;
|
||||||
overflow: hidden;
|
overflow: hidden;
|
||||||
text-overflow: ellipsis;
|
text-overflow: ellipsis;
|
||||||
text-align: center;
|
text-align: center;
|
||||||
opacity: 0.8;
|
transition: color 0.3s;
|
||||||
transition: color 0.3s, opacity 0.3s;
|
padding: 0 4px;
|
||||||
padding: 0 5px;
|
font-family: "DM Sans", sans-serif;
|
||||||
}
|
}
|
||||||
|
|
||||||
.poster-container:hover .poster-title {
|
.poster-container:hover .poster-title {
|
||||||
color: var(--text-primary);
|
color: var(--text-primary);
|
||||||
opacity: 1;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
.progress-bar {
|
.progress-bar {
|
||||||
@@ -492,53 +526,56 @@
|
|||||||
bottom: 0;
|
bottom: 0;
|
||||||
left: 0;
|
left: 0;
|
||||||
width: 100%;
|
width: 100%;
|
||||||
height: 4px;
|
height: 3px;
|
||||||
background-color: rgba(255, 255, 255, 0.2);
|
background-color: rgba(255, 255, 255, 0.15);
|
||||||
z-index: 2;
|
z-index: 2;
|
||||||
}
|
}
|
||||||
|
|
||||||
.progress-bar-inner {
|
.progress-bar-inner {
|
||||||
height: 100%;
|
height: 100%;
|
||||||
background-color: var(--accent-color);
|
background: linear-gradient(to right, var(--accent-color), #ffb340);
|
||||||
box-shadow: 0 0 10px var(--accent-color);
|
box-shadow: 0 0 8px var(--accent-glow);
|
||||||
|
border-radius: 0 2px 2px 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
.episode-indicator {
|
.episode-indicator {
|
||||||
position: absolute;
|
position: absolute;
|
||||||
top: 10px;
|
top: 8px;
|
||||||
right: 10px;
|
right: 8px;
|
||||||
left: auto;
|
left: auto;
|
||||||
background-color: rgba(0, 0, 0, 0.6);
|
background-color: rgba(0, 0, 0, 0.72);
|
||||||
color: #fff;
|
color: #fff;
|
||||||
padding: 4px 10px;
|
padding: 3px 8px;
|
||||||
border-radius: 4px;
|
border-radius: 6px;
|
||||||
font-size: 0.75rem;
|
font-size: 0.7rem;
|
||||||
font-weight: 700;
|
font-weight: 700;
|
||||||
z-index: 2;
|
z-index: 2;
|
||||||
backdrop-filter: blur(4px);
|
backdrop-filter: blur(6px);
|
||||||
border: 1px solid rgba(255, 255, 255, 0.1);
|
border: 1px solid rgba(255, 255, 255, 0.08);
|
||||||
letter-spacing: 0.5px;
|
letter-spacing: 0.4px;
|
||||||
|
font-family: "Outfit", sans-serif;
|
||||||
}
|
}
|
||||||
|
|
||||||
.poster-info-btn {
|
.poster-info-btn {
|
||||||
position: absolute;
|
position: absolute;
|
||||||
top: 10px;
|
top: 8px;
|
||||||
left: 10px;
|
left: 8px;
|
||||||
right: auto;
|
right: auto;
|
||||||
z-index: 3;
|
z-index: 3;
|
||||||
background-color: rgba(30, 30, 30, 0.8);
|
background-color: rgba(20, 20, 20, 0.82);
|
||||||
color: #fff;
|
color: #fff;
|
||||||
width: 32px;
|
width: 30px;
|
||||||
height: 32px;
|
height: 30px;
|
||||||
border-radius: 50%;
|
border-radius: 50%;
|
||||||
display: flex;
|
display: flex;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
justify-content: center;
|
justify-content: center;
|
||||||
opacity: 0;
|
opacity: 0;
|
||||||
transform: translateY(-10px);
|
transform: translateY(-8px);
|
||||||
transition: all 0.3s ease;
|
transition: all 0.3s ease;
|
||||||
border: 1px solid rgba(255, 255, 255, 0.2);
|
border: 1px solid rgba(255, 255, 255, 0.15);
|
||||||
cursor: pointer;
|
cursor: pointer;
|
||||||
|
font-size: 0.8rem;
|
||||||
}
|
}
|
||||||
|
|
||||||
.poster-container:hover .poster-info-btn {
|
.poster-container:hover .poster-info-btn {
|
||||||
@@ -547,8 +584,9 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
.poster-info-btn:hover {
|
.poster-info-btn:hover {
|
||||||
background-color: #fff;
|
background-color: var(--accent-color);
|
||||||
color: #000;
|
border-color: var(--accent-color);
|
||||||
|
color: #fff;
|
||||||
}
|
}
|
||||||
|
|
||||||
/* --- Context Menu --- */
|
/* --- Context Menu --- */
|
||||||
@@ -573,12 +611,12 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
.context-menu {
|
.context-menu {
|
||||||
width: 380px;
|
width: 340px;
|
||||||
max-width: 90%;
|
max-width: 90%;
|
||||||
background-color: #1e1e1e;
|
background-color: #1c1c1c;
|
||||||
border-radius: 16px;
|
border-radius: 18px;
|
||||||
box-shadow: var(--shadow-heavy);
|
box-shadow: var(--shadow-heavy), 0 0 0 1px rgba(255,255,255,0.06);
|
||||||
border: 1px solid rgba(255, 255, 255, 0.1);
|
border: 1px solid rgba(255, 255, 255, 0.08);
|
||||||
transform: scale(0.95) translateY(20px);
|
transform: scale(0.95) translateY(20px);
|
||||||
transition: transform 0.3s cubic-bezier(0.175, 0.885, 0.32, 1.275);
|
transition: transform 0.3s cubic-bezier(0.175, 0.885, 0.32, 1.275);
|
||||||
overflow: hidden;
|
overflow: hidden;
|
||||||
|
|||||||
@@ -1083,7 +1083,7 @@
|
|||||||
opacity: 0;
|
opacity: 0;
|
||||||
transform: translateY(20px);
|
transform: translateY(20px);
|
||||||
transition: opacity 0.4s ease, transform 0.4s ease;
|
transition: opacity 0.4s ease, transform 0.4s ease;
|
||||||
box-shadow: 0 8px 32px rgba(0, 0, 0, 0.5);
|
box-shadow: 0 8px 32px rgba(0, 0, 0, 0.5), 0 0 0 1px rgba(255,255,255,0.06) inset;
|
||||||
}
|
}
|
||||||
#startup-status.visible {
|
#startup-status.visible {
|
||||||
opacity: 1;
|
opacity: 1;
|
||||||
@@ -1095,6 +1095,7 @@
|
|||||||
border-radius: 50%;
|
border-radius: 50%;
|
||||||
background: #ff9500;
|
background: #ff9500;
|
||||||
box-shadow: 0 0 10px #ff9500;
|
box-shadow: 0 0 10px #ff9500;
|
||||||
|
flex-shrink: 0;
|
||||||
}
|
}
|
||||||
#startup-status .status-dot.active {
|
#startup-status .status-dot.active {
|
||||||
animation: blink 1s infinite;
|
animation: blink 1s infinite;
|
||||||
@@ -1105,14 +1106,14 @@
|
|||||||
animation: none;
|
animation: none;
|
||||||
}
|
}
|
||||||
#startup-status span {
|
#startup-status span {
|
||||||
color: #fff;
|
color: rgba(255,255,255,0.85);
|
||||||
font-size: 0.85rem;
|
font-size: 0.82rem;
|
||||||
font-weight: 500;
|
font-weight: 500;
|
||||||
letter-spacing: 0.02em;
|
letter-spacing: 0.03em;
|
||||||
}
|
}
|
||||||
@keyframes blink {
|
@keyframes blink {
|
||||||
0%, 100% { opacity: 1; }
|
0%, 100% { opacity: 1; }
|
||||||
50% { opacity: 0.4; }
|
50% { opacity: 0.3; }
|
||||||
}
|
}
|
||||||
|
|
||||||
/* --- UPDATED: Ambient U-Glow --- */
|
/* --- UPDATED: Ambient U-Glow --- */
|
||||||
@@ -1313,8 +1314,9 @@
|
|||||||
.modal-overlay {
|
.modal-overlay {
|
||||||
position: fixed;
|
position: fixed;
|
||||||
inset: 0;
|
inset: 0;
|
||||||
background: var(--color-bg-overlay);
|
background: rgba(0,0,0,0.8);
|
||||||
backdrop-filter: blur(8px);
|
backdrop-filter: blur(12px);
|
||||||
|
-webkit-backdrop-filter: blur(12px);
|
||||||
display: flex;
|
display: flex;
|
||||||
place-items: center;
|
place-items: center;
|
||||||
justify-content: center;
|
justify-content: center;
|
||||||
@@ -1329,33 +1331,37 @@
|
|||||||
pointer-events: auto;
|
pointer-events: auto;
|
||||||
}
|
}
|
||||||
.modal-content {
|
.modal-content {
|
||||||
background: var(--color-bg-modal);
|
background: #1c1c1e;
|
||||||
padding: var(--spacing-lg) var(--spacing-lg);
|
background-image: linear-gradient(145deg, rgba(255,255,255,0.03) 0%, transparent 60%);
|
||||||
border-radius: var(--radius);
|
padding: 28px 28px 24px;
|
||||||
border: 1px solid var(--color-border);
|
border-radius: 18px;
|
||||||
box-shadow: 0 4px 20px rgba(0, 0, 0, 0.5);
|
border: 1px solid rgba(255, 255, 255, 0.08);
|
||||||
|
box-shadow: 0 24px 60px rgba(0, 0, 0, 0.7), 0 0 0 1px rgba(255,255,255,0.04) inset;
|
||||||
text-align: center;
|
text-align: center;
|
||||||
max-width: 28rem;
|
max-width: 28rem;
|
||||||
width: 100%;
|
width: 100%;
|
||||||
transform: scale(0.95);
|
transform: scale(0.94) translateY(12px);
|
||||||
transition: transform var(--transition-medium);
|
transition: transform 0.35s cubic-bezier(0.34, 1.56, 0.64, 1), opacity 0.3s ease;
|
||||||
|
opacity: 0;
|
||||||
}
|
}
|
||||||
.modal-overlay.active .modal-content {
|
.modal-overlay.active .modal-content {
|
||||||
transform: scale(1);
|
transform: scale(1) translateY(0);
|
||||||
|
opacity: 1;
|
||||||
}
|
}
|
||||||
.modal-content h2 {
|
.modal-content h2 {
|
||||||
margin-bottom: var(--spacing);
|
margin-bottom: var(--spacing);
|
||||||
font-size: 1.75rem;
|
font-size: 1.6rem;
|
||||||
font-weight: 400;
|
font-weight: 400;
|
||||||
color: var(--text-primary);
|
color: var(--text-primary);
|
||||||
font-family: "Bitcount Grid Single";
|
font-family: "Bitcount Grid Single";
|
||||||
|
letter-spacing: 0.01em;
|
||||||
}
|
}
|
||||||
.modal-content p {
|
.modal-content p {
|
||||||
margin-bottom: var(--spacing-lg);
|
margin-bottom: var(--spacing-lg);
|
||||||
font-size: 1rem;
|
font-size: 0.95rem;
|
||||||
line-height: 1.6;
|
line-height: 1.65;
|
||||||
color: var(--text-secondary);
|
color: var(--text-secondary);
|
||||||
font-family: Tahoma;
|
font-family: "Inter", sans-serif;
|
||||||
}
|
}
|
||||||
.modal-content a {
|
.modal-content a {
|
||||||
color: var(--accent);
|
color: var(--accent);
|
||||||
@@ -1372,36 +1378,42 @@
|
|||||||
.ip-input-container { margin-bottom: var(--spacing-lg); }
|
.ip-input-container { margin-bottom: var(--spacing-lg); }
|
||||||
.ip-input {
|
.ip-input {
|
||||||
width: 90%;
|
width: 90%;
|
||||||
background: var(--color-bg-input);
|
background: rgba(255,255,255,0.05);
|
||||||
border: 1px solid var(--color-border);
|
border: 1px solid rgba(255,255,255,0.1);
|
||||||
border-radius: var(--radius);
|
border-radius: 10px;
|
||||||
padding: var(--spacing) calc(var(--spacing) * 1.25);
|
padding: var(--spacing) calc(var(--spacing) * 1.25);
|
||||||
font-size: 1.1rem;
|
font-size: 1.05rem;
|
||||||
color: var(--text-primary);
|
color: var(--text-primary);
|
||||||
text-align: center;
|
text-align: center;
|
||||||
outline: none;
|
outline: none;
|
||||||
transition: box-shadow var(--transition-fast), border-color var(--transition-fast);
|
font-family: "Inter", sans-serif;
|
||||||
|
transition: box-shadow var(--transition-fast), border-color var(--transition-fast), background var(--transition-fast);
|
||||||
}
|
}
|
||||||
.ip-input:focus {
|
.ip-input:focus {
|
||||||
border-color: var(--accent);
|
border-color: var(--accent);
|
||||||
box-shadow: 0 0 0 0.25rem var(--accent-fade);
|
background: rgba(255, 149, 0, 0.06);
|
||||||
|
box-shadow: 0 0 0 3px var(--accent-fade);
|
||||||
}
|
}
|
||||||
|
.ip-input::placeholder { color: rgba(255,255,255,0.25); }
|
||||||
|
|
||||||
.connect-button {
|
.connect-button {
|
||||||
display: block;
|
display: block;
|
||||||
width: 100%;
|
width: 100%;
|
||||||
padding: var(--spacing) 0;
|
padding: 14px 0;
|
||||||
background: var(--accent);
|
background: var(--accent);
|
||||||
border: none;
|
border: none;
|
||||||
border-radius: var(--radius);
|
border-radius: 10px;
|
||||||
font-size: 1.1rem;
|
font-size: 1rem;
|
||||||
font-weight: 600;
|
font-weight: 600;
|
||||||
color: #fff;
|
color: #fff;
|
||||||
cursor: pointer;
|
cursor: pointer;
|
||||||
transition: background var(--transition-fast), box-shadow var(--transition-fast);
|
letter-spacing: 0.02em;
|
||||||
|
font-family: "Inter", sans-serif;
|
||||||
|
transition: background var(--transition-fast), box-shadow var(--transition-fast), transform 0.1s;
|
||||||
}
|
}
|
||||||
.connect-button:hover { background: var(--accent-light); }
|
.connect-button:hover { background: var(--accent-light); box-shadow: 0 4px 16px rgba(255,149,0,0.35); }
|
||||||
.connect-button:focus { outline: none; box-shadow: 0 0 0 0.25rem var(--accent-fade); }
|
.connect-button:active { transform: scale(0.98); }
|
||||||
|
.connect-button:focus { outline: none; box-shadow: 0 0 0 3px var(--accent-fade); }
|
||||||
|
|
||||||
.status-indicator {
|
.status-indicator {
|
||||||
margin-top: var(--spacing-sm);
|
margin-top: var(--spacing-sm);
|
||||||
@@ -1577,6 +1589,71 @@
|
|||||||
.toast-message.success .toast-icon { color: #34c759; }
|
.toast-message.success .toast-icon { color: #34c759; }
|
||||||
.toast-message.error .toast-icon { color: #ff3b30; }
|
.toast-message.error .toast-icon { color: #ff3b30; }
|
||||||
.toast-message.info .toast-icon { color: #007aff; }
|
.toast-message.info .toast-icon { color: #007aff; }
|
||||||
|
/* --- TUNNEL WARNING MODAL --- */
|
||||||
|
.tunnel-warning-content {
|
||||||
|
max-width: 30rem !important;
|
||||||
|
text-align: left !important;
|
||||||
|
}
|
||||||
|
.tunnel-warning-icon {
|
||||||
|
width: 52px;
|
||||||
|
height: 52px;
|
||||||
|
border-radius: 14px;
|
||||||
|
background: rgba(255, 149, 0, 0.12);
|
||||||
|
border: 1px solid rgba(255, 149, 0, 0.25);
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
margin-bottom: 18px;
|
||||||
|
}
|
||||||
|
.tunnel-warning-icon i {
|
||||||
|
font-size: 1.5rem;
|
||||||
|
color: var(--accent);
|
||||||
|
}
|
||||||
|
.tunnel-warning-content h2 {
|
||||||
|
text-align: left !important;
|
||||||
|
font-size: 1.4rem !important;
|
||||||
|
margin-bottom: 10px !important;
|
||||||
|
}
|
||||||
|
.tunnel-warning-content p {
|
||||||
|
text-align: left !important;
|
||||||
|
margin-bottom: 10px !important;
|
||||||
|
font-size: 0.95rem !important;
|
||||||
|
}
|
||||||
|
.tunnel-subtext {
|
||||||
|
color: rgba(179, 179, 181, 0.7) !important;
|
||||||
|
font-size: 0.85rem !important;
|
||||||
|
margin-bottom: 20px !important;
|
||||||
|
}
|
||||||
|
.tunnel-warning-actions {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
gap: 10px;
|
||||||
|
margin-top: 6px;
|
||||||
|
}
|
||||||
|
.tunnel-refresh-btn {
|
||||||
|
display: flex !important;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
gap: 8px;
|
||||||
|
margin-bottom: 0 !important;
|
||||||
|
}
|
||||||
|
.tunnel-dismiss-btn {
|
||||||
|
background: transparent;
|
||||||
|
border: 1px solid rgba(255,255,255,0.12);
|
||||||
|
border-radius: var(--radius);
|
||||||
|
color: var(--text-secondary);
|
||||||
|
font-size: 0.95rem;
|
||||||
|
font-weight: 500;
|
||||||
|
padding: var(--spacing) 0;
|
||||||
|
cursor: pointer;
|
||||||
|
transition: background var(--transition-fast), color var(--transition-fast);
|
||||||
|
font-family: "Inter", sans-serif;
|
||||||
|
width: 100%;
|
||||||
|
}
|
||||||
|
.tunnel-dismiss-btn:hover {
|
||||||
|
background: rgba(255,255,255,0.07);
|
||||||
|
color: var(--text-primary);
|
||||||
|
}
|
||||||
</style>
|
</style>
|
||||||
</head>
|
</head>
|
||||||
<!-- Initialize Alpine Global Player State -->
|
<!-- Initialize Alpine Global Player State -->
|
||||||
@@ -1839,6 +1916,30 @@
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
<!-- Tunnel Warning Modal -->
|
||||||
|
<div id="tunnel-warning-modal" class="modal-overlay">
|
||||||
|
<div class="modal-content tunnel-warning-content">
|
||||||
|
<div class="tunnel-warning-icon">
|
||||||
|
<i class="fas fa-plug-circle-exclamation"></i>
|
||||||
|
</div>
|
||||||
|
<h2>Service Disruption</h2>
|
||||||
|
<p>
|
||||||
|
Some features are temporarily unavailable. Our backend service is experiencing connectivity issues — live streaming, search suggestions, and syncing may not work as expected.
|
||||||
|
</p>
|
||||||
|
<p class="tunnel-subtext">
|
||||||
|
This is usually resolved automatically. Try refreshing in a few minutes, or continue browsing with limited functionality.
|
||||||
|
</p>
|
||||||
|
<div class="tunnel-warning-actions">
|
||||||
|
<button id="tunnel-refresh-btn" class="connect-button tunnel-refresh-btn">
|
||||||
|
<i class="fas fa-rotate-right"></i> Refresh Page
|
||||||
|
</button>
|
||||||
|
<button id="tunnel-dismiss-btn" class="tunnel-dismiss-btn">
|
||||||
|
Continue Anyway
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
<!-- Fullscreen Iframe Popup -->
|
<!-- Fullscreen Iframe Popup -->
|
||||||
<div id="iframe-popup-overlay">
|
<div id="iframe-popup-overlay">
|
||||||
<div class="popup-content-wrapper">
|
<div class="popup-content-wrapper">
|
||||||
@@ -2329,6 +2430,10 @@
|
|||||||
setTimeout(() => {
|
setTimeout(() => {
|
||||||
serverModal.classList.remove("active");
|
serverModal.classList.remove("active");
|
||||||
}, 1000);
|
}, 1000);
|
||||||
|
// Check tunnel health
|
||||||
|
if (data.tunnel_active === false) {
|
||||||
|
setTimeout(() => showTunnelWarning(), 1200);
|
||||||
|
}
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -2353,6 +2458,18 @@
|
|||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
function showTunnelWarning() {
|
||||||
|
const modal = document.getElementById("tunnel-warning-modal");
|
||||||
|
if (!modal) return;
|
||||||
|
modal.classList.add("active");
|
||||||
|
document.getElementById("tunnel-refresh-btn").addEventListener("click", () => {
|
||||||
|
window.location.reload();
|
||||||
|
});
|
||||||
|
document.getElementById("tunnel-dismiss-btn").addEventListener("click", () => {
|
||||||
|
modal.classList.remove("active");
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
async function initApp() {
|
async function initApp() {
|
||||||
const isDeployed = window.location.protocol.startsWith("http");
|
const isDeployed = window.location.protocol.startsWith("http");
|
||||||
await delay(500);
|
await delay(500);
|
||||||
|
|||||||
@@ -154,7 +154,6 @@
|
|||||||
background-color: var(--brand-hover);
|
background-color: var(--brand-hover);
|
||||||
transform: translateY(-2px);
|
transform: translateY(-2px);
|
||||||
}
|
}
|
||||||
/* New Find Icon Button */
|
|
||||||
.btn-icon-secondary {
|
.btn-icon-secondary {
|
||||||
display: inline-flex;
|
display: inline-flex;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
@@ -697,7 +696,7 @@
|
|||||||
|
|
||||||
async function loadJikanMetadata() {
|
async function loadJikanMetadata() {
|
||||||
const detRes = await fetch(
|
const detRes = await fetch(
|
||||||
`https://api.jikan.moe/v4/manga/${state.mangaId}/full`,
|
`${serverUrl}/jikan/manga/${state.mangaId}/full`,
|
||||||
);
|
);
|
||||||
if (!detRes.ok) throw new Error("Jikan API Error");
|
if (!detRes.ok) throw new Error("Jikan API Error");
|
||||||
const data = (await detRes.json()).data;
|
const data = (await detRes.json()).data;
|
||||||
@@ -740,7 +739,11 @@
|
|||||||
|
|
||||||
async function loadFusedChapters() {
|
async function loadFusedChapters() {
|
||||||
try {
|
try {
|
||||||
const chRes = await fetch(`${serverUrl}/chapters/${state.mangaId}`);
|
const isFinished = state.mangaDetails && (
|
||||||
|
state.mangaDetails.status.toLowerCase() === "finished" ||
|
||||||
|
state.mangaDetails.status.toLowerCase() === "completed"
|
||||||
|
);
|
||||||
|
const chRes = await fetch(`${serverUrl}/chapters/${state.mangaId}?finished=${isFinished}`);
|
||||||
if (!chRes.ok) return;
|
if (!chRes.ok) return;
|
||||||
const chData = await chRes.json();
|
const chData = await chRes.json();
|
||||||
let modulesMap = chData.modules || {};
|
let modulesMap = chData.modules || {};
|
||||||
@@ -1023,7 +1026,7 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// --- New Logic: Find & Animate ---
|
// --- Logic: Find & Animate ---
|
||||||
function findChapterInList(chapterNum) {
|
function findChapterInList(chapterNum) {
|
||||||
// 1. Find index in currently filtered chapter list
|
// 1. Find index in currently filtered chapter list
|
||||||
const index = state.chapters.findIndex(
|
const index = state.chapters.findIndex(
|
||||||
|
|||||||
1501
animex/manga.html
1501
animex/manga.html
File diff suppressed because it is too large
Load Diff
@@ -838,12 +838,19 @@ body {
|
|||||||
display: none; /* Hide the main title */
|
display: none; /* Hide the main title */
|
||||||
}
|
}
|
||||||
.episode-list.grid-view.thumbnails-hidden .episode-title-romanji {
|
.episode-list.grid-view.thumbnails-hidden .episode-title-romanji {
|
||||||
font-size: 0.9rem; /* Match the desired font size */
|
display: none; /* Hide the "Episode X" romanji text in compact mode */
|
||||||
|
}
|
||||||
|
/* Episode number badge shown only in no-thumbnail tile mode */
|
||||||
|
.episode-list.grid-view.thumbnails-hidden .ep-number-badge {
|
||||||
|
display: block;
|
||||||
|
font-size: 1rem;
|
||||||
|
font-weight: 800;
|
||||||
|
color: var(--text-primary);
|
||||||
margin: 0;
|
margin: 0;
|
||||||
white-space: nowrap;
|
line-height: 1;
|
||||||
display: block; /* Make the romanji title visible */
|
}
|
||||||
color: var(--text-primary); /* Ensure it's visible, not muted */
|
.ep-number-badge {
|
||||||
font-weight: 700; /* Make it bolder */
|
display: none; /* Hidden in all other modes */
|
||||||
}
|
}
|
||||||
.episode-list.grid-view.thumbnails-hidden .episode-progress-wrapper,
|
.episode-list.grid-view.thumbnails-hidden .episode-progress-wrapper,
|
||||||
.episode-list.grid-view.thumbnails-hidden .episode-action-buttons {
|
.episode-list.grid-view.thumbnails-hidden .episode-action-buttons {
|
||||||
@@ -1240,6 +1247,17 @@ body {
|
|||||||
.popup-close:hover { background: #fff; color: #000; transform: rotate(90deg) scale(1.1); }
|
.popup-close:hover { background: #fff; color: #000; transform: rotate(90deg) scale(1.1); }
|
||||||
iframe { width: 100%; height: 100%; border: none; }
|
iframe { width: 100%; height: 100%; border: none; }
|
||||||
|
|
||||||
|
/* Shrink player modal on 1080p / shorter screens */
|
||||||
|
@media (max-height: 900px) and (min-width: 1024px) {
|
||||||
|
.episode-popup-content {
|
||||||
|
width: 88%;
|
||||||
|
max-width: 1200px;
|
||||||
|
aspect-ratio: unset;
|
||||||
|
height: 82vh;
|
||||||
|
max-height: 720px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/* =========================================
|
/* =========================================
|
||||||
12. RX GATE MODAL (PHASE 1 MODERNIZATION)
|
12. RX GATE MODAL (PHASE 1 MODERNIZATION)
|
||||||
========================================= */
|
========================================= */
|
||||||
@@ -1849,6 +1867,7 @@ iframe { width: 100%; height: 100%; border: none; }
|
|||||||
<div class="episode-info">
|
<div class="episode-info">
|
||||||
<div class="episode-title">${ep.attributes.canonicalTitle || 'Episode ' + num}</div>
|
<div class="episode-title">${ep.attributes.canonicalTitle || 'Episode ' + num}</div>
|
||||||
<div class="episode-title-romanji">Episode ${num}</div>
|
<div class="episode-title-romanji">Episode ${num}</div>
|
||||||
|
<span class="ep-number-badge">${num}</span>
|
||||||
${progressHtml}
|
${progressHtml}
|
||||||
</div>
|
</div>
|
||||||
<div class="episode-action-buttons"><i class="fas ${isFinished ? 'fa-rotate-right' : 'fa-play'}"></i></div>
|
<div class="episode-action-buttons"><i class="fas ${isFinished ? 'fa-rotate-right' : 'fa-play'}"></i></div>
|
||||||
|
|||||||
66
app.py
66
app.py
@@ -582,7 +582,6 @@ async def websocket_tunnel(websocket: WebSocket, token: str = Query(...)):
|
|||||||
|
|
||||||
# --- Core Endpoints ---
|
# --- Core Endpoints ---
|
||||||
|
|
||||||
|
|
||||||
# --- MISSING HELPERS & GRAPHQL QUERIES ---
|
# --- MISSING HELPERS & GRAPHQL QUERIES ---
|
||||||
|
|
||||||
def _get_cover_url_from_manga(manga: Dict[str, Any]) -> Optional[str]:
|
def _get_cover_url_from_manga(manga: Dict[str, Any]) -> Optional[str]:
|
||||||
@@ -666,6 +665,36 @@ async def proxy_image(url: str):
|
|||||||
res = await tunnel_request("GET", url)
|
res = await tunnel_request("GET", url)
|
||||||
return Response(content=base64.b64decode(res["body"]), media_type=res["headers"].get("content-type"))
|
return Response(content=base64.b64decode(res["body"]), media_type=res["headers"].get("content-type"))
|
||||||
|
|
||||||
|
|
||||||
|
# --- JIKAN METADATA & CACHING ---
|
||||||
|
@app.get("/jikan/manga/{mal_id}/full")
|
||||||
|
async def get_jikan_manga_full(mal_id: int):
|
||||||
|
cache_key = f"jikan_manga_full_{mal_id}"
|
||||||
|
|
||||||
|
# Default valid 1 day TTL
|
||||||
|
cached = load_named_cache(cache_key, ttl=86400)
|
||||||
|
if not cached:
|
||||||
|
# Check if there's a cache valid within 1 week (For Finished Series)
|
||||||
|
cached_week = load_named_cache(cache_key, ttl=604800)
|
||||||
|
if cached_week and cached_week.get("publishing") is False:
|
||||||
|
cached = cached_week
|
||||||
|
|
||||||
|
if cached:
|
||||||
|
return {"data": cached}
|
||||||
|
|
||||||
|
# Fetch from Jikan via tunnel/proxy if no valid cache
|
||||||
|
url = f"https://api.jikan.moe/v4/manga/{mal_id}/full"
|
||||||
|
r = await hybrid_client.get(url, timeout=15)
|
||||||
|
|
||||||
|
if r.status_code != 200:
|
||||||
|
raise HTTPException(status_code=r.status_code, detail="Jikan API error")
|
||||||
|
|
||||||
|
data = r.json().get("data", {})
|
||||||
|
save_named_cache(cache_key, data)
|
||||||
|
|
||||||
|
return {"data": data}
|
||||||
|
|
||||||
|
|
||||||
# --- MangaDex Tunnel Support ---
|
# --- MangaDex Tunnel Support ---
|
||||||
MANGADEX_API_URL = "https://api.mangadex.org"
|
MANGADEX_API_URL = "https://api.mangadex.org"
|
||||||
|
|
||||||
@@ -705,10 +734,27 @@ async def list_mangadex(
|
|||||||
|
|
||||||
@app.get("/mangadex/manga/{manga_id}")
|
@app.get("/mangadex/manga/{manga_id}")
|
||||||
async def get_mangadex_manga_details(manga_id: str):
|
async def get_mangadex_manga_details(manga_id: str):
|
||||||
|
cache_key = f"mangadex_manga_{manga_id}"
|
||||||
|
|
||||||
|
# Fast access caching
|
||||||
|
cached = load_named_cache(cache_key, ttl=86400)
|
||||||
|
if not cached:
|
||||||
|
cached_week = load_named_cache(cache_key, ttl=604800)
|
||||||
|
# Check MangaDex "status" string
|
||||||
|
if cached_week and cached_week.get("attributes", {}).get("status") in ["completed", "cancelled"]:
|
||||||
|
cached = cached_week
|
||||||
|
|
||||||
|
if cached:
|
||||||
|
if "image_url" not in cached:
|
||||||
|
cached["image_url"] = _get_cover_url_from_manga(cached)
|
||||||
|
return cached
|
||||||
|
|
||||||
url = f"{MANGADEX_API_URL}/manga/{manga_id}?includes[]=cover_art&includes[]=author&includes[]=artist"
|
url = f"{MANGADEX_API_URL}/manga/{manga_id}?includes[]=cover_art&includes[]=author&includes[]=artist"
|
||||||
res = await tunnel_request("GET", url)
|
res = await tunnel_request("GET", url)
|
||||||
data = json.loads(base64.b64decode(res["body"])).get("data")
|
data = json.loads(base64.b64decode(res["body"])).get("data")
|
||||||
if data: data["image_url"] = _get_cover_url_from_manga(data)
|
if data:
|
||||||
|
data["image_url"] = _get_cover_url_from_manga(data)
|
||||||
|
save_named_cache(cache_key, data)
|
||||||
return data
|
return data
|
||||||
|
|
||||||
@app.get("/mangadex/manga/{manga_id}/chapters")
|
@app.get("/mangadex/manga/{manga_id}/chapters")
|
||||||
@@ -991,14 +1037,25 @@ async def get_manga_banner(mal_id: int, cover: bool = False):
|
|||||||
# --- RESTORED MODULE INTERFACING & DOWNLOADS ---
|
# --- RESTORED MODULE INTERFACING & DOWNLOADS ---
|
||||||
|
|
||||||
@app.get("/chapters/{mal_id}")
|
@app.get("/chapters/{mal_id}")
|
||||||
async def get_manga_chapters_module(mal_id: int):
|
async def get_manga_chapters_module(mal_id: str, finished: bool = Query(False)):
|
||||||
|
cache_key = f"manga_chapters_{mal_id}"
|
||||||
|
|
||||||
|
# Condition: 1 week (604800) if finished boolean passed by frontend, else 4 hours (14400)
|
||||||
|
ttl = 604800 if finished else 14400
|
||||||
|
|
||||||
|
cached = load_named_cache(cache_key, ttl=ttl)
|
||||||
|
if cached:
|
||||||
|
return {"modules": cached}
|
||||||
|
|
||||||
all_modules_results = {}
|
all_modules_results = {}
|
||||||
|
|
||||||
for mid, mdata in loaded_modules.items():
|
for mid, mdata in loaded_modules.items():
|
||||||
if module_states.get(mid) and "MANGA_READER" in mdata["info"].get("type",[]):
|
if module_states.get(mid) and "MANGA_READER" in mdata["info"].get("type",[]):
|
||||||
try:
|
try:
|
||||||
mdata["instance"].httpx = hybrid_client
|
mdata["instance"].httpx = hybrid_client
|
||||||
chaps = await mdata["instance"].get_chapters(mal_id)
|
# Standard ID Parsing: Jikan IDs expect INT, UUIDs remain STR for Mangadex
|
||||||
|
parsed_id = int(mal_id) if mal_id.isdigit() else mal_id
|
||||||
|
chaps = await mdata["instance"].get_chapters(parsed_id)
|
||||||
if chaps is not None:
|
if chaps is not None:
|
||||||
all_modules_results[mid] = chaps
|
all_modules_results[mid] = chaps
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
@@ -1006,6 +1063,7 @@ async def get_manga_chapters_module(mal_id: int):
|
|||||||
pass
|
pass
|
||||||
|
|
||||||
if all_modules_results:
|
if all_modules_results:
|
||||||
|
save_named_cache(cache_key, all_modules_results)
|
||||||
return {"modules": all_modules_results}
|
return {"modules": all_modules_results}
|
||||||
|
|
||||||
raise HTTPException(status_code=404, detail="No chapters found from any module")
|
raise HTTPException(status_code=404, detail="No chapters found from any module")
|
||||||
|
|||||||
1
data/cache/generic/2c933bd1b1aafc7da1050f1a5cf204fe.json
vendored
Normal file
1
data/cache/generic/2c933bd1b1aafc7da1050f1a5cf204fe.json
vendored
Normal file
File diff suppressed because one or more lines are too long
1
data/cache/generic/3160d77d6fdf35834068513e2cb2909c.json
vendored
Normal file
1
data/cache/generic/3160d77d6fdf35834068513e2cb2909c.json
vendored
Normal file
File diff suppressed because one or more lines are too long
1
data/cache/generic/67f01f92707418b5916b549ca9fb6c7a.json
vendored
Normal file
1
data/cache/generic/67f01f92707418b5916b549ca9fb6c7a.json
vendored
Normal file
@@ -0,0 +1 @@
|
|||||||
|
{"payload": {"comix": [{"title": "Chapter 6", "url": "1mql9:honki-de-shitemo-ii-desu-ka:3874900", "chapter_number": "6", "is_external": false}, {"title": "Chapter 5", "url": "1mql9:honki-de-shitemo-ii-desu-ka:3874755", "chapter_number": "5", "is_external": false}, {"title": "Chapter 4", "url": "1mql9:honki-de-shitemo-ii-desu-ka:3874561", "chapter_number": "4", "is_external": false}, {"title": "Chapter 3", "url": "1mql9:honki-de-shitemo-ii-desu-ka:3874447", "chapter_number": "3", "is_external": false}, {"title": "Chapter 2", "url": "1mql9:honki-de-shitemo-ii-desu-ka:3874343", "chapter_number": "2", "is_external": false}, {"title": "Chapter 1", "url": "1mql9:honki-de-shitemo-ii-desu-ka:3874222", "chapter_number": "1", "is_external": false}]}, "_timestamp": 1775087084.836826}
|
||||||
1
data/cache/generic/7955a0672b506c835e12bef8f377e94a.json
vendored
Normal file
1
data/cache/generic/7955a0672b506c835e12bef8f377e94a.json
vendored
Normal file
File diff suppressed because one or more lines are too long
1
data/cache/generic/8ee9072364a0fe7c6e577f3583b73e89.json
vendored
Normal file
1
data/cache/generic/8ee9072364a0fe7c6e577f3583b73e89.json
vendored
Normal file
File diff suppressed because one or more lines are too long
1
data/cache/generic/ccf62cf7596be8fbec0186623afea75a.json
vendored
Normal file
1
data/cache/generic/ccf62cf7596be8fbec0186623afea75a.json
vendored
Normal file
@@ -0,0 +1 @@
|
|||||||
|
{"payload": {"entries": [{"anilist_id": 176496, "mal_id": 58567, "title_romaji": "Ore dake Level Up na Ken: Season 2 - Arise from the Shadow", "title_english": "Solo Leveling Season 2 -Arise from the Shadow-", "title_native": "\u4ffa\u3060\u3051\u30ec\u30d9\u30eb\u30a2\u30c3\u30d7\u306a\u4ef6 Season 2 -Arise from the Shadow-", "format": "TV", "start_date": {"year": 2025, "month": 1, "day": 5}, "season": "WINTER", "season_year": 2025, "episodes": 13, "relation_type_from_parent": null, "inferred_part": 2, "is_season": true, "is_final_from_title": false, "title_display": "Solo Leveling Season 2 -Arise from the Shadow-", "_start_tuple": [2025, 1, 5], "is_final_season": false}, {"anilist_id": 184694, "mal_id": 59841, "title_romaji": "Ore dake Level Up na Ken: ReAwakening", "title_english": "Solo Leveling -ReAwakening-", "title_native": "\u4ffa\u3060\u3051\u30ec\u30d9\u30eb\u30a2\u30c3\u30d7\u306a\u4ef6 -ReAwakening-", "format": "MOVIE", "start_date": {"year": 2024, "month": 11, "day": 29}, "season": "FALL", "season_year": 2024, "episodes": 1, "relation_type_from_parent": null, "inferred_part": null, "is_season": false, "is_final_from_title": false, "title_display": "Solo Leveling -ReAwakening-", "_start_tuple": [2024, 11, 29], "is_final_season": false}, {"anilist_id": 151807, "mal_id": 52299, "title_romaji": "Ore dake Level Up na Ken", "title_english": "Solo Leveling", "title_native": "\u4ffa\u3060\u3051\u30ec\u30d9\u30eb\u30a2\u30c3\u30d7\u306a\u4ef6", "format": "TV", "start_date": {"year": 2024, "month": 1, "day": 7}, "season": "WINTER", "season_year": 2024, "episodes": 12, "relation_type_from_parent": null, "inferred_part": null, "is_season": true, "is_final_from_title": false, "title_display": "Solo Leveling", "_start_tuple": [2024, 1, 7], "is_final_season": false}], "season_groups": [{"season_index": 1, "group_label": "Season 1", "parts": [{"short_label": "S1", "title": "Solo Leveling", "mal_id": 52299, "anilist_id": 151807, "start_date": {"year": 2024, "month": 1, "day": 7}, "format": "TV", "is_final": false}]}, {"season_index": 2, "group_label": "Season 2", "parts": [{"short_label": "S2", "title": "Solo Leveling Season 2 -Arise from the Shadow-", "mal_id": 58567, "anilist_id": 176496, "start_date": {"year": 2025, "month": 1, "day": 5}, "format": "TV", "is_final": false}]}], "extras": [{"title": "Solo Leveling -ReAwakening-", "mal_id": 59841, "anilist_id": 184694, "format": "MOVIE", "start_date": {"year": 2024, "month": 11, "day": 29}}]}, "_timestamp": 1775086915.503556}
|
||||||
1
data/cache/generic/fd08dd3d86fef10b776a89ee8f362c5d.json
vendored
Normal file
1
data/cache/generic/fd08dd3d86fef10b776a89ee8f362c5d.json
vendored
Normal file
@@ -0,0 +1 @@
|
|||||||
|
{"payload": {"mal_id": 121948, "url": "https://myanimelist.net/manga/121948/Dokusen_shitemo_Ii_desu_ka", "images": {"jpg": {"image_url": "https://myanimelist.net/images/manga/2/235219.jpg", "small_image_url": "https://myanimelist.net/images/manga/2/235219t.jpg", "large_image_url": "https://myanimelist.net/images/manga/2/235219l.jpg"}, "webp": {"image_url": "https://myanimelist.net/images/manga/2/235219.webp", "small_image_url": "https://myanimelist.net/images/manga/2/235219t.webp", "large_image_url": "https://myanimelist.net/images/manga/2/235219l.webp"}}, "approved": true, "titles": [{"type": "Default", "title": "Dokusen shitemo Ii desu ka"}, {"type": "Synonym", "title": "Will You Be Mine?"}, {"type": "Japanese", "title": "\u72ec\u5360\u3057\u3066\u3082\u3044\u3044\u3067\u3059\u304b"}, {"type": "English", "title": "Can I Have You All to Myself?"}], "title": "Dokusen shitemo Ii desu ka", "title_english": "Can I Have You All to Myself?", "title_japanese": "\u72ec\u5360\u3057\u3066\u3082\u3044\u3044\u3067\u3059\u304b", "title_synonyms": ["Will You Be Mine?"], "type": "Manga", "chapters": 7, "volumes": 1, "status": "Finished", "publishing": false, "published": {"from": "2017-04-28T00:00:00+00:00", "to": "2017-10-31T00:00:00+00:00", "prop": {"from": {"day": 28, "month": 4, "year": 2017}, "to": {"day": 31, "month": 10, "year": 2017}}, "string": "Apr 28, 2017 to Oct 31, 2017"}, "score": 6.74, "scored": 6.74, "scored_by": 136, "rank": null, "popularity": 32965, "members": 333, "favorites": 0, "synopsis": "Yuu and Ryou have been best friends since they were kids. Yuu is not the most approachable guy, unlike Ryou, who is very popular. It doesn't really bother Yuu that he's the outcast of the class, and he even admits that Ryou is the only person he feels comfortable talking to. But then, Ryou is asked to substitute for a player on the basketball team, meaning that he is not able to spend as much time with Yuu... Yuu is unhappy about this, and he cannot help but lash out at Ryou. Could Yuu's extremely possessive feelings be something more? Is their relationship really just about friendship?\n\n(Source: Renta!)", "background": "Dokusen shitemo Ii desu ka was published digitally in English as Can I Have You All to Myself? by Renta!", "authors": [{"mal_id": 52232, "type": "people", "name": "Kogamo", "url": "https://myanimelist.net/people/52232/Kogamo"}], "serializations": [], "genres": [{"mal_id": 28, "type": "manga", "name": "Boys Love", "url": "https://myanimelist.net/manga/genre/28/Boys_Love"}, {"mal_id": 49, "type": "manga", "name": "Erotica", "url": "https://myanimelist.net/manga/genre/49/Erotica"}], "explicit_genres": [], "themes": [{"mal_id": 23, "type": "manga", "name": "School", "url": "https://myanimelist.net/manga/genre/23/School"}], "demographics": [], "relations": [], "external": []}, "_timestamp": 1775087082.701801}
|
||||||
Reference in New Issue
Block a user