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.gstatic.com" crossorigin />
|
||||
<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"
|
||||
/>
|
||||
<link
|
||||
@@ -19,20 +19,22 @@
|
||||
/>
|
||||
<style>
|
||||
:root {
|
||||
--accent-color: #ff9500; /* Distinct Orange */
|
||||
--background-color: #121212;
|
||||
--background-color-secondary: #1e1e1e;
|
||||
--accent-color: #ff9500;
|
||||
--accent-glow: rgba(255, 149, 0, 0.35);
|
||||
--background-color: #0e0e0e;
|
||||
--background-color-secondary: #181818;
|
||||
--surface: #1c1c1c;
|
||||
--text-primary: #ffffff;
|
||||
--text-secondary: #aaaaaa;
|
||||
--border-color: rgba(255, 255, 255, 0.15);
|
||||
--text-secondary: #999999;
|
||||
--border-color: rgba(255, 255, 255, 0.08);
|
||||
--shadow-medium: 0 10px 30px rgba(0, 0, 0, 0.5);
|
||||
--shadow-heavy: 0 20px 50px rgba(0, 0, 0, 0.7);
|
||||
--transition-smooth: cubic-bezier(0.25, 0.46, 0.45, 0.94);
|
||||
--radius-card: 14px;
|
||||
}
|
||||
|
||||
body {
|
||||
font-family: "Inter", -apple-system, BlinkMacSystemFont, "Segoe UI",
|
||||
Roboto, sans-serif;
|
||||
font-family: "Outfit", -apple-system, BlinkMacSystemFont, sans-serif;
|
||||
background-color: var(--background-color);
|
||||
margin: 0;
|
||||
color: var(--text-primary);
|
||||
@@ -44,17 +46,17 @@
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
/* --- Liquid Glass Navbar --- */
|
||||
/* --- Navbar --- */
|
||||
.navbar {
|
||||
position: fixed;
|
||||
top: 0;
|
||||
left: 0;
|
||||
width: 100%;
|
||||
z-index: 1000;
|
||||
padding: 20px 6%;
|
||||
padding: 22px 6%;
|
||||
background: linear-gradient(
|
||||
to bottom,
|
||||
rgba(0, 0, 0, 0.8) 0%,
|
||||
rgba(0, 0, 0, 0.85) 0%,
|
||||
transparent 100%
|
||||
);
|
||||
backdrop-filter: blur(0px);
|
||||
@@ -68,19 +70,20 @@
|
||||
}
|
||||
|
||||
.navbar.scrolled {
|
||||
background: rgba(18, 18, 18, 0.85);
|
||||
backdrop-filter: blur(16px);
|
||||
-webkit-backdrop-filter: blur(16px);
|
||||
border-bottom: 1px solid rgba(255, 255, 255, 0.05);
|
||||
padding: 15px 6%;
|
||||
background: rgba(14, 14, 14, 0.92);
|
||||
backdrop-filter: blur(20px);
|
||||
-webkit-backdrop-filter: blur(20px);
|
||||
border-bottom: 1px solid rgba(255, 149, 0, 0.08);
|
||||
padding: 14px 6%;
|
||||
box-shadow: 0 1px 0 rgba(255,255,255,0.04);
|
||||
}
|
||||
|
||||
.navbar svg {
|
||||
display: block;
|
||||
margin: 0;
|
||||
height: 40px;
|
||||
height: 38px;
|
||||
width: auto;
|
||||
opacity: 0.9;
|
||||
opacity: 0.92;
|
||||
transition: opacity 0.4s ease-out;
|
||||
}
|
||||
|
||||
@@ -91,8 +94,8 @@
|
||||
/* --- Hero Section Cinematic Carousel --- */
|
||||
#hero-section {
|
||||
position: relative;
|
||||
height: 65vh;
|
||||
min-height: 600px;
|
||||
height: 68vh;
|
||||
min-height: 580px;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
justify-content: center;
|
||||
@@ -197,39 +200,41 @@
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
gap: 12px;
|
||||
padding: 14px 32px;
|
||||
border-radius: 8px;
|
||||
gap: 10px;
|
||||
padding: 13px 30px;
|
||||
border-radius: 10px;
|
||||
font-family: "Outfit", sans-serif;
|
||||
font-weight: 700;
|
||||
text-decoration: none;
|
||||
font-size: 1.1rem;
|
||||
font-size: 1rem;
|
||||
cursor: pointer;
|
||||
transition: all 0.3s var(--transition-smooth);
|
||||
border: 1px solid transparent;
|
||||
letter-spacing: 0.02em;
|
||||
}
|
||||
|
||||
.hero-btn.play-btn {
|
||||
background-color: #ff9500;
|
||||
background-color: var(--accent-color);
|
||||
color: #fff;
|
||||
box-shadow: 0 4px 20px var(--accent-glow);
|
||||
}
|
||||
|
||||
.hero-btn.play-btn:hover {
|
||||
transform: scale(1.05);
|
||||
background-color: var(--accent-color);
|
||||
color: #fff;
|
||||
box-shadow: 0 0 30px rgba(255, 149, 0, 0.4);
|
||||
transform: scale(1.05) translateY(-1px);
|
||||
box-shadow: 0 8px 32px rgba(255, 149, 0, 0.55);
|
||||
}
|
||||
|
||||
.hero-btn.info-btn {
|
||||
background-color: rgba(100, 100, 100, 0.4);
|
||||
background-color: rgba(255, 255, 255, 0.1);
|
||||
color: #ffffff;
|
||||
backdrop-filter: blur(10px);
|
||||
backdrop-filter: blur(12px);
|
||||
border-color: rgba(255, 255, 255, 0.18);
|
||||
}
|
||||
|
||||
.hero-btn.info-btn:hover {
|
||||
background-color: rgba(255, 255, 255, 0.2);
|
||||
border-color: rgba(255, 255, 255, 0.5);
|
||||
transform: scale(1.05);
|
||||
background-color: rgba(255, 255, 255, 0.18);
|
||||
border-color: rgba(255, 255, 255, 0.4);
|
||||
transform: scale(1.05) translateY(-1px);
|
||||
}
|
||||
|
||||
.hero-dots {
|
||||
@@ -266,13 +271,13 @@
|
||||
@media (max-width: 768px) {
|
||||
.navbar {
|
||||
justify-content: center;
|
||||
padding: 15px 6%;
|
||||
padding: 14px 6%;
|
||||
display: none;
|
||||
}
|
||||
|
||||
#hero-section {
|
||||
height: 55vh;
|
||||
min-height: 400px;
|
||||
height: 58vh;
|
||||
min-height: 380px;
|
||||
}
|
||||
|
||||
.hero-content {
|
||||
@@ -283,25 +288,25 @@
|
||||
|
||||
.hero-logo {
|
||||
object-position: center bottom;
|
||||
max-width: 80%;
|
||||
margin-bottom: 20px;
|
||||
max-width: 78%;
|
||||
margin-bottom: 16px;
|
||||
}
|
||||
|
||||
.hero-buttons {
|
||||
margin-top: -10px;
|
||||
margin-top: -6px;
|
||||
}
|
||||
|
||||
.hero-background::after {
|
||||
background: radial-gradient(
|
||||
circle at 50% 35%,
|
||||
transparent 0%,
|
||||
rgba(18, 18, 18, 0.2) 40%,
|
||||
rgba(14, 14, 14, 0.2) 40%,
|
||||
var(--background-color) 120%
|
||||
),
|
||||
linear-gradient(
|
||||
to right,
|
||||
var(--background-color) 0%,
|
||||
rgba(18, 18, 18, 0.4) 50%,
|
||||
rgba(14, 14, 14, 0.4) 50%,
|
||||
transparent 100%
|
||||
),
|
||||
linear-gradient(to top, var(--background-color) 0%, transparent 40%);
|
||||
@@ -311,40 +316,38 @@
|
||||
display: none;
|
||||
}
|
||||
|
||||
/* --- UPDATED MOBILE CARD SIZES --- */
|
||||
.horizontal-scroll-container {
|
||||
gap: 12px !important; /* Tighter gap so more fit on screen */
|
||||
gap: 10px !important;
|
||||
margin-bottom: -40px;
|
||||
}
|
||||
|
||||
.poster-container {
|
||||
max-width: 150px; /* Reduced Width (Fits ~3-4 cards per row) */
|
||||
max-width: 140px;
|
||||
}
|
||||
|
||||
.poster-title {
|
||||
font-size: 0.8rem; /* Smaller Font */
|
||||
margin-top: 8px;
|
||||
font-size: 0.78rem;
|
||||
margin-top: 7px;
|
||||
}
|
||||
|
||||
.episode-indicator {
|
||||
font-size: 0.65rem; /* Smaller badge */
|
||||
padding: 2px 6px;
|
||||
top: 6px;
|
||||
right: 6px;
|
||||
font-size: 0.62rem;
|
||||
padding: 2px 5px;
|
||||
top: 5px;
|
||||
right: 5px;
|
||||
}
|
||||
|
||||
.poster-image-wrapper {
|
||||
max-width: 150px;
|
||||
max-width: 140px;
|
||||
}
|
||||
|
||||
.poster-info-btn {
|
||||
width: 28px;
|
||||
height: 28px;
|
||||
font-size: 0.8rem;
|
||||
top: 6px;
|
||||
left: 6px;
|
||||
width: 26px;
|
||||
height: 26px;
|
||||
font-size: 0.75rem;
|
||||
top: 5px;
|
||||
left: 5px;
|
||||
}
|
||||
/* -------------------------------- */
|
||||
|
||||
.hero-buttons {
|
||||
justify-content: center;
|
||||
@@ -356,6 +359,31 @@
|
||||
transform: translateX(-50%) translateY(-450%);
|
||||
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 {
|
||||
@@ -379,33 +407,36 @@
|
||||
|
||||
/* --- Content Sections --- */
|
||||
.content-section {
|
||||
margin-bottom: 50px;
|
||||
margin-bottom: 48px;
|
||||
padding: 0 0 0 6%;
|
||||
position: relative;
|
||||
z-index: 5;
|
||||
}
|
||||
|
||||
.content-section:first-of-type {
|
||||
margin-top: -60px;
|
||||
margin-top: -70px;
|
||||
}
|
||||
|
||||
.section-title {
|
||||
font-size: 1.5rem;
|
||||
font-size: 1.35rem;
|
||||
font-weight: 700;
|
||||
letter-spacing: 0.01em;
|
||||
color: var(--text-primary);
|
||||
margin-bottom: 25px;
|
||||
margin-bottom: 20px;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 10px;
|
||||
gap: 12px;
|
||||
font-family: "Outfit", sans-serif;
|
||||
}
|
||||
|
||||
.section-title::before {
|
||||
content: "";
|
||||
display: block;
|
||||
width: 4px;
|
||||
height: 24px;
|
||||
background-color: var(--accent-color);
|
||||
border-radius: 2px;
|
||||
width: 3px;
|
||||
height: 20px;
|
||||
background: linear-gradient(to bottom, var(--accent-color), rgba(255,149,0,0.3));
|
||||
border-radius: 3px;
|
||||
flex-shrink: 0;
|
||||
}
|
||||
|
||||
.horizontal-scroll-container {
|
||||
@@ -434,28 +465,28 @@
|
||||
/* --- Poster Cards --- */
|
||||
.poster-container {
|
||||
flex-shrink: 0;
|
||||
width: 220px; /* Desktop Width */
|
||||
width: 200px;
|
||||
position: relative;
|
||||
transition: z-index 0.3s, transform 0.3s;
|
||||
}
|
||||
|
||||
.poster-image-wrapper {
|
||||
position: relative;
|
||||
border-radius: 12px;
|
||||
border-radius: var(--radius-card);
|
||||
overflow: hidden;
|
||||
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),
|
||||
box-shadow 0.4s var(--transition-smooth), border-color 0.3s;
|
||||
border: 2px solid transparent;
|
||||
background-color: #2a2a2a;
|
||||
border: 1.5px solid transparent;
|
||||
background-color: var(--surface);
|
||||
}
|
||||
|
||||
.poster-image-wrapper img {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
object-fit: cover;
|
||||
transition: filter 0.3s;
|
||||
transition: filter 0.3s, transform 0.4s var(--transition-smooth);
|
||||
}
|
||||
|
||||
.poster-container:hover {
|
||||
@@ -463,28 +494,31 @@
|
||||
}
|
||||
|
||||
.poster-container:hover .poster-image-wrapper {
|
||||
transform: scale(1) translateY(-5px);
|
||||
box-shadow: 0 15px 40px rgba(0, 0, 0, 0.6);
|
||||
border-color: rgba(255, 255, 255, 0.2);
|
||||
transform: scale(1) translateY(-6px);
|
||||
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, 149, 0, 0.2);
|
||||
}
|
||||
|
||||
.poster-container:hover .poster-image-wrapper img {
|
||||
transform: scale(1.03);
|
||||
}
|
||||
|
||||
.poster-title {
|
||||
margin-top: 15px;
|
||||
font-size: 1rem;
|
||||
margin-top: 12px;
|
||||
font-size: 0.88rem;
|
||||
font-weight: 600;
|
||||
color: var(--text-secondary);
|
||||
white-space: nowrap;
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
text-align: center;
|
||||
opacity: 0.8;
|
||||
transition: color 0.3s, opacity 0.3s;
|
||||
padding: 0 5px;
|
||||
transition: color 0.3s;
|
||||
padding: 0 4px;
|
||||
font-family: "DM Sans", sans-serif;
|
||||
}
|
||||
|
||||
.poster-container:hover .poster-title {
|
||||
color: var(--text-primary);
|
||||
opacity: 1;
|
||||
}
|
||||
|
||||
.progress-bar {
|
||||
@@ -492,53 +526,56 @@
|
||||
bottom: 0;
|
||||
left: 0;
|
||||
width: 100%;
|
||||
height: 4px;
|
||||
background-color: rgba(255, 255, 255, 0.2);
|
||||
height: 3px;
|
||||
background-color: rgba(255, 255, 255, 0.15);
|
||||
z-index: 2;
|
||||
}
|
||||
|
||||
.progress-bar-inner {
|
||||
height: 100%;
|
||||
background-color: var(--accent-color);
|
||||
box-shadow: 0 0 10px var(--accent-color);
|
||||
background: linear-gradient(to right, var(--accent-color), #ffb340);
|
||||
box-shadow: 0 0 8px var(--accent-glow);
|
||||
border-radius: 0 2px 2px 0;
|
||||
}
|
||||
|
||||
.episode-indicator {
|
||||
position: absolute;
|
||||
top: 10px;
|
||||
right: 10px;
|
||||
top: 8px;
|
||||
right: 8px;
|
||||
left: auto;
|
||||
background-color: rgba(0, 0, 0, 0.6);
|
||||
background-color: rgba(0, 0, 0, 0.72);
|
||||
color: #fff;
|
||||
padding: 4px 10px;
|
||||
border-radius: 4px;
|
||||
font-size: 0.75rem;
|
||||
padding: 3px 8px;
|
||||
border-radius: 6px;
|
||||
font-size: 0.7rem;
|
||||
font-weight: 700;
|
||||
z-index: 2;
|
||||
backdrop-filter: blur(4px);
|
||||
border: 1px solid rgba(255, 255, 255, 0.1);
|
||||
letter-spacing: 0.5px;
|
||||
backdrop-filter: blur(6px);
|
||||
border: 1px solid rgba(255, 255, 255, 0.08);
|
||||
letter-spacing: 0.4px;
|
||||
font-family: "Outfit", sans-serif;
|
||||
}
|
||||
|
||||
.poster-info-btn {
|
||||
position: absolute;
|
||||
top: 10px;
|
||||
left: 10px;
|
||||
top: 8px;
|
||||
left: 8px;
|
||||
right: auto;
|
||||
z-index: 3;
|
||||
background-color: rgba(30, 30, 30, 0.8);
|
||||
background-color: rgba(20, 20, 20, 0.82);
|
||||
color: #fff;
|
||||
width: 32px;
|
||||
height: 32px;
|
||||
width: 30px;
|
||||
height: 30px;
|
||||
border-radius: 50%;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
opacity: 0;
|
||||
transform: translateY(-10px);
|
||||
transform: translateY(-8px);
|
||||
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;
|
||||
font-size: 0.8rem;
|
||||
}
|
||||
|
||||
.poster-container:hover .poster-info-btn {
|
||||
@@ -547,8 +584,9 @@
|
||||
}
|
||||
|
||||
.poster-info-btn:hover {
|
||||
background-color: #fff;
|
||||
color: #000;
|
||||
background-color: var(--accent-color);
|
||||
border-color: var(--accent-color);
|
||||
color: #fff;
|
||||
}
|
||||
|
||||
/* --- Context Menu --- */
|
||||
@@ -573,12 +611,12 @@
|
||||
}
|
||||
|
||||
.context-menu {
|
||||
width: 380px;
|
||||
width: 340px;
|
||||
max-width: 90%;
|
||||
background-color: #1e1e1e;
|
||||
border-radius: 16px;
|
||||
box-shadow: var(--shadow-heavy);
|
||||
border: 1px solid rgba(255, 255, 255, 0.1);
|
||||
background-color: #1c1c1c;
|
||||
border-radius: 18px;
|
||||
box-shadow: var(--shadow-heavy), 0 0 0 1px rgba(255,255,255,0.06);
|
||||
border: 1px solid rgba(255, 255, 255, 0.08);
|
||||
transform: scale(0.95) translateY(20px);
|
||||
transition: transform 0.3s cubic-bezier(0.175, 0.885, 0.32, 1.275);
|
||||
overflow: hidden;
|
||||
|
||||
@@ -1083,7 +1083,7 @@
|
||||
opacity: 0;
|
||||
transform: translateY(20px);
|
||||
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 {
|
||||
opacity: 1;
|
||||
@@ -1095,6 +1095,7 @@
|
||||
border-radius: 50%;
|
||||
background: #ff9500;
|
||||
box-shadow: 0 0 10px #ff9500;
|
||||
flex-shrink: 0;
|
||||
}
|
||||
#startup-status .status-dot.active {
|
||||
animation: blink 1s infinite;
|
||||
@@ -1105,14 +1106,14 @@
|
||||
animation: none;
|
||||
}
|
||||
#startup-status span {
|
||||
color: #fff;
|
||||
font-size: 0.85rem;
|
||||
color: rgba(255,255,255,0.85);
|
||||
font-size: 0.82rem;
|
||||
font-weight: 500;
|
||||
letter-spacing: 0.02em;
|
||||
letter-spacing: 0.03em;
|
||||
}
|
||||
@keyframes blink {
|
||||
0%, 100% { opacity: 1; }
|
||||
50% { opacity: 0.4; }
|
||||
50% { opacity: 0.3; }
|
||||
}
|
||||
|
||||
/* --- UPDATED: Ambient U-Glow --- */
|
||||
@@ -1313,8 +1314,9 @@
|
||||
.modal-overlay {
|
||||
position: fixed;
|
||||
inset: 0;
|
||||
background: var(--color-bg-overlay);
|
||||
backdrop-filter: blur(8px);
|
||||
background: rgba(0,0,0,0.8);
|
||||
backdrop-filter: blur(12px);
|
||||
-webkit-backdrop-filter: blur(12px);
|
||||
display: flex;
|
||||
place-items: center;
|
||||
justify-content: center;
|
||||
@@ -1329,33 +1331,37 @@
|
||||
pointer-events: auto;
|
||||
}
|
||||
.modal-content {
|
||||
background: var(--color-bg-modal);
|
||||
padding: var(--spacing-lg) var(--spacing-lg);
|
||||
border-radius: var(--radius);
|
||||
border: 1px solid var(--color-border);
|
||||
box-shadow: 0 4px 20px rgba(0, 0, 0, 0.5);
|
||||
background: #1c1c1e;
|
||||
background-image: linear-gradient(145deg, rgba(255,255,255,0.03) 0%, transparent 60%);
|
||||
padding: 28px 28px 24px;
|
||||
border-radius: 18px;
|
||||
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;
|
||||
max-width: 28rem;
|
||||
width: 100%;
|
||||
transform: scale(0.95);
|
||||
transition: transform var(--transition-medium);
|
||||
transform: scale(0.94) translateY(12px);
|
||||
transition: transform 0.35s cubic-bezier(0.34, 1.56, 0.64, 1), opacity 0.3s ease;
|
||||
opacity: 0;
|
||||
}
|
||||
.modal-overlay.active .modal-content {
|
||||
transform: scale(1);
|
||||
transform: scale(1) translateY(0);
|
||||
opacity: 1;
|
||||
}
|
||||
.modal-content h2 {
|
||||
margin-bottom: var(--spacing);
|
||||
font-size: 1.75rem;
|
||||
font-size: 1.6rem;
|
||||
font-weight: 400;
|
||||
color: var(--text-primary);
|
||||
font-family: "Bitcount Grid Single";
|
||||
letter-spacing: 0.01em;
|
||||
}
|
||||
.modal-content p {
|
||||
margin-bottom: var(--spacing-lg);
|
||||
font-size: 1rem;
|
||||
line-height: 1.6;
|
||||
font-size: 0.95rem;
|
||||
line-height: 1.65;
|
||||
color: var(--text-secondary);
|
||||
font-family: Tahoma;
|
||||
font-family: "Inter", sans-serif;
|
||||
}
|
||||
.modal-content a {
|
||||
color: var(--accent);
|
||||
@@ -1372,36 +1378,42 @@
|
||||
.ip-input-container { margin-bottom: var(--spacing-lg); }
|
||||
.ip-input {
|
||||
width: 90%;
|
||||
background: var(--color-bg-input);
|
||||
border: 1px solid var(--color-border);
|
||||
border-radius: var(--radius);
|
||||
background: rgba(255,255,255,0.05);
|
||||
border: 1px solid rgba(255,255,255,0.1);
|
||||
border-radius: 10px;
|
||||
padding: var(--spacing) calc(var(--spacing) * 1.25);
|
||||
font-size: 1.1rem;
|
||||
font-size: 1.05rem;
|
||||
color: var(--text-primary);
|
||||
text-align: center;
|
||||
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 {
|
||||
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 {
|
||||
display: block;
|
||||
width: 100%;
|
||||
padding: var(--spacing) 0;
|
||||
padding: 14px 0;
|
||||
background: var(--accent);
|
||||
border: none;
|
||||
border-radius: var(--radius);
|
||||
font-size: 1.1rem;
|
||||
border-radius: 10px;
|
||||
font-size: 1rem;
|
||||
font-weight: 600;
|
||||
color: #fff;
|
||||
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:focus { outline: none; box-shadow: 0 0 0 0.25rem var(--accent-fade); }
|
||||
.connect-button:hover { background: var(--accent-light); box-shadow: 0 4px 16px rgba(255,149,0,0.35); }
|
||||
.connect-button:active { transform: scale(0.98); }
|
||||
.connect-button:focus { outline: none; box-shadow: 0 0 0 3px var(--accent-fade); }
|
||||
|
||||
.status-indicator {
|
||||
margin-top: var(--spacing-sm);
|
||||
@@ -1577,6 +1589,71 @@
|
||||
.toast-message.success .toast-icon { color: #34c759; }
|
||||
.toast-message.error .toast-icon { color: #ff3b30; }
|
||||
.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>
|
||||
</head>
|
||||
<!-- Initialize Alpine Global Player State -->
|
||||
@@ -1839,6 +1916,30 @@
|
||||
</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 -->
|
||||
<div id="iframe-popup-overlay">
|
||||
<div class="popup-content-wrapper">
|
||||
@@ -2329,6 +2430,10 @@
|
||||
setTimeout(() => {
|
||||
serverModal.classList.remove("active");
|
||||
}, 1000);
|
||||
// Check tunnel health
|
||||
if (data.tunnel_active === false) {
|
||||
setTimeout(() => showTunnelWarning(), 1200);
|
||||
}
|
||||
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() {
|
||||
const isDeployed = window.location.protocol.startsWith("http");
|
||||
await delay(500);
|
||||
|
||||
@@ -154,7 +154,6 @@
|
||||
background-color: var(--brand-hover);
|
||||
transform: translateY(-2px);
|
||||
}
|
||||
/* New Find Icon Button */
|
||||
.btn-icon-secondary {
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
@@ -697,7 +696,7 @@
|
||||
|
||||
async function loadJikanMetadata() {
|
||||
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");
|
||||
const data = (await detRes.json()).data;
|
||||
@@ -740,7 +739,11 @@
|
||||
|
||||
async function loadFusedChapters() {
|
||||
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;
|
||||
const chData = await chRes.json();
|
||||
let modulesMap = chData.modules || {};
|
||||
@@ -1023,7 +1026,7 @@
|
||||
}
|
||||
}
|
||||
|
||||
// --- New Logic: Find & Animate ---
|
||||
// --- Logic: Find & Animate ---
|
||||
function findChapterInList(chapterNum) {
|
||||
// 1. Find index in currently filtered chapter list
|
||||
const index = state.chapters.findIndex(
|
||||
|
||||
1391
animex/manga.html
1391
animex/manga.html
File diff suppressed because it is too large
Load Diff
@@ -838,12 +838,19 @@ body {
|
||||
display: none; /* Hide the main title */
|
||||
}
|
||||
.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;
|
||||
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 */
|
||||
line-height: 1;
|
||||
}
|
||||
.ep-number-badge {
|
||||
display: none; /* Hidden in all other modes */
|
||||
}
|
||||
.episode-list.grid-view.thumbnails-hidden .episode-progress-wrapper,
|
||||
.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); }
|
||||
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)
|
||||
========================================= */
|
||||
@@ -1849,6 +1867,7 @@ iframe { width: 100%; height: 100%; border: none; }
|
||||
<div class="episode-info">
|
||||
<div class="episode-title">${ep.attributes.canonicalTitle || 'Episode ' + num}</div>
|
||||
<div class="episode-title-romanji">Episode ${num}</div>
|
||||
<span class="ep-number-badge">${num}</span>
|
||||
${progressHtml}
|
||||
</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 ---
|
||||
|
||||
|
||||
# --- MISSING HELPERS & GRAPHQL QUERIES ---
|
||||
|
||||
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)
|
||||
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_API_URL = "https://api.mangadex.org"
|
||||
|
||||
@@ -705,10 +734,27 @@ async def list_mangadex(
|
||||
|
||||
@app.get("/mangadex/manga/{manga_id}")
|
||||
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"
|
||||
res = await tunnel_request("GET", url)
|
||||
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
|
||||
|
||||
@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 ---
|
||||
|
||||
@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 = {}
|
||||
|
||||
for mid, mdata in loaded_modules.items():
|
||||
if module_states.get(mid) and "MANGA_READER" in mdata["info"].get("type",[]):
|
||||
try:
|
||||
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:
|
||||
all_modules_results[mid] = chaps
|
||||
except Exception as e:
|
||||
@@ -1006,6 +1063,7 @@ async def get_manga_chapters_module(mal_id: int):
|
||||
pass
|
||||
|
||||
if all_modules_results:
|
||||
save_named_cache(cache_key, all_modules_results)
|
||||
return {"modules": all_modules_results}
|
||||
|
||||
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