/* ═══════════════════════════════════════════
   now-streaming — beat 4: episode-guide  (shots.md "### `episode-guide`")

   The show page. A banner (the top 38% of the screen: the last moment's
   photo, dimmed, the title docked on it and glowing) carries the season
   line, the synopsis and the three small lines over its scrim; under it the
   episode rows, a list that scrolls inside its own region (pan-y) while the
   banner and the header stay put; under that a band for the hint, the
   "Play from the top" button and the outro lines, which sits above the
   badge band (the bottom 72 px plus the safe-area inset hold nothing).

   States (on #episode-guide-container, toggled by js/episode-guide.js):
     --few       N <= 5: the banner photo runs taller, the rows sit lower
     --season    0.4 s   "Season {S}" and "{together}"
     --whole     reduced motion: the synopsis fades in whole
     --held      9.8 s   the hold cue: the ring (+0.4 s) and the hint (+1.2 s), by CSS delay
     --opened    after the first open: the hint leaves
     --can-play  after the third distinct open: "Play from the top"
     --released  the hold is over: ring, hint and button leave
     --wave      post +1.0 s: the seen dots pulse, 0.12 s apart
     --outro1/2  post +2.6 / +3.4 s: the two outro lines, the line fills over 3.0 s
     --leaving   post +6.4 s: the rows ghost upward, the text leaves
     --grow      post +6.4 s: the banner photo grows to the full screen (5.6 s)
     --dissolve  post +13.0 s: the first moment dissolves in (1.2 s)

   Transform and opacity everywhere except three deliberate cases: a row's
   panel opening (grid-template-rows), the banner's clip-path growing to the
   full screen, and the fill line. No filter, backdrop-filter or blend mode.
   ═══════════════════════════════════════════ */

.episode-guide-container {
    --episode-guide-banner-h: 38%;      /* where the banner (and the header on it) ends */
    --episode-guide-photo-h: 38%;       /* how far the banner photo runs (taller with few rows) */
    --episode-guide-row-h: 56px;
    --episode-guide-band: calc(72px + env(safe-area-inset-bottom, 0px));
    --episode-guide-foot-h: 52px;

    position: absolute;
    inset: 0;
    overflow: hidden;
    background: var(--bg-primary);
    color: var(--text-primary);
    font-family: var(--font-body);
}

.episode-guide-container.episode-guide--few {
    --episode-guide-photo-h: 45%;
}

/* ─────────── The banner: one full-screen photo, clipped to the top ───────────
   It is full-screen size from the start and shows only its top band, so the
   exit can grow it to the whole screen by clip-path alone. The compound rule
   keeps it absolute when ReveelStage.lightSweep adds .reveel-sweep
   (which sets position: relative). */
.episode-guide-banner,
.episode-guide-banner.reveel-sweep {
    position: absolute;
    inset: 0;
    overflow: hidden;
}

.episode-guide-banner {
    z-index: 0;
    pointer-events: none;
    -webkit-clip-path: inset(0 0 calc(100% - var(--episode-guide-photo-h)) 0);
    clip-path: inset(0 0 calc(100% - var(--episode-guide-photo-h)) 0);
}

.episode-guide-banner-img,
.episode-guide-full,
.episode-guide-cut {
    position: absolute;
    inset: 0;
    width: 100%;
    height: 100%;
    object-fit: cover;
    object-position: 50% 30%;
    display: block;
}

.episode-guide-banner-img.episode-guide-broken,
.episode-guide-full.episode-guide-broken,
.episode-guide-cut.episode-guide-broken {
    visibility: hidden;
}

.episode-guide-banner-dim {
    position: absolute;
    inset: 0;
    background: rgba(5, 5, 17, 0.34);
}

/* The scrim lies over the visible band only and ends in the page's own
   ground, so the photo's lower edge is a feather, never a line. */
.episode-guide-banner-scrim {
    position: absolute;
    left: 0;
    right: 0;
    top: 0;
    height: calc(var(--episode-guide-photo-h) + 2px);   /* past the clip edge: no seam on a subpixel boundary */
    background: linear-gradient(180deg,
        rgba(5, 5, 17, 0.55) 0%,
        rgba(5, 5, 17, 0) 20%,
        rgba(12, 18, 34, 0.5) 48%,
        rgba(12, 18, 34, 0.9) 80%,
        rgba(12, 18, 34, 1) 100%);
}

