next-ep popup: design A (Cinematic Strip) shipped to dev + designs A/B/C archived

Adds web-overrides/popup-designs/ with the 4-up preview (preview.html) and
three standalone candidate designs (a-strip.html / b-terminal.html /
c-minimal.html) so we can revisit the alternates later without re-running
the design generator. Owner picked A.

Design A is wired into Jellyfin's stock .upNextDialog by overriding its
CSS to a full-bleed bottom 26 % strip with white 'Start Now' CTA and a
custom SVG countdown ring that mirrors .upNextDialog-countdownText. The
DOM stays intact so Jellyfin's own countdown timer and click handlers on
.btnStartNow / .btnHide keep working untouched.

Shim is bracketed by NEXT-EP-POPUP-BEGIN / NEXT-EP-POPUP-END markers
inside the existing ARRFLIX-SHIM block in web-overrides/index-dev.html.
Only deployed to dev (dev.arrflix.s8n.ru) for spot-check; promote to
prod once verified by editing prod's index.html the same way and
redeploying via the nsenter trick.

Adds bin/revert-next-ep-popup.sh — sed-deletes between markers, defaults
to dev with --prod flag for prod target. Saves a timestamped backup and
prints the redeploy command.
This commit is contained in:
s8n 2026-05-10 02:44:30 +01:00
parent 24a9497e7d
commit 508fc42a1e
7 changed files with 1704 additions and 0 deletions

54
bin/revert-next-ep-popup.sh Executable file
View file

@ -0,0 +1,54 @@
#!/usr/bin/env bash
# Revert the NEXT-EP-POPUP shim injected into dev's index-dev.html on 2026-05-10.
#
# What it removes:
# /* NEXT-EP-POPUP-BEGIN ... */ ... /* NEXT-EP-POPUP-END */
#
# Defaults to dev. Pass --prod to remove from prod's index.html instead.
# Idempotent: safe to re-run.
#
# After local edit, redeploy to nullstone via the same nsenter cp trick used
# for sub-label-shim revert (see comment at end).
set -euo pipefail
REPO_ROOT="$(cd "$(dirname "$0")/.." && pwd)"
TARGET_DEV="$REPO_ROOT/web-overrides/index-dev.html"
TARGET_PROD="$REPO_ROOT/web-overrides/index.html"
TARGET="$TARGET_DEV"
ENV="dev"
if [[ "${1:-}" == "--prod" ]]; then
TARGET="$TARGET_PROD"
ENV="prod"
fi
if [[ ! -f "$TARGET" ]]; then
echo "ERROR: $TARGET not found" >&2
exit 1
fi
if ! grep -q "NEXT-EP-POPUP-BEGIN" "$TARGET"; then
echo "shim already absent in $ENV — nothing to do"
exit 0
fi
cp "$TARGET" "$TARGET.bak.$(date -u +%Y%m%dT%H%M%SZ)"
sed -i '/NEXT-EP-POPUP-BEGIN/,/NEXT-EP-POPUP-END/d' "$TARGET"
if grep -q "NEXT-EP-POPUP" "$TARGET"; then
echo "ERROR: revert left orphan markers in $TARGET" >&2
exit 2
fi
echo "reverted ($ENV). backup at $TARGET.bak.*"
echo
echo "Now redeploy to nullstone:"
if [[ "$ENV" == "dev" ]]; then
echo " scp $TARGET user@192.168.0.100:/tmp/index-dev-new.html"
echo " ssh user@192.168.0.100 'docker run --rm --privileged --pid=host --userns=host -v /opt:/opt -v /tmp:/tmp alpine nsenter -t 1 -m -u -i -n cp /tmp/index-dev-new.html /opt/docker/jellyfin-dev/web-overrides/index-dev.html'"
else
echo " scp $TARGET user@192.168.0.100:/tmp/arrflix-index.html"
echo " ssh user@192.168.0.100 'docker run --rm --privileged --pid=host --userns=host -v /opt:/opt -v /tmp:/tmp alpine nsenter -t 1 -m -u -i -n cp /tmp/arrflix-index.html /opt/docker/jellyfin/web-overrides/index.html'"
fi
echo "Then hard-refresh the browser (Ctrl+Shift+R)."

File diff suppressed because one or more lines are too long

View file