/* The calm exit's full-screen photo (reduced motion only) and the dissolve
   to the first moment of the playback. */
.episode-guide-full {
    z-index: 1;
    opacity: 0;
    pointer-events: none;
}

.episode-guide-cut {
    z-index: 6;
    opacity: 0;
    pointer-events: none;
}

/* ─────────── The header, on the banner's lower edge ─────────── */
.episode-guide-head {
    position: absolute;
    z-index: 2;
    left: 18px;
    right: 18px;
    bottom: calc(100% - var(--episode-guide-banner-h) + 6px);
    display: flex;
    flex-direction: column;
    gap: 5px;
    pointer-events: none;
}

.episode-guide-title {
    margin: 0 0 1px;
    font-family: var(--font-display);
    font-weight: 700;
    font-size: clamp(1.35rem, 6.4vw, 2rem);
    line-height: 1.12;
    color: var(--text-primary);
    text-shadow: var(--title-shadow);
    overflow-wrap: anywhere;
    text-wrap: balance;
}

.episode-guide-season {
    margin: 0;
    font-family: var(--font-body);
    font-size: 0.74rem;
    line-height: 1.3;
    opacity: 0;
    transition: opacity 0.7s var(--ease-silk);
}

.episode-guide--season .episode-guide-season {
    opacity: 1;
}

.episode-guide-season-tag {
    font-weight: 600;
    letter-spacing: 0.16em;
    text-transform: uppercase;
    color: var(--accent-warm-bright);
}

.episode-guide-together {
    font-weight: 400;
    font-size: 0.8rem;
    letter-spacing: 0.02em;
    color: var(--text-secondary);
}

/* The separator belongs to the span, so an empty {together} takes it away
   with itself (no :has()). */
.episode-guide-together::before {
    content: "\00b7";
    margin: 0 0.5em;
    color: var(--decor-ink);
}

.episode-guide-together:empty {
    display: none;
}

.episode-guide-synopsis {
    margin: 2px 0 1px;
    font-family: var(--font-display);
    font-weight: 400;
    font-size: clamp(0.92rem, 4.1vw, 1.04rem);
    line-height: 1.36;
    color: var(--text-primary);
    text-wrap: pretty;
}

/* It writes itself in left to right, a word at a time. */
.episode-guide-word {
    display: inline-block;
    opacity: 0;
    transform: translateX(-4px);
    transition: opacity 0.38s var(--ease-silk), transform 0.55s var(--ease-expo-out);
}

.episode-guide-word.episode-guide-on,
.episode-guide--whole .episode-guide-word {
    opacity: 1;
    transform: none;
}

.episode-guide-meta {
    position: relative;
    margin: 0;
    font-family: var(--font-body);
    font-weight: 400;
    font-size: 0.76rem;
    line-height: 1.38;
    color: var(--text-secondary);
    opacity: 0;
}

.episode-guide-meta--starring > span {
    font-weight: 500;
    color: var(--text-primary);
}

/* ─────────── The ghost drop-in (concept.md "The ghost drop-in") ───────────
   The real element drops in; two aria-hidden echoes ride inside it, offset
   up and translucent, and converge onto it in --ghost-ms, then are removed
   on their animationend. Transform and opacity only. */
.episode-guide-meta.episode-guide-landed,
.episode-guide-item.episode-guide-landed {
    opacity: 1;
}

.episode-guide-meta.episode-guide-dropping,
.episode-guide-item.episode-guide-dropping {
    animation: episode-guide-drop var(--ghost-ms) var(--ease-expo-out) both;
}

.episode-guide-echo {
    position: absolute;
    left: 0;
    right: 0;
    top: 0;
    pointer-events: none;
    animation: episode-guide-echo var(--ghost-ms) var(--ease-expo-out) both;
}

/* Two-class selectors: they outrank .episode-guide-row's own position
   (a row echo carries that class to look like the row). */
.episode-guide-meta > .episode-guide-echo {
    position: absolute;
    top: 0;
    bottom: 0;
    left: 0;
    right: 0;
    display: block;
}

.episode-guide-item > .episode-guide-echo {
    position: absolute;
    top: 0;
    left: 0;
    right: 0;
    height: var(--episode-guide-row-h);
    margin: 0;
}

.episode-guide-echo--1 {
    --episode-guide-echo-y: var(--ghost-offset-1);
    --episode-guide-echo-a: var(--ghost-alpha-1);
}

.episode-guide-echo--2 {
    --episode-guide-echo-y: var(--ghost-offset-2);
    --episode-guide-echo-a: var(--ghost-alpha-2);
}

@keyframes episode-guide-drop {
    from { opacity: 0; transform: translateY(-12px); }
    35%  { opacity: 1; }
    to   { opacity: 1; transform: translateY(0); }
}

@keyframes episode-guide-echo {
    from { opacity: var(--episode-guide-echo-a); transform: translateY(var(--episode-guide-echo-y)); }
    to   { opacity: 0; transform: translateY(0); }
}

@keyframes episode-guide-fade-in {
    from { opacity: 0; }
    to   { opacity: 1; }
}

/* ─────────── The episode list: its own scroll region ─────────── */
.episode-guide-region {
    position: absolute;
    z-index: 3;
    left: 12px;
    right: 12px;
    top: calc(var(--episode-guide-banner-h) + 8px);
    bottom: calc(var(--episode-guide-band) + var(--episode-guide-foot-h));
    padding: 0 4px;
    overflow-x: hidden;
    overflow-y: auto;
    touch-action: pan-y;
    overscroll-behavior: contain;
    -webkit-overflow-scrolling: touch;
    scrollbar-width: none;
}

.episode-guide-region::-webkit-scrollbar {
    display: none;
}

.episode-guide-list {
    position: relative;
    list-style: none;
    margin: 0;
    padding: calc(4px + var(--episode-guide-lead, 0px)) 0 8px;
    display: flex;
    flex-direction: column;
    gap: 6px;
}

.episode-guide-item {
    position: relative;
    opacity: 0;
}

.episode-guide-row {
    position: relative;
    display: flex;
    align-items: center;
    gap: 12px;
    width: 100%;
    height: var(--episode-guide-row-h);
    margin: 0;
    padding: 0 14px 0 8px;
    border: 1px solid var(--row-line);
    border-radius: 10px;
    background: var(--row-fill);
    color: var(--text-primary);
    font: inherit;
    text-align: left;
    cursor: pointer;
    -webkit-appearance: none;
    appearance: none;
    -webkit-tap-highlight-color: transparent;
    transition: border-color 0.5s var(--ease-silk), background-color 0.5s var(--ease-silk);
}

.episode-guide-row:focus-visible,
.episode-guide-play:focus-visible {
    outline: 2px solid var(--focus-ring);
    outline-offset: 2px;
}

.episode-guide-row:focus-visible {
    outline-offset: -3px;
}

.episode-guide-item.episode-guide-open .episode-guide-row {
    border-color: var(--gilt-line-strong);
    background: rgba(16, 24, 48, 0.94);
}

.episode-guide-num {
    flex: 0 0 24px;
    font-family: var(--font-display);
    font-weight: 600;
    font-size: 1.2rem;
    line-height: 1;
    text-align: center;
    color: var(--accent-warm);
}

.episode-guide-thumb {
    position: relative;
    flex: 0 0 40px;
    width: 40px;
    height: 40px;
    border-radius: 6px;
    overflow: hidden;
    background: var(--bg-elevated);
}

.episode-guide-thumb img {
    display: block;
    width: 100%;
    height: 100%;
    object-fit: cover;
    object-position: 50% 30%;
}

.episode-guide-thumb.episode-guide-broken img {
    visibility: hidden;
}

.episode-guide-name {
    flex: 1 1 auto;
    min-width: 0;
    font-family: var(--font-body);
    font-weight: 500;
    font-size: 0.92rem;
    line-height: 1.25;
    overflow-wrap: anywhere;
    display: -webkit-box;
    -webkit-line-clamp: 2;
    -webkit-box-orient: vertical;
    overflow: hidden;
}