@ -0,0 +1,37 @@
# Next-episode popup designs
Side-by-side preview of the 4 candidate popup designs evaluated 2026-05-10.
Owner picked **A · Cinematic Strip** for prod-bound shim. **B · Terminal**
and **C · Minimal Bar** archived as standalone files for future re-evaluation
(e.g. when ARRFLIX visual direction shifts or owner gets bored of A).
## Files
- `preview.html` — full 4-up preview page (drop into `python3 -m http.server`
to compare). Designed at 1920×1080 in a faux Star-Wars-credits backdrop.
- `a-strip.html` — standalone A. Full-bleed bottom strip, big countdown
ring, white "Start Now" CTA, "Hide" secondary. Netflix-grade muscle
memory. **Currently shipped to dev.**
- `b-terminal.html` — standalone B. Bottom-right card with JetBrains
Mono, dashed dividers, red accent line. Edgy, ARRFLIX-distinct.
- `c-minimal.html` — standalone C. Thin progress bar across bottom + small
text strip, no card. Disappears into UX. Power-user.
D · Poster Card was discarded — too similar to Jellyfin stock to justify
shipping.
## Wiring (current state)
Design A is shipped to **dev only** (`dev.arrflix.s8n.ru`) as a CSS+JS
shim bracketed in `/opt/docker/jellyfin-dev/web-overrides/index-dev.html`
between `NEXT-EP-POPUP-BEGIN` and `NEXT-EP-POPUP-END` markers. The shim
keeps Jellyfin's `.upNextDialog` DOM intact (so its countdown timer keeps
ticking and clicking the underlying buttons stays wired) and overlays
Design A's visual via CSS + a small countdown-ring SVG that mirrors
`.upNextDialog-countdownText`.
Promote to prod when satisfied: copy the shim block into prod's
`web-overrides/index.html` between the same markers, then deploy via
the same nsenter trick documented in `bin/revert-sub-label-shim.sh`.
Revert (dev): `bin/revert-next-ep-popup.sh`.

View file