/* The "seen" dot: decoration. Dim until the row has been opened and folded. */
.episode-guide-dot {
    flex: 0 0 7px;
    width: 7px;
    height: 7px;
    border-radius: 50%;
    background: rgba(216, 207, 196, 0.18);
    pointer-events: none;
    transition: background-color 0.5s var(--ease-silk), box-shadow 0.5s var(--ease-silk);
}

.episode-guide-item.episode-guide-seen .episode-guide-dot {
    background: var(--accent-warm-bright);
    box-shadow: 0 0 8px 1px rgba(242, 196, 160, 0.55);
}

/* The ring on the next episode to open: decoration on a real control. The
   outer span arrives (0.4 s after the hold cue, and again each time it moves
   to a new row); the inner one breathes. Parked unless shown. */
.episode-guide-ring {
    position: absolute;
    inset: -4px;
    border-radius: 13px;
    pointer-events: none;
    opacity: 0;
    transition: opacity 0.3s var(--ease-silk);
}

.episode-guide-ring-glow {
    position: absolute;
    inset: 0;
    border-radius: inherit;
    box-shadow: 0 0 0 1.5px var(--row-next-ring), 0 0 16px rgba(242, 196, 160, 0.18);
    opacity: 0.55;
}

.episode-guide--held:not(.episode-guide--released) .episode-guide-item.episode-guide-next .episode-guide-ring {
    opacity: 1;
    transition: opacity 0.6s var(--ease-silk) 0.4s;
}

.episode-guide--held:not(.episode-guide--released) .episode-guide-item.episode-guide-next .episode-guide-ring-glow {
    animation: episode-guide-breath 2.4s var(--ease-breath) 0.4s infinite alternate;
}

@keyframes episode-guide-breath {
    from { opacity: 0.55; }
    to   { opacity: 1; }
}

/* ─────────── An opened episode: the photo whole, the line beneath ───────────
   grid-template-rows 0fr -> 1fr: the panel takes its content's height. It
   opens on expo-out in 0.7 s and folds back on silk in 0.5 s. */
.episode-guide-panel {
    display: grid;
    grid-template-rows: 0fr;
    opacity: 0;
    transition: grid-template-rows 0.5s var(--ease-silk), opacity 0.35s var(--ease-silk);
}

.episode-guide-item.episode-guide-open .episode-guide-panel {
    grid-template-rows: 1fr;
    opacity: 1;
    transition: grid-template-rows 0.7s var(--ease-expo-out), opacity 0.5s var(--ease-silk);
}

.episode-guide-clip {
    min-height: 0;
    overflow: hidden;
}

.episode-guide-body {
    display: flex;
    flex-direction: column;
    gap: 8px;
    padding: 8px 2px 12px;
}

/* The box is at most 36vh (the script fits it to the region it opens in);
   the photo is contained, never cropped, on a dim plate. */
.episode-guide-shot {
    position: relative;
    height: var(--episode-guide-shot-h, min(36vh, 220px));
    border-radius: 8px;
    overflow: hidden;
    background: var(--plate-dim);
}

.episode-guide-photo {
    display: block;
    width: 100%;
    height: 100%;
    object-fit: contain;
}

.episode-guide-shot-title {
    position: absolute;
    inset: 0;
    display: none;
    align-items: center;
    justify-content: center;
    padding: 0 18px;
    background: var(--bg-elevated);
    font-family: var(--font-display);
    font-weight: 600;
    font-size: 1.15rem;
    line-height: 1.25;
    text-align: center;
    color: var(--text-primary);
}

.episode-guide-shot.episode-guide-broken .episode-guide-photo {
    visibility: hidden;
}

.episode-guide-shot.episode-guide-broken .episode-guide-shot-title {
    display: flex;
}

.episode-guide-line {
    margin: 0;
    padding: 0 4px;
    font-family: var(--font-display);
    font-style: italic;
    font-weight: 400;
    font-size: clamp(0.94rem, 4.2vw, 1.04rem);
    line-height: 1.38;
    color: var(--text-primary);
    overflow-wrap: anywhere;
}

.episode-guide-line:empty {
    display: none;
}

/* ─────────── The band above the badge: hint, button, outro ─────────── */
.episode-guide-foot {
    position: absolute;
    z-index: 4;
    left: 18px;
    right: 18px;
    bottom: var(--episode-guide-band);
    height: var(--episode-guide-foot-h);
    display: grid;
    place-items: center;
    pointer-events: none;
}

.episode-guide-foot > * {
    grid-area: 1 / 1;
}

.episode-guide-hint {
    margin: 0;
    font-family: var(--font-body);
    font-weight: 500;
    font-size: 0.74rem;
    line-height: 1.2;
    letter-spacing: 0.2em;
    text-transform: uppercase;
    color: var(--text-secondary);
    opacity: 0;
    transition: opacity 0.35s var(--ease-silk);
}

.episode-guide--held .episode-guide-hint {
    opacity: 1;
    transition: opacity 0.6s var(--ease-silk) 1.2s;
}

.episode-guide--opened .episode-guide-hint,
.episode-guide--released .episode-guide-hint {
    opacity: 0;
    transition: opacity 0.35s var(--ease-silk) 0s;
}

.episode-guide-play {
    pointer-events: auto;
    min-height: 44px;
    margin: 0;
    padding: 0 24px;
    border: 0;
    border-radius: 999px;
    background: var(--btn-fill);
    color: var(--btn-ink);
    font-family: var(--font-body);
    font-weight: 600;
    font-size: 0.9rem;
    line-height: 1;
    letter-spacing: 0.04em;
    box-shadow: var(--btn-shadow);
    cursor: pointer;
    -webkit-appearance: none;
    appearance: none;
    -webkit-tap-highlight-color: transparent;
    opacity: 0;
}

.episode-guide-play[hidden] {
    display: none;
}

.episode-guide--can-play .episode-guide-play {
    animation: episode-guide-rise 0.6s var(--ease-expo-out) both;
}

.episode-guide--can-play.episode-guide--released .episode-guide-play {
    animation: episode-guide-fade-out 0.35s var(--ease-silk) both;
    pointer-events: none;
}

@keyframes episode-guide-rise {
    from { opacity: 0; transform: translateY(8px); }
    to   { opacity: 1; transform: none; }
}

@keyframes episode-guide-fade-out {
    from { opacity: 1; }
    to   { opacity: 0; }
}

/* The outro: automatic, and it reads as such (no glyph, no ring, no thumb). */
.episode-guide-outro {
    display: flex;
    flex-direction: column;
    align-items: center;
    gap: 4px;
    pointer-events: none;
}

.episode-guide-outro-line,
.episode-guide-outro-sub {
    margin: 0;
    text-align: center;
    opacity: 0;
    transform: translateY(4px);
    transition: opacity 0.7s var(--ease-silk), transform 0.9s var(--ease-expo-out);
}

.episode-guide-outro-line {
    font-family: var(--font-display);
    font-style: italic;
    font-size: 1rem;
    line-height: 1.2;
    color: var(--text-primary);
}

.episode-guide-outro-sub {
    font-family: var(--font-body);
    font-weight: 500;
    font-size: 0.72rem;
    line-height: 1.2;
    letter-spacing: 0.2em;
    text-transform: uppercase;
    color: var(--text-secondary);
}

.episode-guide--outro1 .episode-guide-outro-line,
.episode-guide--outro2 .episode-guide-outro-sub {
    opacity: 1;
    transform: none;
}

.episode-guide-fill {
    display: block;
    width: 132px;
    height: 2px;
    margin-top: 2px;
    border-radius: 1px;
    overflow: hidden;
    background: var(--progress-track);
    opacity: 0;
    transition: opacity 0.5s var(--ease-silk);
}

.episode-guide-fill-bar {
    display: block;
    width: 100%;
    height: 100%;
    background: var(--progress-fill);
    transform: scaleX(0);
    transform-origin: left center;
}

.episode-guide--outro2 .episode-guide-fill {
    opacity: 1;
}

.episode-guide--outro2 .episode-guide-fill-bar {
    transform: scaleX(1);
    transition: transform 3s linear;
}

/* ─────────── The exit ─────────── */