@ -0,0 +1,42 @@
<!DOCTYPE html>
<html lang="en"><head><meta charset="UTF-8"><title>ARRFLIX popup — A · Cinematic Strip</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=Geist:wght@400;500;600;700&family=JetBrains+Mono:wght@500&display=swap" rel="stylesheet">
<style>
:root { --arrflix-red:#E50914; --ink:#fff; --ink-dim:rgba(255,255,255,0.55); --ink-faint:rgba(255,255,255,0.3); }
*{box-sizing:border-box;margin:0;padding:0;}
body{background:#000;color:#fff;font-family:'Geist',system-ui,sans-serif;height:100vh;overflow:hidden;position:relative;}
.bg{position:absolute;inset:0;background:radial-gradient(ellipse 60% 40% at 50% 50%,rgba(20,20,40,0.4) 0%,transparent 70%),black;}
.popup{position:absolute;bottom:0;left:0;right:0;height:26%;background:linear-gradient(to top,rgba(0,0,0,0.95) 50%,rgba(0,0,0,0.7) 80%,transparent);padding:28px 56px 32px;display:flex;align-items:center;gap:36px;}
.ring{position:relative;width:88px;height:88px;flex-shrink:0;}
.ring svg{transform:rotate(-90deg);}
.ring circle{fill:none;stroke-width:3;}
.ring .track{stroke:rgba(255,255,255,0.1);}
.ring .progress{stroke:var(--arrflix-red);stroke-dasharray:264;stroke-dashoffset:67;stroke-linecap:round;filter:drop-shadow(0 0 8px rgba(229,9,20,0.5));}
.ring .num{position:absolute;inset:0;display:flex;align-items:center;justify-content:center;font-family:'JetBrains Mono',monospace;font-size:28px;font-weight:500;}
.info{flex:1;min-width:0;}
.label{font-size:10px;letter-spacing:0.32em;text-transform:uppercase;color:var(--arrflix-red);margin-bottom:8px;font-weight:600;}
.title{font-size:28px;font-weight:600;letter-spacing:-0.02em;margin-bottom:6px;line-height:1.1;}
.episode-title{font-size:16px;color:var(--ink-dim);margin-bottom:4px;}
.meta{font-size:12px;color:var(--ink-faint);letter-spacing:0.04em;}
.actions{display:flex;gap:10px;flex-shrink:0;}
.btn{border:none;background:white;color:black;padding:14px 28px;font-family:inherit;font-size:13px;font-weight:600;cursor:pointer;display:flex;align-items:center;gap:10px;}
.btn:hover{background:rgba(255,255,255,0.85);}
.btn-secondary{background:rgba(255,255,255,0.08);color:white;backdrop-filter:blur(10px);}
.btn-secondary:hover{background:rgba(255,255,255,0.16);}
</style></head><body>
<div class="bg"></div>
<div class="popup">
<div class="ring"><svg width="88" height="88" viewBox="0 0 88 88"><circle class="track" cx="44" cy="44" r="42"/><circle class="progress" cx="44" cy="44" r="42"/></svg><div class="num">17</div></div>
<div class="info">
<div class="label">Up Next</div>
<div class="title">Star Wars: Maul · Shadow Lord</div>
<div class="episode-title">S1·E3 — Chapter 3: The Crucible</div>
<div class="meta">22 min · Ends at 2:49 AM</div>
</div>
<div class="actions">
<button class="btn">▶ Start Now</button>
<button class="btn btn-secondary">Hide</button>
</div>
</div>
</body></html>

View file

@ -0,0 +1,40 @@
<!DOCTYPE html>
<html lang="en"><head><meta charset="UTF-8"><title>ARRFLIX popup — B · Terminal Card</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=Geist:wght@400;500&family=JetBrains+Mono:wght@400;500;700&display=swap" rel="stylesheet">
<style>
:root{--arrflix-red:#E50914;--arrflix-red-dark:#B00710;--ink:#fff;--ink-dim:rgba(255,255,255,0.55);--ink-faint:rgba(255,255,255,0.3);}
*{box-sizing:border-box;margin:0;padding:0;}
body{background:#000;color:#fff;font-family:'JetBrains Mono',monospace;height:100vh;overflow:hidden;position:relative;}
.bg{position:absolute;inset:0;background:radial-gradient(ellipse 60% 40% at 50% 50%,rgba(20,20,40,0.4) 0%,transparent 70%),black;}
.popup{position:absolute;bottom:32px;right:32px;width:380px;background:rgba(5,5,5,0.92);backdrop-filter:blur(14px);border:1px solid rgba(255,255,255,0.12);border-left:2px solid var(--arrflix-red);padding:20px 22px;}
.top-row{display:flex;justify-content:space-between;align-items:center;margin-bottom:14px;font-size:10px;letter-spacing:0.18em;text-transform:uppercase;}
.tag{color:var(--arrflix-red);font-weight:700;}
.countdown{color:var(--ink-dim);}
.countdown strong{color:var(--ink);font-weight:700;}
hr{border:none;border-top:1px dashed rgba(255,255,255,0.1);margin:12px 0;}
.show{font-family:'Geist',sans-serif;font-size:11px;letter-spacing:0.18em;text-transform:uppercase;color:var(--ink-faint);margin-bottom:4px;}
.ep{font-family:'Geist',sans-serif;font-size:15px;font-weight:500;margin-bottom:4px;color:var(--ink);}
.ep-meta{font-size:11px;color:var(--ink-faint);letter-spacing:0.06em;margin-bottom:16px;}
.progress-bar{height:2px;background:rgba(255,255,255,0.08);margin-bottom:18px;position:relative;}
.progress-bar::after{content:'';position:absolute;top:0;left:0;height:100%;width:75%;background:var(--arrflix-red);box-shadow:0 0 8px rgba(229,9,20,0.6);}
.actions{display:flex;gap:8px;}
.btn{flex:1;background:transparent;border:1px solid rgba(255,255,255,0.18);color:white;padding:9px 14px;font-family:inherit;font-size:11px;letter-spacing:0.18em;text-transform:uppercase;cursor:pointer;}
.btn-primary{background:var(--arrflix-red);border-color:var(--arrflix-red);}
.btn-primary:hover{background:var(--arrflix-red-dark);}
.btn:hover{background:rgba(255,255,255,0.06);border-color:rgba(255,255,255,0.3);}
</style></head><body>
<div class="bg"></div>
<div class="popup">
<div class="top-row"><span class="tag">▎ Up Next</span><span class="countdown"><strong>17</strong>s</span></div>
<hr>
<div class="show">Star Wars: Maul · Shadow Lord · S1E3</div>
<div class="ep">Chapter 3: The Crucible</div>
<div class="ep-meta">22m / ends 02:49</div>
<div class="progress-bar"></div>
<div class="actions">
<button class="btn btn-primary">▶ Start now</button>
<button class="btn">Hide</button>
</div>
</div>
</body></html>

View file

@ -0,0 +1,36 @@
<!DOCTYPE html>
<html lang="en"><head><meta charset="UTF-8"><title>ARRFLIX popup — C · Minimal Bar</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=Geist:wght@400;500;600;700&family=JetBrains+Mono:wght@500&family=Bebas+Neue&display=swap" rel="stylesheet">
<style>
:root{--arrflix-red:#E50914;--ink:#fff;--ink-dim:rgba(255,255,255,0.55);}
*{box-sizing:border-box;margin:0;padding:0;}
body{background:#000;color:#fff;font-family:'Geist',system-ui,sans-serif;height:100vh;overflow:hidden;position:relative;}
.bg{position:absolute;inset:0;background:radial-gradient(ellipse 60% 40% at 50% 50%,rgba(20,20,40,0.4) 0%,transparent 70%),black;}
.popup{position:absolute;bottom:0;left:0;right:0;}
.progress-line{height:3px;background:rgba(255,255,255,0.1);position:relative;}
.progress-line::after{content:'';position:absolute;top:0;left:0;height:100%;width:75%;background:var(--arrflix-red);}
.row{background:linear-gradient(to bottom,transparent,rgba(0,0,0,0.85) 30%);padding:24px 56px 22px;display:flex;align-items:center;gap:24px;}
.label{font-family:'Bebas Neue',sans-serif;font-size:13px;letter-spacing:0.32em;color:var(--arrflix-red);flex-shrink:0;}
.text{flex:1;font-size:14px;color:var(--ink-dim);letter-spacing:0.02em;}
.text strong{color:var(--ink);font-weight:500;margin-right:8px;}
.text .countdown{color:var(--arrflix-red);font-family:'JetBrains Mono',monospace;font-weight:500;margin-left:8px;}
.actions{display:flex;gap:16px;}
.btn{background:transparent;border:none;color:white;font-family:inherit;font-size:12px;letter-spacing:0.2em;text-transform:uppercase;font-weight:600;cursor:pointer;padding:8px 0;position:relative;}
.btn::after{content:'';position:absolute;left:0;bottom:0;height:1px;width:100%;background:currentColor;opacity:0.3;}
.btn:hover::after{opacity:1;}
.btn-primary{color:var(--arrflix-red);}
</style></head><body>
<div class="bg"></div>
<div class="popup">
<div class="progress-line"></div>
<div class="row">
<div class="label">UP NEXT</div>
<div class="text"><strong>Star Wars: Maul · Shadow Lord</strong>S1·E3 — Chapter 3: The Crucible<span class="countdown">00:17</span></div>
<div class="actions">
<button class="btn btn-primary">Start now ▶</button>
<button class="btn">Hide</button>
</div>
</div>
</div>
</body></html>

View file

@ -0,0 +1,760 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>ARRFLIX — Next-Episode popup designs</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=Geist:wght@300;400;500;600;700&family=JetBrains+Mono:wght@400;500;700&family=Bebas+Neue&family=Anton&display=swap" rel="stylesheet">
<style>
:root {
--arrflix-red: #E50914;
--arrflix-red-dark: #B00710;
--arrflix-bg: #0a0a0a;
--ink: #fff;
--ink-dim: rgba(255,255,255,0.55);
--ink-faint: rgba(255,255,255,0.3);
}
* { box-sizing: border-box; margin: 0; padding: 0; }
html, body {
background: #050505;
color: var(--ink);
font-family: 'Geist', system-ui, sans-serif;
font-feature-settings: "ss01", "ss02";
overflow-x: hidden;
}
.header {
position: sticky; top: 0; z-index: 100;
background: rgba(5,5,5,0.85);
backdrop-filter: blur(12px);
border-bottom: 1px solid rgba(255,255,255,0.08);
padding: 18px 28px;
display: flex; align-items: center; justify-content: space-between;
}
.header h1 {
font-family: 'Bebas Neue', sans-serif;
letter-spacing: 0.1em;
font-size: 20px;
color: var(--arrflix-red);
}
.header .meta {
font-size: 11px;
letter-spacing: 0.18em;
text-transform: uppercase;
color: var(--ink-dim);
}
.picker {
display: flex; gap: 10px;
}
.picker button {
background: transparent;
border: 1px solid rgba(255,255,255,0.15);
color: var(--ink-dim);
padding: 8px 14px;
font-family: inherit;
font-size: 11px;
letter-spacing: 0.15em;
text-transform: uppercase;
cursor: pointer;
transition: all 0.15s ease;
}
.picker button:hover {
border-color: var(--arrflix-red);
color: var(--ink);
}
.picker button.active {
background: var(--arrflix-red);
border-color: var(--arrflix-red);
color: white;
}
.stage {
position: relative;
width: 100%;
aspect-ratio: 16/9;
max-height: calc(100vh - 80px);
background: black;
overflow: hidden;
}
.stage::before {
/* Star Wars credits backdrop */
content: '';
position: absolute; inset: 0;
background:
radial-gradient(ellipse 60% 40% at 50% 50%, rgba(20,20,40,0.4) 0%, transparent 70%),
black;
}
.stars {
position: absolute; inset: 0;
background-image:
radial-gradient(1px 1px at 20% 30%, white, transparent),
radial-gradient(1px 1px at 65% 50%, white, transparent),
radial-gradient(1px 1px at 80% 20%, rgba(255,255,255,0.7), transparent),
radial-gradient(2px 2px at 10% 70%, white, transparent),
radial-gradient(1px 1px at 85% 80%, rgba(255,255,255,0.8), transparent),
radial-gradient(1px 1px at 45% 90%, white, transparent),
radial-gradient(1px 1px at 30% 15%, rgba(255,255,255,0.5), transparent),
radial-gradient(1.5px 1.5px at 70% 75%, white, transparent),
radial-gradient(1px 1px at 5% 45%, white, transparent),
radial-gradient(1px 1px at 95% 60%, rgba(255,255,255,0.6), transparent),
radial-gradient(1px 1px at 25% 85%, white, transparent),
radial-gradient(1px 1px at 55% 25%, white, transparent),
radial-gradient(2px 2px at 50% 50%, rgba(255,255,255,0.4), transparent),
radial-gradient(1px 1px at 15% 92%, white, transparent),
radial-gradient(1px 1px at 88% 35%, white, transparent);
background-size: 100% 100%;
}
.credits {
position: absolute;
top: 18%;
left: 50%;
transform: translateX(-50%);
color: #4488dd;
font-family: 'Geist', sans-serif;
font-weight: 500;
font-size: 14px;
letter-spacing: 0.04em;
text-align: center;
line-height: 1.7;
opacity: 0.55;
text-shadow: 0 0 4px rgba(68,136,221,0.2);
}
.credits .row {
display: grid; grid-template-columns: 1fr 1fr; gap: 32px;
text-align: left;
margin-bottom: 18px;
}
.credits .row .role { text-align: right; opacity: 0.85; }
.credits .row .name { text-transform: uppercase; }
.credits .title-block { text-align: center; margin-bottom: 18px; }
/* Stop the star backdrop from comUSER-Eing */
.stage .popup-host {
position: absolute; inset: 0; z-index: 50;
pointer-events: none;
}
.stage .popup-host > * { pointer-events: auto; }
/* === DESIGN A — CINEMATIC STRIP === */
.design-a {
position: absolute;
bottom: 0; left: 0; right: 0;
height: 26%;
background: linear-gradient(to top, rgba(0,0,0,0.95) 50%, rgba(0,0,0,0.7) 80%, transparent);
padding: 28px 56px 32px;
display: flex;
align-items: center;
gap: 36px;
transform: translateY(0);
animation: slideUp 0.5s cubic-bezier(0.16, 1, 0.3, 1);
}
@keyframes slideUp {
from { transform: translateY(100%); opacity: 0; }
to { transform: translateY(0); opacity: 1; }
}
.design-a .ring {
position: relative;
width: 88px; height: 88px;
flex-shrink: 0;
}
.design-a .ring svg { transform: rotate(-90deg); }
.design-a .ring circle {
fill: none;
stroke-width: 3;
}
.design-a .ring .track { stroke: rgba(255,255,255,0.1); }
.design-a .ring .progress {
stroke: var(--arrflix-red);
stroke-dasharray: 264;
stroke-dashoffset: 67;
stroke-linecap: round;
transition: stroke-dashoffset 1s linear;
filter: drop-shadow(0 0 8px rgba(229, 9, 20, 0.5));
}
.design-a .ring .num {
position: absolute; inset: 0;
display: flex; align-items: center; justify-content: center;
font-family: 'JetBrains Mono', monospace;
font-size: 28px;
font-weight: 500;
letter-spacing: -0.02em;
}
.design-a .info { flex: 1; min-width: 0; }
.design-a .label {
font-size: 10px;
letter-spacing: 0.32em;
text-transform: uppercase;
color: var(--arrflix-red);
margin-bottom: 8px;
font-weight: 600;
}
.design-a .title {
font-size: 28px;
font-weight: 600;
letter-spacing: -0.02em;
margin-bottom: 6px;
line-height: 1.1;
}
.design-a .episode-title {
font-size: 16px;
color: var(--ink-dim);
margin-bottom: 4px;
}
.design-a .meta {
font-size: 12px;
color: var(--ink-faint);
letter-spacing: 0.04em;
}
.design-a .actions {
display: flex; gap: 10px;
flex-shrink: 0;
}
.design-a .btn {
border: none;
background: white;
color: black;
padding: 14px 28px;
font-family: inherit;
font-size: 13px;
font-weight: 600;
letter-spacing: 0.02em;
cursor: pointer;
display: flex;
align-items: center;
gap: 10px;
transition: all 0.15s ease;
}
.design-a .btn:hover { background: rgba(255,255,255,0.85); }
.design-a .btn-secondary {
background: rgba(255,255,255,0.08);
color: white;
backdrop-filter: blur(10px);
}
.design-a .btn-secondary:hover { background: rgba(255,255,255,0.16); }
/* === DESIGN B — TERMINAL CARD === */
.design-b {
position: absolute;
bottom: 32px; right: 32px;
width: 380px;
background: rgba(5,5,5,0.92);
backdrop-filter: blur(14px);
border: 1px solid rgba(255,255,255,0.12);
border-left: 2px solid var(--arrflix-red);
padding: 20px 22px;
font-family: 'JetBrains Mono', monospace;
animation: slideRight 0.4s cubic-bezier(0.16, 1, 0.3, 1);
}
@keyframes slideRight {
from { transform: translateX(20px); opacity: 0; }
to { transform: translateX(0); opacity: 1; }
}
.design-b .top-row {
display: flex; justify-content: space-between; align-items: center;
margin-bottom: 14px;
font-size: 10px;
letter-spacing: 0.18em;
text-transform: uppercase;
}
.design-b .tag {
color: var(--arrflix-red);
font-weight: 700;
}
.design-b .countdown {
color: var(--ink-dim);
}
.design-b .countdown strong {
color: var(--ink);
font-weight: 700;
}
.design-b hr {
border: none;
border-top: 1px dashed rgba(255,255,255,0.1);
margin: 12px 0;
}
.design-b .ep {
font-family: 'Geist', sans-serif;
font-size: 15px;
font-weight: 500;
margin-bottom: 4px;
color: var(--ink);
letter-spacing: -0.01em;
}
.design-b .ep-meta {
font-size: 11px;
color: var(--ink-faint);
letter-spacing: 0.06em;
margin-bottom: 16px;
}
.design-b .progress-bar {
height: 2px;
background: rgba(255,255,255,0.08);
margin-bottom: 18px;
position: relative;
}
.design-b .progress-bar::after {
content: '';
position: absolute;
top: 0; left: 0;
height: 100%;
width: 75%;
background: var(--arrflix-red);
box-shadow: 0 0 8px rgba(229,9,20,0.6);
animation: fillBar 17s linear forwards;
}
@keyframes fillBar {
to { width: 100%; }
}
.design-b .actions {
display: flex; gap: 8px;
}
.design-b .btn {
flex: 1;
background: transparent;
border: 1px solid rgba(255,255,255,0.18);
color: white;
padding: 9px 14px;
font-family: inherit;
font-size: 11px;
letter-spacing: 0.18em;
text-transform: uppercase;
cursor: pointer;
transition: all 0.15s ease;
}
.design-b .btn-primary {
background: var(--arrflix-red);
border-color: var(--arrflix-red);
}
.design-b .btn-primary:hover { background: var(--arrflix-red-dark); }
.design-b .btn-secondary:hover {
background: rgba(255,255,255,0.06);
border-color: rgba(255,255,255,0.3);
}
/* === DESIGN C — MINIMAL BAR === */
.design-c {
position: absolute;
bottom: 0; left: 0; right: 0;
pointer-events: auto;
animation: fadeUp 0.4s ease;
}
@keyframes fadeUp {
from { transform: translateY(20px); opacity: 0; }
to { transform: translateY(0); opacity: 1; }
}
.design-c .progress-line {
height: 3px;
background: rgba(255,255,255,0.1);
position: relative;
}
.design-c .progress-line::after {
content: '';
position: absolute;
top: 0; left: 0;
height: 100%;
width: 75%;
background: var(--arrflix-red);
animation: fillBar 17s linear forwards;
}
.design-c .row {
background: linear-gradient(to bottom, transparent, rgba(0,0,0,0.85) 30%);
padding: 24px 56px 22px;
display: flex;
align-items: center;
gap: 24px;
}
.design-c .label {
font-family: 'Bebas Neue', sans-serif;
font-size: 13px;
letter-spacing: 0.32em;
color: var(--arrflix-red);
flex-shrink: 0;
}
.design-c .text {
flex: 1;
font-size: 14px;
color: var(--ink-dim);
letter-spacing: 0.02em;
}
.design-c .text strong {
color: var(--ink);
font-weight: 500;
margin-right: 8px;
}
.design-c .text .countdown {
color: var(--arrflix-red);
font-family: 'JetBrains Mono', monospace;
font-weight: 500;
margin-left: 8px;
}
.design-c .actions {
display: flex; gap: 16px;
}
.design-c .btn {
background: transparent;
border: none;
color: white;
font-family: inherit;
font-size: 12px;
letter-spacing: 0.2em;
text-transform: uppercase;
font-weight: 600;
cursor: pointer;
padding: 8px 0;
position: relative;
transition: color 0.15s ease;
}
.design-c .btn::after {
content: '';
position: absolute;
left: 0; bottom: 0;
height: 1px; width: 100%;
background: currentColor;
opacity: 0.3;
transition: opacity 0.15s ease;
}
.design-c .btn:hover::after { opacity: 1; }
.design-c .btn-primary { color: var(--arrflix-red); }
/* === DESIGN D — POSTER CARD === */
.design-d {
position: absolute;
bottom: 28px; right: 28px;
width: 460px;
background: linear-gradient(135deg, rgba(15,15,15,0.95), rgba(5,5,5,0.95));
backdrop-filter: blur(20px);
border: 1px solid rgba(255,255,255,0.08);
border-radius: 4px;
overflow: hidden;
display: flex;
box-shadow: 0 20px 60px rgba(0,0,0,0.6),
0 0 0 1px rgba(229,9,20,0.15);
animation: slideRight 0.4s cubic-bezier(0.16, 1, 0.3, 1);
}
.design-d .poster {
width: 160px;
flex-shrink: 0;
background:
linear-gradient(135deg, rgba(229,9,20,0.3), transparent 60%),
radial-gradient(circle at 30% 30%, #2a1a1a, #050505);
position: relative;
display: flex; align-items: flex-end;
padding: 16px;
}
.design-d .poster::before {
content: '';
position: absolute; inset: 0;
background-image:
radial-gradient(1px 1px at 20% 30%, white, transparent),
radial-gradient(1px 1px at 70% 50%, white, transparent),
radial-gradient(1px 1px at 40% 70%, white, transparent),
radial-gradient(1px 1px at 80% 80%, white, transparent),
radial-gradient(1px 1px at 50% 20%, white, transparent),
radial-gradient(1.5px 1.5px at 60% 60%, white, transparent);
opacity: 0.4;
}
.design-d .poster .ep-num {
position: relative;
font-family: 'Anton', sans-serif;
font-size: 64px;
line-height: 0.9;
color: var(--arrflix-red);
letter-spacing: -0.04em;
text-shadow: 0 4px 20px rgba(229,9,20,0.5);
}
.design-d .body {
flex: 1;
padding: 18px 20px 16px;
display: flex;
flex-direction: column;
}
.design-d .top {
display: flex; justify-content: space-between; align-items: center;
margin-bottom: 10px;
}
.design-d .label {
font-size: 9.5px;
letter-spacing: 0.32em;
text-transform: uppercase;
color: var(--arrflix-red);
font-weight: 700;
}
.design-d .timer {
font-family: 'JetBrains Mono', monospace;
font-size: 11px;
letter-spacing: 0.04em;
color: var(--ink-dim);
}
.design-d .timer strong {
color: var(--ink);
font-weight: 700;
}
.design-d .show {
font-size: 13px;
color: var(--ink-faint);
margin-bottom: 4px;
letter-spacing: 0.02em;
}
.design-d .ep-title {
font-size: 18px;
font-weight: 600;
letter-spacing: -0.02em;
line-height: 1.2;
margin-bottom: 12px;
}
.design-d .meta {
font-size: 11px;
color: var(--ink-faint);
letter-spacing: 0.06em;
margin-bottom: 14px;
text-transform: uppercase;
}
.design-d .meta .dot { margin: 0 8px; opacity: 0.5; }
.design-d .actions {
display: flex; gap: 8px;
margin-top: auto;
}
.design-d .btn {
flex: 1;
border: none;
padding: 11px 12px;
font-family: inherit;
font-size: 11.5px;
font-weight: 600;
letter-spacing: 0.08em;
text-transform: uppercase;
cursor: pointer;
transition: all 0.15s ease;
border-radius: 2px;
}
.design-d .btn-primary {
background: var(--arrflix-red);
color: white;
box-shadow: 0 4px 14px rgba(229,9,20,0.3);
}
.design-d .btn-primary:hover { background: var(--arrflix-red-dark); }
.design-d .btn-secondary {
background: transparent;
color: var(--ink-dim);
border: 1px solid rgba(255,255,255,0.15);
}
.design-d .btn-secondary:hover {
color: var(--ink);
border-color: rgba(255,255,255,0.4);
}
/* hidden helper */
.hidden { display: none !important; }
/* design label */
.design-label {
position: absolute;
top: 24px; left: 28px;
z-index: 60;
font-family: 'Bebas Neue', sans-serif;
letter-spacing: 0.18em;
font-size: 13px;
color: rgba(255,255,255,0.5);
border-left: 2px solid var(--arrflix-red);
padding-left: 12px;
line-height: 1.4;
}
.design-label strong {
display: block;
color: white;
font-size: 18px;
letter-spacing: 0.1em;
}
.design-label .desc {
font-family: 'Geist', sans-serif;
font-size: 11px;
letter-spacing: 0.04em;
text-transform: none;
color: var(--ink-dim);
margin-top: 4px;
max-width: 300px;
}
.footer {
padding: 28px 56px 36px;
font-size: 12px;
color: var(--ink-faint);
letter-spacing: 0.04em;
line-height: 1.7;
border-top: 1px solid rgba(255,255,255,0.06);
}
.footer strong { color: var(--ink-dim); font-weight: 500; }
.footer kbd {
background: rgba(255,255,255,0.08);
border: 1px solid rgba(255,255,255,0.12);
border-radius: 3px;
padding: 2px 6px;
font-family: 'JetBrains Mono', monospace;
font-size: 11px;
color: var(--ink-dim);
}
</style>
</head>
<body>
<header class="header">
<h1>ARRFLIX · NEXT-EPISODE POPUP</h1>
<div class="picker">
<button data-d="a" class="active">A · STRIP</button>
<button data-d="b">B · TERMINAL</button>
<button data-d="c">C · MINIMAL</button>
<button data-d="d">D · POSTER</button>
</div>
<div class="meta">PICK ONE · 2026-05-10</div>
</header>
<div class="stage">
<div class="stars"></div>
<div class="credits">
<div class="title-block" style="color:#4488dd; font-weight:600;">
Production Services Provided by CGCG, Inc.
</div>
<div class="row">
<div class="role">Lighting Director<br>Lighting Lead<br>Lighting Artists</div>
<div class="name">Chung-Kai Hsueh<br>Yin-Jung Huang<br>Jung-Tzu Chang · Po-Jui Chiu<br>Char Ho · Luna Jiang<br>Chuan-Sheng Lan · Po-Yu Li</div>
</div>
<div class="row">
<div class="role">Special Effects Director<br>Special Effects Artists</div>
<div class="name">Chia-Hung Chu<br>Jia-You Cai · Lin-Chi Chen<br>Cai-Jhu Li · Zhi-Hao Liu</div>
</div>
<div class="row">
<div class="role">Production Technology</div>
<div class="name">Indigo Tang · Joe Chang<br>I Chiang · Chih-Chiang Tsai</div>
</div>
</div>
<div class="design-label" id="dlabel">
<strong>A · CINEMATIC STRIP</strong>
<div class="desc">Full-bleed bottom strip. Big countdown ring. White CTA = Netflix muscle memory. Grand.</div>
</div>
<div class="popup-host">
<!-- DESIGN A -->
<div class="design-a" data-design="a">
<div class="ring">
<svg width="88" height="88" viewBox="0 0 88 88">
<circle class="track" cx="44" cy="44" r="42"/>
<circle class="progress" cx="44" cy="44" r="42"/>
</svg>
<div class="num">17</div>
</div>
<div class="info">
<div class="label">Up Next</div>
<div class="title">Star Wars: Maul · Shadow Lord</div>
<div class="episode-title">S1·E3 — Chapter 3: The Crucible</div>
<div class="meta">22 min · Ends at 2:49 AM</div>
</div>
<div class="actions">
<button class="btn">▶ Start Now</button>
<button class="btn btn-secondary">Hide</button>
</div>
</div>
<!-- DESIGN B -->
<div class="design-b hidden" data-design="b">
<div class="top-row">
<span class="tag">▎ Up Next</span>
<span class="countdown"><strong>17</strong>s</span>
</div>
<hr>
<div style="font-family:'Geist',sans-serif; font-size: 11px; letter-spacing: 0.18em; text-transform: uppercase; color: var(--ink-faint); margin-bottom: 4px;">Star Wars: Maul · Shadow Lord · S1E3</div>
<div class="ep">Chapter 3: The Crucible</div>
<div class="ep-meta">22m / ends 02:49</div>
<div class="progress-bar"></div>
<div class="actions">
<button class="btn btn-primary">▶ Start now</button>
<button class="btn btn-secondary">Hide</button>
</div>
</div>
<!-- DESIGN C -->
<div class="design-c hidden" data-design="c">
<div class="progress-line"></div>
<div class="row">
<div class="label">UP NEXT</div>
<div class="text">
<strong>Star Wars: Maul · Shadow Lord</strong>S1·E3 — Chapter 3: The Crucible
<span class="countdown">00:17</span>
</div>
<div class="actions">
<button class="btn btn-primary">Start now ▶</button>
<button class="btn">Hide</button>
</div>
</div>
</div>
<!-- DESIGN D -->
<div class="design-d hidden" data-design="d">
<div class="poster"><div class="ep-num">E3</div></div>
<div class="body">
<div class="top">
<div class="label">Up Next</div>
<div class="timer">in <strong>17</strong>s</div>
</div>
<div class="show">Star Wars: Maul · Shadow Lord</div>
<div class="ep-title">Chapter 3: The Crucible</div>
<div class="meta">Season 1 <span class="dot">·</span> 22 min <span class="dot">·</span> Ends 02:49</div>
<div class="actions">
<button class="btn btn-primary">▶ Start now</button>
<button class="btn btn-secondary">Hide</button>
</div>
</div>
</div>
</div>
</div>
<div class="footer">
Pick one (<kbd>1</kbd><kbd>4</kbd> or click). When approved, design ships as a JS shim into <strong>web-overrides/index.html</strong> bracketed by <strong>NEXT-EP-POPUP-BEGIN/END</strong> markers, with one-shot revert via <strong>bin/revert-next-ep-popup.sh</strong> (matching the sub-label-shim pattern). The shim hides Jellyfin's stock card and renders the chosen design in its place when Jellyfin signals an upcoming episode.
</div>
<script>
const labels = {
a: ['A · CINEMATIC STRIP', 'Full-bleed bottom strip. Big countdown ring. White CTA = Netflix muscle memory. Grand.'],
b: ['B · TERMINAL CARD', 'Bottom-right card with monospace, dashed dividers, red accent line. Edgy, ARRFLIX-distinct.'],
c: ['C · MINIMAL BAR', 'Thin progress line + small text strip across bottom. Disappears into UX. Power-user.'],
d: ['D · POSTER CARD', 'Bottom-right card with episode-number tile + show + ep title + dual buttons. Polished, pragmatic.'],
};
const buttons = document.querySelectorAll('.picker button');
const designs = document.querySelectorAll('[data-design]');
const lbl = document.getElementById('dlabel');
function show(d) {
buttons.forEach(b => b.classList.toggle('active', b.dataset.d === d));
designs.forEach(el => el.classList.toggle('hidden', el.dataset.design !== d));
lbl.querySelector('strong').textContent = labels[d][0];
lbl.querySelector('.desc').textContent = labels[d][1];
// restart fillBar animations on switch (recreate elements)
designs.forEach(el => {
if (el.dataset.design === d) {
el.style.animation = 'none';
void el.offsetHeight;
el.style.animation = '';
}
});
}
buttons.forEach(b => b.addEventListener('click', () => show(b.dataset.d)));
document.addEventListener('keydown', (e) => {
const map = { '1':'a', '2':'b', '3':'c', '4':'d' };
if (map[e.key]) show(map[e.key]);
});
// animated countdown
let t = 17;
setInterval(() => {
t--;
if (t < 0) t = 17;
document.querySelector('.design-a .num').textContent = t;
document.querySelector('.design-a .ring .progress').style.strokeDashoffset = 67 + (264 - 67) * (1 - t/17);
document.querySelector('.design-b .top-row strong').textContent = t;
document.querySelector('.design-c .countdown').textContent = '00:' + String(t).padStart(2,'0');
document.querySelector('.design-d .timer strong').textContent = t;
}, 1000);
</script>
</body>
</html>