/* Post +1.0 s: the seen dots pulse in a wave, 0.12 s apart. */
.episode-guide--wave .episode-guide-item.episode-guide-seen .episode-guide-dot {
    animation: episode-guide-pulse 0.55s var(--ease-silk) calc(var(--episode-guide-i, 0) * 0.12s) both;
}

@keyframes episode-guide-pulse {
    0%   { transform: scale(1); }
    45%  { transform: scale(2.1); }
    100% { transform: scale(1); }
}

/* Post +6.4 s: the text leaves, the rows ghost upward and out. */
.episode-guide--leaving .episode-guide-head,
.episode-guide--leaving .episode-guide-foot {
    opacity: 0;
    transform: translateY(-14px);
    transition: opacity 0.9s var(--ease-silk), transform 1.2s var(--ease-expo-out);
}

.episode-guide--leaving .episode-guide-item {
    opacity: 0;
    transform: translateY(-36px);
    transition:
        opacity 0.7s var(--ease-silk) calc(var(--episode-guide-i, 0) * 0.05s),
        transform 1s var(--ease-expo-out) calc(var(--episode-guide-i, 0) * 0.05s);
}

/* ...and the banner photo grows to the whole screen (6.4-12.0 s) and rests. */
.episode-guide--grow .episode-guide-banner {
    -webkit-clip-path: inset(0 0 0 0);
    clip-path: inset(0 0 0 0);
    transition: -webkit-clip-path 5.6s var(--ease-stage), clip-path 5.6s var(--ease-stage);
}

.episode-guide--grow .episode-guide-banner-dim,
.episode-guide--grow .episode-guide-banner-scrim {
    opacity: 0;
    transition: opacity 3.2s var(--ease-silk) 0.8s;
}

/* Post +13.0 s: dissolve to the first moment of the playback. */
.episode-guide--dissolve .episode-guide-cut {
    opacity: 1;
    transition: opacity 1.2s var(--ease-silk);
}

/* ─────────── Entry state, instantly (prepare) ─────────── */
.episode-guide-instant,
.episode-guide-instant * {
    transition: none !important;
    animation: none !important;
}

/* ─────────── Reduced motion: the beat plays in full, calmly ───────────
   Ghosts become fades (no echoes), the synopsis fades in whole, a row opens
   as a quick height cross-fade, the dots pulse together, nothing pushes or
   grows: the full-screen photo fades in over the page instead. (The camera
   no-ops by itself.) */
@media (prefers-reduced-motion: reduce) {
    .episode-guide-meta.episode-guide-dropping,
    .episode-guide-item.episode-guide-dropping {
        animation: episode-guide-fade-in 0.6s var(--ease-silk) both;
    }

    .episode-guide-echo {
        display: none;
    }

    .episode-guide-word {
        transform: none;
        transition: opacity 0.8s var(--ease-silk);
    }

    .episode-guide-panel,
    .episode-guide-item.episode-guide-open .episode-guide-panel {
        transition: grid-template-rows 0.25s var(--ease-silk), opacity 0.3s var(--ease-silk);
    }

    .episode-guide--held:not(.episode-guide--released) .episode-guide-item.episode-guide-next .episode-guide-ring-glow {
        animation: none;
        opacity: 0.85;
    }

    .episode-guide--can-play .episode-guide-play {
        animation: episode-guide-fade-in 0.5s var(--ease-silk) both;
    }

    .episode-guide-outro-line,
    .episode-guide-outro-sub {
        transform: none;
    }

    .episode-guide--wave .episode-guide-item.episode-guide-seen .episode-guide-dot {
        animation-delay: 0s;
    }

    .episode-guide--leaving .episode-guide-head,
    .episode-guide--leaving .episode-guide-foot,
    .episode-guide--leaving .episode-guide-item {
        transform: none;
        transition: opacity 0.9s var(--ease-silk);
    }

    .episode-guide--grow .episode-guide-banner {
        -webkit-clip-path: inset(0 0 calc(100% - var(--episode-guide-photo-h)) 0);
        clip-path: inset(0 0 calc(100% - var(--episode-guide-photo-h)) 0);
        transition: none;
    }

    .episode-guide--grow .episode-guide-full {
        opacity: 1;
        transition: opacity 1.6s var(--ease-silk) 0.6s;
    }
}
