/* ==========================================================================
   YouthTrendx Design System — /dora v2
   Ported from design-docs/YouthTrendx Design System.dc.html (v1.1, 20 sections).

   Rule for every contributor: markup in dora_v2 templates carries NO hard-coded
   colour, radius, spacing or duration. Everything resolves through a token
   declared here. If a value you need is missing, add a token — do not inline it.

   cdn bust 2026-08-01a
   ========================================================================== */

@layer tokens, base, components, pages;

/* Modern Chromium can keep the old document painted while the next same-origin
   v2 page renders.  That removes the desktop title-page white/reload flash
   without turning the app into a client-side router or breaking Back/shareable
   URLs.  Older browsers (including the webOS 6 engine) simply ignore this. */
@view-transition {
  navigation: auto;
}

/* Keep the outgoing snapshot opaque: dimming it creates a full-screen flash
   while the incoming snapshot is still transparent. */
::view-transition-old(root) {
  animation: none;
  opacity: 1;
}

::view-transition-new(root) {
  animation: 180ms ease-out both dv-route-in;
}

@keyframes dv-route-in {
  from { opacity: 0; }
}

@media (prefers-reduced-motion: reduce) {
  ::view-transition-old(root),
  ::view-transition-new(root) {
    animation-duration: 1ms;
  }
}

/* ==========================================================================
   @layer tokens
   ========================================================================== */
@layer tokens {

  :root {
    /* --- colour · dark (default) — verbatim from the design doc ----------- */
    --bg:         #0A0A10;
    --surface:    #13131B;
    --card:       #1C1C27;
    --t1:         #F5F5F7;                    /* text/primary   >= 7:1   */
    --t2:         rgba(245,245,247,0.62);     /* text/secondary >= 4.5:1 */
    --t3:         rgba(245,245,247,0.38);     /* text/tertiary          */
    --hair:       rgba(255,255,255,0.10);
    --glassThin:  rgba(22,22,32,0.38);
    --glass:      rgba(18,18,28,0.58);
    --glassThick: rgba(16,16,26,0.78);
    --ring:       #FFFFFF;
    --btn:        #FFFFFF;
    --btnText:    #0A0A10;
    --acc:        oklch(0.74 0.13 245);
    --acc2:       oklch(0.70 0.14 335);
    --danger:     oklch(0.66 0.15 25);
    --ok:         oklch(0.72 0.15 150);
    --shadow:     0 4px 20px rgba(0,0,0,0.35);
    --shadowF:    0 24px 60px rgba(0,0,0,0.55);
    /* Even ambient halo for the floating nav pill + its right-side controls —
       barely offset so it wraps the chip on all sides instead of pooling
       under it. */
    --nav-shadow: 0 2px 18px rgba(0,0,0,0.26), 0 0 14px rgba(0,0,0,0.16);
    /* Liquid-glass fill for the nav + top-right controls: more transparent than
       --glassThin, leaning on the heavy blur for legibility. */
    /* Alpha has to be high enough to MASK content scrolling under the fixed
       nav — the bar re-shows on scroll-up (dora.js:348) directly over headers
       and carousels, and a near-clear fill let them read straight through.
       Blur alone doesn't do it: it diffuses, it doesn't hide. */
    --nav-glass:  rgba(20,20,30,0.58);
    /* Hover wash for an unselected nav tab. It must read against --nav-glass,
       which --glassThick never did: both are the same near-black, so the fill
       was a shade DARKER than the pill it sat on and never showed. The surface
       tokens are for panels on the page background, not a surface stacked on
       another glass surface. This is the design canvas's own nav hover value
       (Detail Page.dc.html). Composited it lands near #3A3A44 — still >10:1
       against --t1 text. */
    --nav-hover:  rgba(255,255,255,0.14);
    --edge:       rgba(255,255,255,0.16);
    --glow1:      rgba(80,120,255,0.07);
    --glow2:      rgba(255,80,180,0.05);

    /* --- spacing · base unit 8 ------------------------------------------- */
    --s1:  4px;   --s2:  8px;   --s3: 12px;   --s4: 16px;
    --s5: 24px;   --s6: 32px;   --s7: 48px;   --s8: 64px;   --s9: 96px;

    /* --- radius ---------------------------------------------------------- */
    --r-card:   16px;  --r-card-md: 12px;  --r-card-sm: 10px;
    --r-sheet:  28px;  --r-sheet-md: 20px; --r-sheet-sm: 16px;
    --r-pill:  999px;
    --control-h: 44px; --control-compact: 36px; --control-lg: 48px;
    --control-gap: var(--s2); --control-radius: var(--r-card-md);

    /* --- motion (§06) ---------------------------------------------------- */
    --m-instant: 120ms;
    --m-focus:   220ms;
    --m-move:    320ms;
    --m-ambient: 600ms;
    --e-out:     cubic-bezier(0.22, 1, 0.36, 1);
    --e-spring:  cubic-bezier(0.34, 1.4, 0.44, 1);   /* spring(1,220,26) approx */
    --e-move:    cubic-bezier(0.32, 0.72, 0, 1);
    --e-inout:   cubic-bezier(0.4, 0, 0.2, 1);

    /* --- type scale · TV / desktop / mobile (§03) ------------------------- */
    --fz-display:  clamp(34px, 5vw, 76px);
    --fz-title1:   clamp(26px, 2.6vw, 48px);
    --fz-title2:   clamp(19px, 1.6vw, 34px);
    --fz-headline: clamp(16px, 1.1vw, 29px);
    --fz-body:     clamp(15px, 0.9vw, 27px);
    --fz-label:    clamp(11px, 0.6vw, 24px);
    --fz-mono:     11px;

    --ff: "Manrope", -apple-system, BlinkMacSystemFont, "SF Pro Display",
          "SF Pro Text", "Helvetica Neue", Helvetica, Arial, sans-serif;
    --ff-mono: ui-monospace, SFMono-Regular, "SF Mono", Menlo, Consolas, monospace;

    /* --- layout ---------------------------------------------------------- */
    --page-max:  1560px;
    --page-pad:  64px;    /* desktop sides; 90px TV safe area, 16px mobile */
    --gutter:    20px;
    --row-gap:   48px;    /* row -> row  */
    --head-gap:  16px;    /* header -> cards */
    /* Clearance under the fixed nav for a page that starts with content rather
       than a hero. Pages MUST use .dv-page, never an inline padding-top — an
       inline style outranks every media query, which is exactly how the
       headings ended up under the mobile nav. */
    --page-top:  120px;

    /* --- focus (§07) ------------------------------------------------------ */
    --focus-scale: 1.08;   /* TV */
    --hover-scale: 1.04;   /* desktop hover maps to the same treatment */
    --focus-ring:  4px;
    --focus-off:   3px;
  }

  /* --- colour · light ---------------------------------------------------- */
  :root[data-theme="light"] {
    --bg:         #F4F4F7;
    --surface:    #FFFFFF;
    --card:       #ECECF1;
    --t1:         #17171E;
    --t2:         rgba(23,23,30,0.62);
    /* DEVIATION from the design doc, which specifies 0.40 here.
       At 0.40 this composites to 2.47:1 on --card / 2.49:1 on --bg — under the
       3:1 floor, and --t3 carries real reading text (timestamps, episode
       eyebrows, metadata). Dark mode's 0.38 clears only because the dark ground
       is more forgiving. 0.50 gives 3.24-3.40:1 across bg/surface/card while
       staying clearly below --t2, honouring the doc's own accessibility
       contract (section 20). Verified by scripts/check_dora_contrast.py. */
    --t3:         rgba(23,23,30,0.50);
    --hair:       rgba(0,0,0,0.09);
    --glassThin:  rgba(255,255,255,0.50);
    --glass:      rgba(250,250,252,0.68);
    --glassThick: rgba(252,252,254,0.82);
    --ring:       #17171E;
    --btn:        #17171E;
    --btnText:    #FFFFFF;
    --acc:        oklch(0.54 0.13 250);
    --acc2:       oklch(0.54 0.14 335);
    --danger:     oklch(0.55 0.15 25);
    --ok:         oklch(0.52 0.15 150);
    --shadow:     0 4px 20px rgba(20,20,40,0.10);
    --shadowF:    0 24px 60px rgba(20,20,40,0.20);
    --nav-shadow: 0 2px 18px rgba(20,20,40,0.12), 0 0 14px rgba(20,20,40,0.08);
    --nav-glass:  rgba(255,255,255,0.72);
    /* Mirror image: the light pill is near-white, so the wash has to darken
       rather than lighten. --glassThick was white-on-white here. */
    --nav-hover:  rgba(20,20,40,0.10);
    --edge:       rgba(255,255,255,0.85);
    --glow1:      rgba(80,120,255,0.10);
    --glow2:      rgba(255,80,180,0.07);
  }
}

/* ==========================================================================
   @layer base
   ========================================================================== */
@layer base {

  *, *::before, *::after { box-sizing: border-box; }

  /* The `hidden` attribute must actually hide things.
     The browser's own `[hidden]{display:none}` is a *user-agent* rule, so ANY
     author rule that sets display beats it regardless of specificity. The nav
     search field carries .dv-nav__pill (display:flex), so `hidden` did nothing
     and the field sat permanently beside the nav instead of replacing it on
     demand. One rule here rather than a `.is-hidden` class per component. */
  [hidden] { display: none !important; }

  html { -webkit-text-size-adjust: 100%; }

  body {
    margin: 0;
    font-family: var(--ff);
    font-size: var(--fz-body);
    line-height: 1.55;
    background: var(--bg);
    color: var(--t1);
    -webkit-font-smoothing: antialiased;
    -moz-osx-font-smoothing: grayscale;
    /* Guards the known mobile failure mode: a nowrap title blowing out a grid
       track pushes the whole page sideways. Grid items also carry min-width:0. */
    overflow-x: hidden;
    transition: background var(--m-ambient) var(--e-inout),
                color      var(--m-ambient) var(--e-inout);
  }

  a { color: var(--acc); text-decoration: none; }
  a:hover { text-decoration: underline; }

  img, svg, video { max-width: 100%; }
  img { display: block; }

  button, input, select, textarea { font: inherit; color: inherit; }

  /* Type ------------------------------------------------------------------ */
  .dv-display  { font-size: var(--fz-display);  font-weight: 800; letter-spacing: -0.03em;  line-height: 1.03; }
  .dv-title1   { font-size: var(--fz-title1);   font-weight: 700; letter-spacing: -0.02em;  line-height: 1.1; }
  .dv-title2   { font-size: var(--fz-title2);   font-weight: 600; letter-spacing: -0.01em; }
  .dv-headline { font-size: var(--fz-headline); font-weight: 600; }
  .dv-body     { font-size: var(--fz-body);     font-weight: 400; color: var(--t2); line-height: 1.55; }
  .dv-label    { font-size: var(--fz-label);    font-weight: 600; letter-spacing: 0.09em;
                 text-transform: uppercase; color: var(--t2); }
  .dv-mono     { font-family: var(--ff-mono);   font-size: var(--fz-mono);
                 letter-spacing: 0.1em; color: var(--t3); }

  .dv-t2 { color: var(--t2); }
  .dv-t3 { color: var(--t3); }
  .dv-clamp2 {
    display: -webkit-box; -webkit-line-clamp: 2; -webkit-box-orient: vertical;
    overflow: hidden; white-space: normal;
  }

  /* Canonical focus ring (§07). Applied to anything keyboard-reachable. */
  :where(a, button, [tabindex]):focus-visible {
    outline: none;
    box-shadow: 0 0 0 var(--focus-off) var(--bg),
                0 0 0 calc(var(--focus-off) + var(--focus-ring)) var(--ring);
    border-radius: var(--r-card-sm);
  }

  /* Accessibility contract (§20) ------------------------------------------ */

  /* Reduce Motion: everything collapses to a 120ms opacity fade. Ambient
     rotation and focus tilt stop entirely. */
  @media (prefers-reduced-motion: reduce) {
    *, *::before, *::after {
      animation-duration: 0.01ms !important;
      animation-iteration-count: 1 !important;
      transition-duration: var(--m-instant) !important;
      transition-property: opacity !important;
      scroll-behavior: auto !important;
    }
  }

  /* Increase Contrast: 2px hairlines, secondary text up to 80%. */
  @media (prefers-contrast: more) {
    :root {
      --hair: rgba(255,255,255,0.28);
      --t2:   rgba(245,245,247,0.80);
    }
    :root[data-theme="light"] {
      --hair: rgba(0,0,0,0.28);
      --t2:   rgba(23,23,30,0.80);
    }
    .dv-glass, .dv-glass-thin, .dv-glass-thick { border-width: 2px; }
  }

  /* Reduce Transparency -> solid fallbacks. backdrop-filter is also absent on
     some engines, so the solid path doubles as the no-support fallback. */
  @media (prefers-reduced-transparency: reduce) {
    .dv-glass, .dv-glass-thin, .dv-glass-thick {
      backdrop-filter: none !important;
      -webkit-backdrop-filter: none !important;
      background: var(--surface) !important;
    }
  }
  @supports not (backdrop-filter: blur(1px)) {
    .dv-glass, .dv-glass-thin, .dv-glass-thick { background: var(--surface); }
  }

  .dv-sr {
    position: absolute; width: 1px; height: 1px; padding: 0; margin: -1px;
    overflow: hidden; clip: rect(0 0 0 0); white-space: nowrap; border: 0;
  }
}

/* ==========================================================================
   @layer components
   ========================================================================== */
@layer components {

  /* --- glass materials (§02) --------------------------------------------- */
  .dv-glass-thin, .dv-glass, .dv-glass-thick {
    border: 1px solid var(--hair);
    box-shadow: inset 0 1px 0 var(--edge);
  }
  .dv-glass-thin {
    background: var(--glassThin);
    backdrop-filter: blur(24px) saturate(1.7);
    -webkit-backdrop-filter: blur(24px) saturate(1.7);
  }
  .dv-glass {
    background: var(--glass);
    backdrop-filter: blur(32px) saturate(1.8);
    -webkit-backdrop-filter: blur(32px) saturate(1.8);
    box-shadow: inset 2px 2px 1px 0 rgba(255,255,255,0.14),
                inset -1px -1px 1px 1px rgba(255,255,255,0.06);
  }
  .dv-glass-thick {
    background: var(--glassThick);
    backdrop-filter: blur(48px) saturate(1.8);
    -webkit-backdrop-filter: blur(48px) saturate(1.8);
  }

  .dv-surface {
    background: var(--surface);
    border: 1px solid var(--hair);
    border-radius: var(--r-sheet-md);
    box-shadow: inset 0 1px 0 var(--edge);
  }

  /* --- page shell -------------------------------------------------------- */
  .dv-wrap { max-width: var(--page-max); margin: 0 auto; padding: 0 var(--page-pad); }
  /* Standard page shell: clears the nav, breathes at the bottom. */
  .dv-page { padding-top: var(--page-top); padding-bottom: var(--s9); }

  /* Auto-fitting card grid. The track floor is a custom property so a page can
     tune it inline while the phone breakpoint (in @layer pages) can still
     override the columns outright — an inline grid-template-columns could not
     be overridden at all, which is how several pages became one tall column. */
  .dv-autogrid {
    display: grid; gap: var(--gutter);
    grid-template-columns: repeat(auto-fill, minmax(var(--gmin, 200px), 1fr));
  }
  .dv-autogrid > * { min-width: 0; }
  /* Poster cards carry a fixed width (168px, or 232px when ranked) so they sit
     correctly in a horizontal shelf. Inside the responsive autogrid that fixed
     width overflows a narrow column — most visibly Top Rated's ranked cards on
     a 2-across phone, which shoved the whole page sideways — so here they fill
     their track instead. !important beats the macro's inline width. */
  .dv-autogrid .dv-card--poster { width: 100% !important; max-width: 100%; }
  .dv-bleed { padding: 0; }

  /* --- Button (§12) · 64 TV / 44 desktop / 50 mobile ---------------------- */
  .dv-btn {
    display: inline-flex; align-items: center; justify-content: center; gap: var(--control-gap);
    padding: 12px 26px; border-radius: var(--r-pill);
    font-size: 13.5px; font-weight: 700; line-height: 1;
    border: 0; cursor: pointer; text-decoration: none; white-space: nowrap;
    transition: transform var(--m-focus) var(--e-spring),
                background var(--m-instant) var(--e-out),
                opacity var(--m-instant) var(--e-out);
  }
  .dv-btn:hover { transform: scale(1.05); text-decoration: none; }
  .dv-btn:active { transform: scale(0.98); }
  .dv-btn[disabled], .dv-btn.is-disabled {
    opacity: 0.38; pointer-events: none;   /* disabled is skipped by focus */
  }

  .dv-btn--primary { background: var(--btn); color: var(--btnText); }
  .dv-btn--glass {
    background: var(--glassThin); color: var(--t1); font-weight: 600;
    border: 1px solid var(--hair);
    /* Radius sized to the control, not to the page — see .dv-btn--icon. */
    backdrop-filter: blur(14px); -webkit-backdrop-filter: blur(14px);
    box-shadow: inset 0 1px 0 var(--edge);
  }
  .dv-btn--quiet {
    background: none; padding: 0; color: var(--t2);
    font-weight: 500; font-size: 13px; gap: 5px;
  }
  .dv-btn--quiet:hover { color: var(--t1); transform: none; }
  .dv-btn--danger { background: var(--danger); color: #fff; }

  /* Same even-glass treatment as the nav controls. The old diagonal inset
     speculars (bright top-left, dim bottom-right) read as an uneven grey smear
     on a round button — one even top sheen instead, so the fill looks uniform
     from every side. Shared token, so page icons and nav icons can't drift. */
  .dv-btn--icon {
    width: 44px; height: 44px; padding: 0; border-radius: 50%;
    background: var(--nav-glass); color: var(--t1);
    border: 1px solid var(--edge);
    /* 14px, not 36px: the radius was wider than the 44px button itself, which
       samples far outside the control for no visible difference while costing
       full price on every scroll frame (these live in the fixed nav, so they
       repaint constantly). Right-size the radius to the element. */
    backdrop-filter: blur(14px) saturate(1.8);
    -webkit-backdrop-filter: blur(14px) saturate(1.8);
    box-shadow: inset 0 1px 0 0 rgba(255,255,255,0.16),
                var(--nav-shadow);
  }
  .dv-btn--icon {
    /* The §05 press morph swells the control on an overshoot spring. */
    transition: transform var(--m-focus) var(--e-spring),
                box-shadow var(--m-focus) var(--e-out),
                background var(--m-instant) var(--e-out);
  }
  .dv-btn--icon:hover { transform: scale(1.1); }
  /* Press = liquid-glass morph (§05): swell past 1 and bloom a specular rim on
     an overshoot spring, rather than the flat scale-down of a normal button. */
  .dv-btn--icon:active {
    transform: scale(1.14);
    box-shadow: inset 0 1px 0 0 rgba(255,255,255,0.34),
                0 6px 20px rgba(0,0,0,0.28);
  }
  @media (prefers-reduced-motion: reduce) {
    .dv-btn--icon:active { transform: none; }
  }
  .dv-btn--lg { padding: 14px 34px; font-size: 15px; box-shadow: var(--shadow); }

  /* Loading keeps the button's width — never let it collapse mid-request. */
  .dv-spinner {
    width: 13px; height: 13px; flex: none; border-radius: 50%;
    border: 2px solid currentColor; border-top-color: transparent;
    animation: dv-spin 0.8s linear infinite;
  }
  @keyframes dv-spin { to { transform: rotate(360deg); } }

  /* --- Badges & chips (§12) ---------------------------------------------- */
  .dv-badge {
    display: inline-flex; align-items: center; gap: 6px;
    padding: 6px 14px; border-radius: var(--r-pill);
    background: var(--glassThin); border: 1px solid var(--hair);
    box-shadow: inset 0 1px 0 var(--edge);
    font-size: 9.5px; font-weight: 600; letter-spacing: 0.1em;
    text-transform: uppercase; white-space: nowrap;
  }
  .dv-badge--live::before {
    content: ""; width: 6px; height: 6px; border-radius: 50%; background: var(--acc2);
  }
  .dv-badge--danger {
    background: color-mix(in oklab, var(--danger) 18%, transparent);
    border-color: color-mix(in oklab, var(--danger) 35%, transparent);
    color: var(--danger);
  }

  .dv-rating {
    border: 1px solid var(--hair); border-radius: 6px;
    padding: 4px 9px; font-size: 10.5px; color: var(--t2); white-space: nowrap;
  }

  .dv-chip {
    display: inline-flex; align-items: center; gap: 6px;
    padding: 8px 18px; border-radius: var(--r-pill);
    background: var(--glassThin); border: 1px solid var(--hair);
    font-size: 12.5px; color: var(--t2); cursor: pointer;
    transition: color var(--m-instant) var(--e-out),
                background var(--m-instant) var(--e-out);
  }
  .dv-chip:hover { color: var(--t1); text-decoration: none; }
  .dv-chip.is-on {
    background: color-mix(in oklab, var(--acc) 20%, transparent);
    border-color: color-mix(in oklab, var(--acc) 45%, transparent);
    color: var(--t1); font-weight: 600;
  }

  /* --- MetadataRow · one line, truncates from the end -------------------- */
  .dv-meta {
    display: flex; align-items: center; flex-wrap: wrap; gap: 10px;
    font-size: 12.5px; color: var(--t2); min-width: 0;
  }
  .dv-meta > * { flex: none; }
  .dv-meta__text { overflow: hidden; text-overflow: ellipsis; white-space: nowrap; min-width: 0; }

  /* --- SegmentedControl / SeasonSelector (§13) --------------------------- */
  .dv-seg {
    display: inline-flex; height:var(--control-h); padding:4px; align-items:stretch; border-radius: var(--r-pill);
    background: var(--glassThin); border: 1px solid var(--hair);
  }
  .dv-seg > * {
    display:inline-flex;align-items:center;justify-content:center;line-height:1.2;
    padding:0 var(--s4); border-radius: var(--r-pill); border: 0; background: none;
    font-size: 12.5px; color: var(--t2); cursor: pointer; white-space: nowrap;
    transition: color var(--m-instant) var(--e-out),
                background var(--m-focus) var(--e-spring);
  }
  .dv-seg > *:hover { color: var(--t1); text-decoration: none; }
  .dv-seg > .is-on { background: var(--btn); color: var(--btnText); font-weight: 600; }

  /* --- Shelf + RowHeader ------------------------------------------------- */
  .dv-row { margin-bottom: var(--row-gap); }
  .dv-row__head {
    display: flex; align-items: baseline; justify-content: space-between;
    gap: var(--s4); margin-bottom: var(--head-gap); min-width: 0; flex-wrap:wrap;
  }
  .dv-row__title { font-size: var(--fz-title2); font-weight: 600; letter-spacing: -0.01em; min-width: 0; }

  /* Shelves bleed off BOTH edges to the page gutter, then re-inset their
     content by the same amount. Bleeding only the right left a dead band at
     --page-pad on the left where cards hard-clipped mid-poster — the "invisible
     black borders" that cut the leading/trailing cards. Now the first card sits
     at the gutter at rest and slides cleanly to the true viewport edge on scroll,
     symmetrically on both sides. scroll-padding keeps snap targets off the edge. */
  .dv-shelf {
    display: flex; gap: var(--s6); overflow-x: auto; scroll-behavior: smooth;
    scrollbar-width: none; padding: var(--s3) 0;
    margin-inline: calc(var(--page-pad) * -1);
    padding-inline: var(--page-pad);
    scroll-padding-inline: var(--page-pad);
    scroll-snap-type: x proximity;
  }
  .dv-shelf::-webkit-scrollbar { display: none; }
  .dv-shelf > * { flex: none; scroll-snap-align: start; }
  .dv-shelf--portrait { gap: var(--s5); }

  .dv-grid {
    display: grid; gap: var(--gutter);
    grid-template-columns: repeat(auto-fill, minmax(160px, 1fr));
  }
  /* Every grid child needs this or a nowrap title blows out the track. */
  .dv-grid > *, .dv-shelf > * { min-width: 0; }

  /* --- Cards ------------------------------------------------------------- */
  .dv-card { cursor: pointer; min-width: 0; display: block; text-decoration: none; color: inherit; }
  .dv-card:hover { text-decoration: none; }

  /* Every part below is a <span> inside an <a>, because a card is one link and
     nesting block elements in an inline anchor is invalid. Spans default to
     `display:inline`, and an inline box IGNORES aspect-ratio, width, height and
     vertical margin — which collapsed every poster to zero height and let
     nowrap titles overlap their neighbours. These `display:block` declarations
     are load-bearing; do not drop them. */
  .dv-card__art {
    display: block;
    position: relative; overflow: hidden; border-radius: var(--r-card-md);
    background: var(--card);
    transition: transform var(--m-focus) var(--e-spring),
                box-shadow var(--m-focus) var(--e-out),
                filter var(--m-focus) var(--e-out);
  }
  .dv-card__art > img {
    position: absolute; inset: 0; width: 100%; height: 100%; object-fit: cover;
  }
  /* Canonical focus/hover treatment (§07): scale + ring at offset + lift + 6%. */
  .dv-card:focus-visible .dv-card__art {
    transform: scale(var(--hover-scale));
    box-shadow: 0 0 0 var(--focus-off) var(--bg),
                0 0 0 calc(var(--focus-off) + var(--focus-ring)) var(--ring),
                var(--shadowF);
    filter: brightness(1.06);
  }
  /* Input capability media queries are unreliable on Windows hybrid devices:
     Chrome/Edge can keep reporting the touchscreen as the primary (and only)
     pointer even while a mouse or trackpad is producing the current event.
     dora.js therefore tracks the input that was actually used and applies this
     class. A touch immediately removes it, preventing sticky tap-hover on
     phones and when the same hybrid switches back to its screen. */
  .dv-pointer-fine .dv-card:hover .dv-card__art {
    transform: scale(var(--hover-scale));
    box-shadow: 0 0 0 var(--focus-off) var(--bg),
                0 0 0 calc(var(--focus-off) + var(--focus-ring)) var(--ring),
                var(--shadowF);
    filter: brightness(1.06);
  }
  /* Touch: no transition either, so a tap can't animate the art at all. Use
     the same event-driven class rather than the unreliable capability query. */
  html:not(.dv-pointer-fine) .dv-card__art { transition: none; }
  .dv-card:focus-visible { outline: none; box-shadow: none; }

  .dv-card--poster    .dv-card__art { aspect-ratio: 2/3; }
  .dv-card--landscape .dv-card__art { aspect-ratio: 16/9; border-radius: var(--r-card); }
  .dv-card--square    .dv-card__art { aspect-ratio: 1; }

  .dv-card__title {
    display: block;
    font-size: 14.5px; font-weight: 600; margin: 12px 0 4px;
    overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
  }
  .dv-card__sub {
    display: block; font-size: 12px; color: var(--t2); line-height: 1.55;
    overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
  }
  /* Bare cards (home rails): the key art already says the title, so the only
     caption is the dominant genre, set inside the artwork over its own scrim.
     The scrim is on the label rather than the art so posters that are light at
     the bottom still read, and cards without a genre stay completely clean. */
  .dv-card__genre {
    position: absolute; left: 0; right: 0; bottom: 0;
    padding: 34px 10px 11px;
    display: block; text-align: center;
    font-size: 12px; font-weight: 500; letter-spacing: 0.01em;
    color: rgba(255,255,255,0.86);
    text-shadow: 0 1px 10px rgba(0,0,0,0.6);
    background: linear-gradient(0deg, rgba(6,6,12,0.72), rgba(6,6,12,0));
    overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
    pointer-events: none;
  }
  /* A bare card is nothing but its artwork, so the rail's vertical rhythm has
     to come from the card itself — the caption used to provide it. */
  .dv-card--bare .dv-card__art { margin-bottom: 4px; }

  /* --- Person card (Cast & Crew) ----------------------------------------
     Circular headshot, name, role. Used on the detail rail, the full-cast
     page and the person page, so it lives here rather than in a template. */
  .dv-person { display: block; text-align: center; text-decoration: none;
               color: inherit; min-width: 0; }
  .dv-person:hover { text-decoration: none; }
  .dv-person__ava {
    display: grid; place-items: center; overflow: hidden;
    border-radius: 50%; background: var(--card);
    box-shadow: inset 0 0 0 1px var(--edge);
    transition: transform var(--m-focus) var(--e-spring),
                box-shadow var(--m-focus) var(--e-out);
  }
  .dv-person__ava > img { width: 100%; height: 100%; object-fit: cover; display: block; }
  .dv-person:hover .dv-person__ava, .dv-person:focus-visible .dv-person__ava {
    transform: scale(var(--hover-scale));
    box-shadow: 0 0 0 var(--focus-off) var(--bg),
                0 0 0 calc(var(--focus-off) + var(--focus-ring)) var(--ring),
                var(--shadowF);
  }
  .dv-person:focus-visible { outline: none; }
  .dv-person__ini { font-size: 26px; font-weight: 700; color: var(--t3); }
  .dv-person__name {
    display: block; margin-top: 11px; font-size: 13.5px; font-weight: 600;
    overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
  }
  .dv-person__role {
    display: block; margin-top: 3px; font-size: 12.5px; color: var(--t3);
    overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
  }
  /* Bigger, more legible names now the circles are larger (macro default 140). */
  .dv-person__name { font-size: 14.5px; }
  /* TV: bigger circles for the 10-foot read. Keep the anchor's generous hit
     area, but draw selection on the portrait itself so focus stays circular. */
  body.dv-tv .dv-person { padding: 12px 10px 16px; }
  body.dv-tv .dv-person,
  body.dv-tv .dv-person__ava { width: 180px !important; }
  body.dv-tv .dv-person__ava { height: 180px !important; }
  body.dv-tv .dv-person__ini { font-size: 40px; }
  body.dv-tv .dv-person__name { font-size: 17px; }
  body.dv-tv .dv-person__role { font-size: 15px; }

  .dv-card__eyebrow {
    display: block;
    font-family: var(--ff-mono); font-size: 9.5px; letter-spacing: 0.12em;
    color: var(--t3); margin: 12px 0 4px; text-transform: uppercase;
  }

  /* pill overlaid on artwork (duration, quality, rank)

     NO backdrop-filter here, deliberately. Each blurred element is its own
     compositing layer that must re-sample and re-blur whatever is behind it on
     every scroll frame — and this badge repeats once per card. /dora/v2/watched
     alone rendered 221 of these plus 221 flags: ~450 live blur layers on one
     scrolling page, which is what made scrolling crawl on a TV (v1 renders a
     handful, at a max radius of 14px).

     A blur radius should also never exceed the element it sits on: a 20px blur
     on a 20px-tall pill samples far outside itself for no visible gain. An
     opaque-enough solid is indistinguishable here and costs nothing, so the
     alpha goes up and the filter goes away. Glass is reserved for the large
     chrome surfaces below, where you can actually see content move behind it. */
  .dv-card__pin {
    position: absolute; left: 8px; bottom: 8px;
    display: inline-flex; align-items: center; gap: 5px;
    padding: 4px 11px; border-radius: var(--r-pill);
    background: rgba(20,20,30,0.78); color: #fff;
    border: 1px solid rgba(255,255,255,0.2);
    font-size: 10px; font-weight: 600;
  }
  /* Status flag, top-left of the artwork. Reserved for facts the poster can't
     carry on its own: in cinemas now, not released yet. */
  .dv-flag {
    position: absolute; top: 8px; left: 8px; z-index: 2;
    padding: 4px 9px; border-radius: 7px;
    /* Solid, not glass — per-card, see .dv-card__pin above. */
    background: rgba(20,20,30,0.84); color: #fff;
    border: 1px solid rgba(255,255,255,0.2);
    font-family: var(--ff-mono); font-size: 8.5px; letter-spacing: 0.09em;
    text-transform: uppercase; font-weight: 700; white-space: nowrap;
  }
  .dv-flag--cinema {
    background: color-mix(in oklab, var(--acc) 78%, rgba(0,0,0,0.4));
    border-color: color-mix(in oklab, var(--acc) 55%, transparent);
  }
  .dv-card__progress {
    position: absolute; left: 0; bottom: 0; height: 3px;
    background: var(--acc); border-radius: 0 2px 2px 0;
  }

  /* Artwork fallback. A large share of the newest vault rows have no poster
     yet; a lettered plate reads as deliberate where an empty box reads as a
     failed image. */
  .dv-plate {
    position: absolute; inset: 0; display: flex;
    align-items: center; justify-content: center;
    background: linear-gradient(135deg, var(--card), var(--surface));
    font-size: 40px; font-weight: 800; color: var(--t3);
    letter-spacing: -0.02em; user-select: none;
  }

  /* --- CollectionStack (§20) ---------------------------------------------
     Up to three posters fanned out. They splay on hover (pointer) or on first
     tap (touch — see dora.js), staggered so the fan reads as one motion.
     Ported from the v1 .cstack interaction onto v2 tokens. */
  /* display:block for the same reason as .dv-card__art — this is a <span> and
     an inline box ignores aspect-ratio, so the whole fan collapsed to nothing
     and only the count pill was visible. */
  .dv-stack {
    display: block;
    position: relative; width: 100%; aspect-ratio: 2/3; perspective: 900px;
  }
  .dv-stack__card {
    position: absolute; inset: 0; border-radius: var(--r-card-md); overflow: hidden;
    background: var(--card) center/cover no-repeat;
    border: 0;
    box-shadow: inset 0 0 0 1px rgba(255,255,255,0.10), var(--shadow);
    transform-origin: 50% 92%;
    transition: transform var(--m-move) var(--e-move),
                box-shadow var(--m-focus) var(--e-out);
  }
  .dv-stack__card > img {
    position: absolute; inset: 0; display: block;
    width: 100%; height: 100%; object-fit: cover;
  }
  .dv-stack__card:nth-child(1) { z-index: 3; }
  .dv-stack__card:nth-child(2) { z-index: 2; transition-delay: 0.05s; }
  .dv-stack__card:nth-child(3) { z-index: 1; transition-delay: 0.10s; }

  .dv-stack:hover .dv-stack__card:nth-child(2),
  .dv-stack.is-open .dv-stack__card:nth-child(2) {
    transform: translate3d(-16%, 1.5%, 0) rotate(-5.5deg);
  }
  .dv-stack:hover .dv-stack__card:nth-child(3),
  .dv-stack.is-open .dv-stack__card:nth-child(3) {
    transform: translate3d(16%, 1.5%, 0) rotate(5.5deg);
  }
  .dv-stack:hover .dv-stack__card:nth-child(1),
  .dv-stack.is-open .dv-stack__card:nth-child(1) {
    transform: translate3d(0,-6px,0) scale(1.01);
    box-shadow: inset 0 0 0 1px rgba(255,255,255,0.13), var(--shadowF);
  }

  /* A two-poster franchise needs different geometry from a three-poster fan:
     move both cards away from the centre by the same amount. Previously only
     the second card moved left, so 229 production franchises looked lopsided. */
  .dv-stack--2:hover .dv-stack__card:nth-child(1),
  .dv-stack--2.is-open .dv-stack__card:nth-child(1) {
    transform: translate3d(-8%,-5px,0) rotate(-2.5deg);
  }
  .dv-stack--2:hover .dv-stack__card:nth-child(2),
  .dv-stack--2.is-open .dv-stack__card:nth-child(2) {
    transform: translate3d(8%,0,0) rotate(2.5deg);
  }

  /* The fan splays 13% to each side, so an edge-to-edge stack would collide
     with its neighbours. Inset the stack rather than shrinking the fan. */
  .dv-stack-cell { min-width: 0; display: block; color: inherit; text-decoration: none;
                   position: relative; }
  .dv-stack-cell:hover, .dv-stack-cell:focus-within { z-index: 4; }
  .dv-stack-cell .dv-stack { width: 84%; margin: 0 auto; }
  .dv-stack__count {
    position: absolute; right: 8px; bottom: 8px; z-index: 4;
    padding: 3px 10px; border-radius: var(--r-pill);
    /* Solid, not glass — per-card, see .dv-card__pin above. The collections
       grid renders 85 of these. */
    background: rgba(20,20,30,0.82); color: #fff;
    border: 1px solid rgba(255,255,255,0.18);
    font-size: 11px; font-weight: 700;
  }
  @media (prefers-reduced-motion: reduce) {
    .dv-stack__card { transition: none; }
  }

  /* RankedCard — oversized numeral bleeding behind the poster */
  .dv-card--ranked { display: flex; align-items: flex-end; gap: 2px; }
  .dv-card--ranked .dv-rank {
    font-size: 88px; font-weight: 800; line-height: 0.78; letter-spacing: -0.06em;
    color: var(--surface); -webkit-text-stroke: 2px var(--hair); flex: none;
  }
  /* Light theme: the numeral is --surface (white) outlined in --hair, which is
     only 9% black — white-on-near-white, effectively invisible. The ghosted
     outline is the intended look, so keep the fill and darken the stroke
     rather than filling the numeral in. */
  :root[data-theme="light"] .dv-card--ranked .dv-rank {
    -webkit-text-stroke: 2px rgba(23,23,30,0.34);
  }

  /* --- ProviderBadge / ProviderTile (§11) -------------------------------- */
  .dv-prov {
    display: inline-flex; align-items: center; justify-content: center;
    width: 26px; height: 26px; border-radius: 7px; overflow: hidden; flex: none;
    background: var(--glassThin); border: 1px solid var(--hair);
  }
  .dv-prov img { width: 100%; height: 100%; object-fit: cover; }

  .dv-provtile {
    display: flex; align-items: center; gap: var(--s3);
    padding: var(--s3) var(--s4); border-radius: var(--r-card);
    background: var(--glassThin); border: 1px solid var(--hair);
    box-shadow: inset 0 1px 0 var(--edge);
    cursor: pointer; min-width: 0; text-decoration: none; color: inherit;
    transition: transform var(--m-focus) var(--e-spring);
  }
  .dv-provtile:hover { transform: scale(1.03); text-decoration: none; }
  .dv-provtile__name { font-size: 13px; font-weight: 600; overflow: hidden;
                       text-overflow: ellipsis; white-space: nowrap; min-width: 0; }
  /* Disconnected services desaturate rather than disappear (§18 Library). */
  .dv-provtile.is-off, .dv-card.is-off .dv-card__art { filter: grayscale(1) opacity(0.55); }

  /* --- HeroBillboard (§09) ----------------------------------------------- */
  /* --hero-h is declared alongside height because the trailer below has to
     size itself off the hero's height to cover it (see .dv-hero__trailer).
     Any hero that overrides `height` inline must set --hero-h to match. */
  .dv-hero { position: relative; overflow: hidden; --hero-h: 62vh; }
  .dv-hero--home   { --hero-h: min(88vh, 780px); height: var(--hero-h); }
  /* The detail hero is the first element in the page body, so the fixed nav
     pill (top:18px) landed straight on its artwork and title. Unlike the home
     billboard — where the pill floating over full-bleed art is the intended
     look — this hero carries the title copy, so the two must read as separate
     rows.

     Rather than a bare top margin (which just left a dead band of page bg above
     a full-bleed slab), the hero is a rounded card inset from the page edges:
     the gap above it reads as deliberate framing, and the nav pill floats over
     the page rather than over the artwork. Taller than the old full-bleed
     version to compensate for the side insets. */
  /* --dt-x is the 0->1 scroll progress written by initHeroExpand() in dora.js:
     0 = inset rounded card, 1 = full-bleed banner. Every geometry value below
     interpolates off it, so one number drives the whole zoom-to-fill. It stays
     0 without JS (or under Reduce Motion), which is why the card state is the
     declared default rather than something JS has to apply. */
  .dv-hero--detail {
    --dt-x: 0;
    --hero-h: min(76vh, 760px); height: var(--hero-h);
    width: calc(100% - (1 - var(--dt-x)) * 2 * var(--page-pad));
    /* At x=1 the cap is pushed past any viewport so width:100% wins outright. */
    max-width: calc(var(--page-max) + var(--dt-x) * 100vw);
    margin: calc((1 - var(--dt-x)) * 86px) auto 0;
    border-radius: calc((1 - var(--dt-x)) * 28px);  /* .dv-hero clips already */
    will-change: width, margin, border-radius;
  }
  /* The copy is inset by --page-pad for a full-bleed hero; inside the card that
     would double up against the card's own inset. Pad off the card edge. */
  .dv-hero--detail .dt-body {
    padding: 0 clamp(22px, 3.2vw, 52px);
    max-width: none; bottom: 44px;
  }
  .dv-hero--detail .dt-starring { right: clamp(22px, 3.2vw, 52px); bottom: 44px; }
  .dv-hero__art { position: absolute; inset: 0; width: 100%; height: 100%; object-fit: cover; }
  /* Carousel slides stack in place and cross-fade. `visibility` (not just
     opacity) so an off slide can't take a click through the one on top, and it
     is transitioned with a delay so it flips only once the fade has finished. */
  .dv-hero__slide {
    position: absolute; inset: 0; opacity: 0; visibility: hidden;
    transition: opacity var(--m-ambient) var(--e-inout),
                visibility 0s linear var(--m-ambient);
  }
  .dv-hero__slide.is-on { opacity: 1; visibility: visible; transition-delay: 0s; }
  .dv-hero__dots {
    position: absolute; left: 0; right: 0; bottom: 16px; z-index: 5;
    display: flex; gap: 7px; justify-content: center;
  }
  .dv-hero__dots button {
    width: 8px; height: 8px; padding: 0; border: 0; border-radius: 50%;
    background: rgba(255,255,255,0.34); cursor: pointer;
    transition: width var(--m-move) var(--e-inout),
                background var(--m-move) var(--e-inout);
  }
  .dv-hero__dots button.is-on { background: #fff; width: 22px; border-radius: 4px; }
  /* Hover arrows. Hidden until the pointer is over the hero, and never shown
     on touch — a tap target pinned to the screen edge fights the page scroll. */
  .dv-hero__arrow {
    position: absolute; top: 50%; transform: translateY(-50%);
    z-index: 6; width: 46px; height: 46px; padding: 0;
    display: grid; place-items: center; cursor: pointer;
    border: 1px solid var(--edge); border-radius: 50%;
    background: rgba(20,20,30,0.42); color: #fff;
    backdrop-filter: blur(20px); -webkit-backdrop-filter: blur(20px);
    opacity: 0; transition: opacity var(--m-focus) var(--e-out),
                            background var(--m-focus) var(--e-out);
  }
  .dv-hero__arrow--prev { left: 18px; }
  .dv-hero__arrow--next { right: 18px; }
  /* Keyed off the carousel's own marker, not .dv-hero, so the cinema hero
     (.cx-hero) gets the arrows too without duplicating the rule. */
  [data-dv-carousel-bound]:hover .dv-hero__arrow,
  .dv-hero__arrow:focus-visible { opacity: 1; }
  .dv-hero__arrow:hover { background: rgba(20,20,30,0.72); }
  @media (hover: none) and (pointer: coarse) {
    .dv-hero__arrow { display: none; }
  }
  /* Ambient hero trailer: a muted, looping YouTube embed that fades in over the
     cover art a few seconds after load (§09). Sits under the veil so the title
     stays legible; pointer-events off so it never eats clicks. */
  /* A YouTube iframe letterboxes its 16:9 video inside whatever box you give
     it, so `inset:0` produced a small video floating in black bars — the
     "minimised" trailer. Cover instead, exactly as v1's billboard does: size
     the iframe to 16:9 and grow it past the hero on whichever axis is short,
     then centre it and let the hero's overflow:hidden crop the excess. */
  .dv-hero__trailer {
    position: absolute; top: 50%; left: 50%;
    transform: translate(-50%, -50%);
    width: 100vw; height: 56.25vw;              /* 16:9 of the viewport width */
    min-height: var(--hero-h);                  /* never shorter than the hero */
    min-width: calc(var(--hero-h) * 1.7778);    /* …and 16:9 of that height */
    border: 0;
    pointer-events: none; opacity: 0;
    transition: opacity var(--m-ambient) var(--e-inout);
  }
  .dv-hero__trailer.is-on { opacity: 1; }
  /* Mute sits with the trailer rather than in the top chrome, and only exists
     while one is playing. Top-right is the one corner free in all three hero
     layouts — the title copy is bottom-left and .dt-starring is bottom-right —
     and the offset clears the fixed nav when a hero starts at the page top. */
  .dv-hero__mute {
    position: absolute; z-index: 4;
    top: calc(58px + env(safe-area-inset-top)); right: var(--s4);
    width: 38px; height: 38px;
  }
  /* An IMDb trailer is a real <video>, not an iframe. It inherits the 16:9
     sizing above, but unlike an iframe it has an intrinsic aspect of its own —
     without object-fit it letterboxes inside that box instead of filling it.
     A black ground stops one frame of page background showing through while
     the first frame decodes. */
  video.dv-hero__trailer { object-fit: cover; background: #000; }
  /* Scale the 16:9 video to cover the hero box without letterboxing. */
  .dv-hero__trailer-wrap {
    position: absolute; inset: 0; overflow: hidden; pointer-events: none;
  }
  .dv-hero__veil {
    position: absolute; inset: 0;
    background: linear-gradient(0deg,
      var(--bg) 4%,
      color-mix(in srgb, var(--bg) 45%, transparent) 38%,
      color-mix(in srgb, var(--bg) 25%, transparent) 70%,
      color-mix(in srgb, var(--bg) 50%, transparent));
  }
  /* Light theme: --bg is near-white, so the shared veil would wash the whole
     billboard (and its white title) in white. Anchor the scrim to a dark tone
     instead — imagery stays vivid, white text stays legible — and taper to
     clear by mid-height so only the bottom (where the copy sits) is darkened. */
  :root[data-theme="light"] .dv-hero__veil {
    background: linear-gradient(0deg,
      rgba(14,16,24,0.74) 0%,
      rgba(14,16,24,0.44) 24%,
      rgba(14,16,24,0.14) 52%,
      rgba(14,16,24,0) 80%);
  }
  .dv-hero__body {
    position: absolute; left: 0; right: 0; bottom: 44px;
    display: flex; flex-direction: column; align-items: center; gap: var(--s4);
    text-align: center; padding: 0 var(--s4);
  }
  .dv-hero__title {
    font-size: var(--fz-display); font-weight: 800; line-height: 0.95;
    letter-spacing: 0.02em; text-transform: uppercase; color: #fff;
    text-shadow: 0 4px 40px rgba(0,0,0,0.55);
  }
  /* flex-wrap so a narrow phone wraps Download / the ＋ dial to a second line
     instead of overflowing the inset hero card (which pushed the ＋ off to the
     right until the hero widened on scroll). No effect where they already fit. */
  .dv-hero__actions { display: flex; flex-wrap: wrap; gap: var(--s3);
    align-items: center; margin-top: var(--s1); }

  /* --- GlassNavBar (§08) ------------------------------------------------- */
  .dv-nav {
    position: fixed; top: 18px; left: 0; right: 0; z-index: 60;
    display: flex; align-items: center; justify-content: center;
    padding: 0 var(--s4); pointer-events: none;
    transition: transform var(--m-move) var(--e-move),
                opacity   var(--m-move) var(--e-move);
  }
  .dv-nav.is-veiled { transform: translateY(-130%); opacity: 0; }
  .dv-nav > * { pointer-events: auto; }

  .dv-nav__pill {
    position: relative;
    display: flex; align-items: center; gap: var(--s1);
    padding: 5px; border-radius: var(--r-pill);
    /* Liquid glass: a very transparent fill leaning on a heavy blur. The old
       diagonal inset speculars (bright top-left, dim bottom-right) read as an
       uneven grey smear — replaced with a single even top sheen so the surface
       looks uniform on every side. */
    background: var(--nav-glass); border: 1px solid var(--edge);
    /* 22px is the practical ceiling for a ~48px-tall bar — past that the blur
       is already fully diffuse and extra radius buys nothing you can see. It
       matters here more than anywhere: this element is `fixed`, so its backdrop
       changes on every scroll frame and the blur is recomputed each time. */
    backdrop-filter: blur(22px) saturate(1.8);
    -webkit-backdrop-filter: blur(22px) saturate(1.8);
    box-shadow: inset 0 1px 0 0 rgba(255,255,255,0.16),
                var(--nav-shadow);
  }
  .dv-nav__tab {
    position: relative; z-index: 1;
    height:40px; line-height:1; padding:0 var(--s4); border-radius: var(--r-pill); border: 0; background: none;
    font-size: 13px; font-weight: 500; color: var(--t1); opacity: 0.85;
    cursor: pointer; white-space: nowrap; text-decoration: none;
    display: inline-flex; align-items: center; gap: 6px;
    transition: color var(--m-instant) var(--e-out), opacity var(--m-instant) var(--e-out);
  }
  /* Hover on an unselected tab is a transparent glass fill that tracks the
     pointer — the white selection highlight never moves off the current tab
     (§08 / Detail Page.dc.html nav). */
  /* The fill must not come from --t1: a near-opaque --t1 wash sits *behind*
     --t1-coloured text in dark mode and the label vanishes. But the surface
     tokens were the wrong correction — --glassThick is the same near-black as
     the pill, so hover was invisible. --nav-hover is a low-alpha wash defined
     against the pill in each theme: light in dark mode, dark in light mode. */
  .dv-nav__tab:not(.is-on):hover,
  .dv-nav__tab:not(.is-on):focus-visible {
    opacity: 1; text-decoration: none;
    color: var(--t1);
    background: var(--nav-hover);
    box-shadow: inset 0 0 0 1px var(--edge);
  }
  /* The selected tab paints the selection itself, so it is already correct on
     the very first frame of a page load. .dv-nav__hl is opacity:0 until dora.js
     has measured the tab and added .is-ready, so leaving the job to the sliding
     highlight alone meant the selector was simply absent between first paint
     and DOMContentLoaded: it vanished on every navigation and popped back once
     boot() ran. This is also what the design canvas does — in
     YouthTrendx Detail Page.dc.html the active tab carries its own background
     rather than relying on a positioned element.

     It doubles as the legibility guarantee: --btnText is white in light mode,
     so the label is now white on its own --btn background from frame one,
     rather than white on a near-white pill until the selector caught up. */
  .dv-nav__tab.is-on {
    opacity: 1; font-weight: 600;
    background: var(--btn); color: var(--btnText);
  }
  /* Hand over to the sliding highlight the moment it is placed. It is
     positioned to exactly this tab's box and filled with the same --btn, so the
     two are pixel-identical and the swap cannot be seen. place() adds .is-ready
     with transitions suppressed, and .dv-nav__tab does not transition
     background, so the handover completes inside a single frame. From here on
     the highlight owns the selection and can slide between tabs. */
  .dv-nav__hl.is-ready ~ .dv-nav__tab.is-on { background: none; }

  /* The highlight is a single element that slides — and stretches ~6%
     mid-flight. That stretch is the "liquid" in liquid glass (§06). */
  .dv-nav__hl {
    position: absolute; z-index: 0; top: 5px; left: 0;
    height: calc(100% - 10px); border-radius: var(--r-pill);
    background: var(--btn); opacity: 0;
    transition: transform var(--m-focus) var(--e-spring),
                width     var(--m-focus) var(--e-spring),
                opacity   var(--m-instant) var(--e-out);
    will-change: transform, width;
  }
  .dv-nav__hl.is-ready { opacity: 1; }

  .dv-nav__side {
    position: fixed; top:22px; right:var(--s4); z-index:61;
    display: flex; align-items: center; gap: var(--s2);
  }
  /* The right-side controls — and the mobile hamburger, which is the same glass
     button sitting on the opposite corner — float on the same plane as the
     pill, so they get the exact same treatment: the transparent liquid-glass
     fill, the even top sheen (no diagonal specular smear) and the even ambient
     shadow. Without this they read as a different, more-solid material. */
  .dv-nav__side .dv-btn--icon,
  .dv-nav__burger {
    background: var(--nav-glass);
    backdrop-filter: blur(36px) saturate(1.8);
    -webkit-backdrop-filter: blur(36px) saturate(1.8);
    box-shadow: inset 0 1px 0 0 rgba(255,255,255,0.16),
                var(--nav-shadow);
  }
  .dv-nav__side .dv-ava { box-shadow: var(--nav-shadow); }

  .dv-ava {
    width: 38px; height: 38px; border-radius: 50%; flex: none;
    border: 1px solid var(--edge); background: var(--card) center/cover no-repeat;
    color: var(--t1); font-weight: 700; font-size: 14px; cursor: pointer;
    display: inline-flex; align-items: center; justify-content: center;
    transition: transform var(--m-focus) var(--e-spring);
  }
  .dv-ava:hover { transform: scale(1.08); }
  /* The avatar is a background-image on a button that also renders an initial
     as fallback text. When an image IS set the letter must not show through. */
  .dv-ava[style*="background-image"] { color: transparent; }

  /* --- Nav search (replaces the pill in place) ---------------------------- */
  .dv-nav__search { padding: 0; width: min(560px, calc(100vw - 140px)); }
  .dv-nav__search input {
    flex: 1; min-width: 0; border: 0; background: none; outline: none;
    padding: 11px 12px; font: inherit; font-size: 13.5px; color: var(--t1);
  }
  /* iOS Safari zooms the entire page when a focused input's font-size is under
     16px, and never zooms back out — that was the "search enlarges the page".
     16px on touch only; pointer devices keep the designed sizes. Applied to
     every text control, not just search, so no other field can reintroduce it. */
  @media (hover: none) and (pointer: coarse) {
    input[type="text"], input[type="search"], input[type="email"],
    input[type="password"], input[type="number"], input[type="tel"],
    input[type="url"], input:not([type]), select, textarea {
      font-size: 16px;
    }
  }
  .dv-nav__search input::placeholder { color: var(--t3); }
  .dv-nav__search input::-webkit-search-cancel-button { display: none; }
  .dv-nav__search .dv-nav__tab { padding: 9px 14px; }

  .dv-nav__results {
    position: absolute; top: 58px; left: 50%; transform: translateX(-50%);
    width: min(560px, calc(100vw - 32px)); max-height: min(64vh, 520px);
    overflow-y: auto; z-index: 62; pointer-events: auto;
    padding: var(--s2); border-radius: var(--r-sheet-sm);
    background: var(--glassThick); border: 1px solid var(--hair);
    backdrop-filter: blur(48px) saturate(1.8);
    -webkit-backdrop-filter: blur(48px) saturate(1.8);
    box-shadow: var(--shadowF);
  }
  .dv-nav__hit {
    display: flex; align-items: center; gap: var(--s3); padding: 8px;
    border-radius: var(--r-card-sm); color: inherit; text-decoration: none;
  }
  .dv-nav__hit:hover { background: var(--glassThin); text-decoration: none; }
  .dv-nav__hitart {
    flex: none; width: 40px; height: 60px; border-radius: 7px; overflow: hidden;
    background: var(--card); display: flex; align-items: center;
    justify-content: center; font-weight: 800; color: var(--t3);
  }
  .dv-nav__hitart img { width: 100%; height: 100%; object-fit: cover; }
  /* People render as a round avatar so a director/actor reads as a person, not
     a poster, in the same results list. */
  .dv-nav__hitart--person {
    width: 46px; height: 46px; border-radius: 50%;
  }
  .dv-nav__hitmeta { min-width: 0; display: flex; flex-direction: column; gap: 3px; }
  .dv-nav__hitmeta b {
    font-size: 13.5px; font-weight: 600;
    overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
  }
  .dv-nav__hitmeta small { font-size: 11.5px; color: var(--t2); }
  .dv-nav__none, .dv-nav__all {
    display: block; padding: 12px 10px; font-size: 12.5px; color: var(--t2);
    text-decoration: none;
  }
  .dv-nav__all { color: var(--acc); font-weight: 600; }

  /* Voice search, inside the field. No brand red — the accent token carries the
     "live" state, same as everywhere else in v2. */
  .dv-nav__mic {
    flex: none; display: inline-flex; align-items: center; justify-content: center;
    width: 30px; height: 30px; padding: 0; border-radius: 50%; cursor: pointer;
    background: none; border: 0; color: var(--t3);
    transition: color var(--m-instant) var(--e-out),
                background var(--m-instant) var(--e-out);
  }
  .dv-nav__mic:hover { color: var(--t1); background: var(--glassThin); }
  .dv-nav__mic.is-live {
    color: #fff; background: var(--acc);
    animation: dv-mic-pulse 1.4s ease-in-out infinite;
  }
  @keyframes dv-mic-pulse {
    0%, 100% { box-shadow: 0 0 0 0 color-mix(in oklab, var(--acc) 55%, transparent); }
    50%      { box-shadow: 0 0 0 7px transparent; }
  }
  @media (prefers-reduced-motion: reduce) { .dv-nav__mic.is-live { animation: none; } }

  /* Transcript / mic error line. Sits under the field, above the results, and
     is only in flow while it has something to say. */
  .dv-nav__note {
    position: absolute; top: 58px; left: 50%; transform: translateX(-50%);
    z-index: 63; width: min(560px, calc(100vw - 32px));
    padding: 9px 14px; border-radius: var(--r-pill);
    background: var(--glassThick); border: 1px solid var(--hair);
    font-size: 12.5px; color: var(--t2); text-align: center;
  }
  .dv-nav__note:not([hidden]) ~ .dv-nav__results { top: 104px; }

  /* --- Me sheet quick cards ---------------------------------------------- */
  .dv-mecards {
    display: grid; grid-template-columns: repeat(3, 1fr); gap: var(--s2);
    margin-bottom: var(--s5);
  }
  .dv-mecard {
    display: flex; flex-direction: column; gap: 4px; min-width: 0;
    padding: var(--s3) var(--s3) 14px; border-radius: var(--r-card-sm);
    background: var(--glassThin); border: 1px solid var(--hair);
    color: inherit; text-decoration: none;
    transition: background var(--m-instant) var(--e-out),
                transform var(--m-focus) var(--e-spring);
  }
  .dv-mecard:hover { background: var(--card); transform: translateY(-2px); text-decoration: none; }
  .dv-mecard strong { font-size: 22px; font-weight: 700; font-variant-numeric: tabular-nums; }
  .dv-mecard span {
    font-family: var(--ff-mono); font-size: 9px; letter-spacing: 0.1em;
    text-transform: uppercase; color: var(--t3);
    overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
  }
  .dv-mecard.is-due strong { color: var(--acc); }

  /* --- Card preview (hover on pointer, long-press on touch) --------------- */
  .dv-prev {
    position: fixed; z-index: 240; display: none;
    border-radius: var(--r-card); overflow: hidden;
    background: var(--glassThick); border: 1px solid var(--hair);
    backdrop-filter: blur(48px) saturate(1.8);
    -webkit-backdrop-filter: blur(48px) saturate(1.8);
    box-shadow: var(--shadowF);
  }
  .dv-prev.is-on { display: block; }
  .dv-prev__media { position: relative; width: 100%; aspect-ratio: 16/9; background: var(--card); }
  .dv-prev__art {
    position: absolute; inset: 0; background: var(--card) center/cover no-repeat;
  }
  .dv-prev__vid {
    position: absolute; top: 50%; left: 50%; transform: translate(-50%, -50%);
    width: 100%; height: 100%; min-width: 100%; min-height: 100%;
    border: 0; pointer-events: none; opacity: 0;
    transition: opacity var(--m-ambient) var(--e-inout);
  }
  .dv-prev__vid.is-on { opacity: 1; }
  .dv-prev__body { padding: var(--s4); }
  .dv-prev__acts { display: flex; align-items: center; gap: var(--s2); margin-bottom: var(--s3); }
  .dv-prev__acts .dv-prev__x { margin-left: auto; }
  .dv-prev__meta {
    display: flex; gap: 10px; align-items: center; flex-wrap: wrap;
    font-size: 12px; color: var(--t2); margin-bottom: 8px;
  }
  .dv-prev__pills { display: flex; gap: 6px; flex-wrap: wrap; }
  .dv-prev__pills span {
    padding: 3px 10px; border-radius: var(--r-pill);
    border: 1px solid var(--hair); font-size: 11px; color: var(--t2);
  }
  .dv-prev__plot {
    margin: 10px 0 0; font-size: 12.5px; color: var(--t2); line-height: 1.5;
    display: -webkit-box; -webkit-line-clamp: 2; -webkit-box-orient: vertical;
    overflow: hidden;
  }
  /* Touch has no hover, so the preview arrives as a bottom sheet instead.
     This also follows the actual last input on a hybrid PC. */
  html:not(.dv-pointer-fine) .dv-prev {
    left: 0 !important; right: 0 !important; top: auto !important;
    bottom: 0 !important; width: 100% !important;
    border-radius: var(--r-sheet) var(--r-sheet) 0 0;
  }

  /* More dropdown */
  .dv-more {
    position: fixed; top: 74px; left: 50%; transform: translateX(-50%) scale(0.96);
    z-index: 62; width: min(560px, calc(100vw - 32px));
    max-height: min(70vh, 620px); overflow-y: auto;
    padding: var(--s4); border-radius: var(--r-sheet);
    background: var(--glassThick); border: 1px solid var(--hair);
    backdrop-filter: blur(48px) saturate(1.8);
    -webkit-backdrop-filter: blur(48px) saturate(1.8);
    box-shadow: var(--shadowF), inset 0 1px 0 var(--edge);
    opacity: 0; visibility: hidden;
    transition: opacity var(--m-instant) var(--e-out),
                transform var(--m-focus) var(--e-spring),
                visibility 0s linear var(--m-focus);
  }
  .dv-more.is-open {
    opacity: 1; visibility: visible; transform: translateX(-50%) scale(1);
    transition-delay: 0s;
  }
  .dv-more__sec {
    font-family: var(--ff-mono); font-size: 10px; letter-spacing: 0.14em;
    text-transform: uppercase; color: var(--t3);
    margin: var(--s4) var(--s2) var(--s2);
  }
  .dv-more__sec:first-child { margin-top: 0; }
  .dv-more__grid { display: grid; grid-template-columns: repeat(2, 1fr); gap: var(--s1); }
  .dv-more a {
    display: block; padding: 11px var(--s3); border-radius: var(--r-card-md);
    font-size: 13.5px; font-weight: 500; color: var(--t2); text-decoration: none;
    transition: background var(--m-instant) var(--e-out), color var(--m-instant) var(--e-out);
  }
  .dv-more a:hover { background: var(--glassThin); color: var(--t1); text-decoration: none; }
  .dv-more a.is-on { background: var(--glassThin); color: var(--t1); font-weight: 600; }

  /* --- Detail hero (.dt-*) -----------------------------------------------
     Shared by /title/<kind>/<id> and the cinema film page. Lived in
     title.html until the cinema page needed the same hero; a second copy
     would have drifted the moment either page was touched. */
  /* ── Hero, rebuilt to the Apple-TV reference ──────────────────────────
     Bottom-left stack rather than the centred billboard: the artwork on a
     detail page is a portrait of one title, so the copy sits out of the
     subject's way instead of on top of it. */
  .dt-body{align-items:flex-start;text-align:left;left:0;right:0;
    bottom:38px;padding:0 var(--page-pad);max-width:var(--page-max);
    margin:0 auto;gap:var(--s3)}
  .dt-title{font-size:clamp(34px,5.4vw,72px);text-align:left}

  /* ── Title treatment ──────────────────────────────────────────────────── */
  /* The studio lettering (TMDB logos / fanart.tv) rendered in place of the
     text title — see the hero_title macro in _components.html.

     The <h1> keeps the real text in .dt-title__text and the logo carries the
     title as its alt, so hiding the span outright is correct: leaving it
     visually-hidden instead would make a screen reader announce the title
     twice, once from the alt and once from the span. */
  .dv-hero__title.has-logo .dt-title__text{display:none}
  /* Title art comes in wildly different proportions — a 7776x1886 ribbon and a
     1098x1295 stacked mark both arrive as "the logo". Constraining BOTH axes is
     what keeps either from wrecking the hero: max-inline-size stops the ribbon
     spanning the page, max-block-size stops the stacked one towering over the
     copy beneath it. width/height auto keeps the aspect ratio intact. */
  .dt-logo{display:block;width:auto;height:auto;
    max-width:75%;max-height:80px;object-fit:contain;
    filter:drop-shadow(0 4px 34px rgba(0,0,0,0.6))}
  /* Sizes mirror the reference treatment's Tailwind classes:
     max-h-20 max-w-[75%] below the lg breakpoint, lg:max-h-36
     lg:max-w-[500px] at and above it (lg = 1024px). Fixed px rather than vh so
     a short laptop viewport and a tall desktop one render the mark identically. */
  @media (min-width:1024px){
    .dt-logo{max-width:500px;max-height:144px}
  }
  /* The centred heroes (home billboard, TMDB detail) centre their h1 with
     text-align, which does nothing to a block-level image. */
  .dv-hero__title:not(.dt-title) .dt-logo{margin-inline:auto}
  .dt-line{display:flex;flex-wrap:wrap;align-items:center;gap:8px;
    font-size:14px;font-weight:600;color:rgba(255,255,255,0.9)}
  .dt-line > * + *::before{content:"·";margin-right:8px;opacity:.6}
  .dt-line .dt-link{color:inherit;text-decoration:none}
  .dt-line .dt-link:hover{text-decoration:underline}
  .dt-cert{display:inline-flex;align-items:center;padding:1px 6px;
    border:1px solid rgba(255,255,255,0.55);border-radius:4px;
    font-size:11px;letter-spacing:.04em}
  /* Three lines, then MORE. The pill is inline so it sits at the end of the
     truncated text the way the reference does, not on its own row. */
  .dt-blurb{margin:0;max-width:min(620px,100%);color:rgba(255,255,255,0.86);
    font-size:15.5px;line-height:1.5;
    display:-webkit-box;-webkit-line-clamp:3;-webkit-box-orient:vertical;
    overflow:hidden}
  .dt-more{margin-left:8px;padding:2px 9px;border:0;cursor:pointer;
    border-radius:var(--r-pill);font:inherit;font-size:11px;font-weight:700;
    letter-spacing:.06em;background:rgba(255,255,255,0.28);color:#fff;
    backdrop-filter:blur(12px);-webkit-backdrop-filter:blur(12px)}
  .dt-more:hover{background:rgba(255,255,255,0.42)}
  .dt-badges{display:flex;flex-wrap:wrap;align-items:center;gap:10px;
    font-size:13px;font-weight:600;color:rgba(255,255,255,0.82)}
  .dt-chip{display:inline-flex;align-items:center;padding:2px 8px;
    border:1px solid rgba(255,255,255,0.34);border-radius:5px;font-size:11px}
  /* Flags, adapted from the BeaconOS pattern: a fixed 3:2 box with the image
     set to cover, so a square flag (Switzerland) or an odd-ratio one lands as
     the same clean rectangle as every other — rendering them at natural aspect
     (height:auto) is what made the row look ragged. Rounded, hairline ring. */
  .dt-flag{display:inline-flex;flex:none;line-height:0;border-radius:3px;
    overflow:hidden;box-shadow:0 0 0 1px rgba(255,255,255,0.32),
    0 1px 3px rgba(0,0,0,0.35)}
  .dt-flag img{width:26px;height:17.3px;object-fit:cover;display:block}
  .dt-starring{position:absolute;right:var(--page-pad);bottom:38px;z-index:3;
    max-width:min(340px,38vw);text-align:right;font-size:14px;
    color:rgba(255,255,255,0.92);line-height:1.45}
  .dt-starring__k{color:rgba(255,255,255,0.55);margin-right:5px}
  .dt-starring a{color:inherit;text-decoration:none}
  .dt-starring a:hover{text-decoration:underline}

  /* ── Ambient tint ─────────────────────────────────────────────────────
     A fixed, subtle full-page gradient painted from the hero landscape's own
     colours by initAmbient(). Sits above the body-bg canvas but below content
     (which is lifted to z-index:1). Stays transparent — i.e. plain page bg —
     until JS sets --dora-ambient, and if colour extraction fails. */
  /* z-index:-1 sits it above the body-bg canvas but behind every in-flow
     element, so no content needs lifting and it can never cover the page. */
  .dv-ambient{position:fixed;inset:0;z-index:-1;pointer-events:none;
    background:var(--dora-ambient,transparent);transition:background .9s ease}

  /* ── Title detail hero: tall rounded-top cards + side peeks + scroll zoom ──
     Faithful to Title Detail Page.dc.html. The scaler holds 100vw cards scaled
     to 0.86 from the top-centre, so full-height neighbour cards peek at the
     sides; scrolling zooms the scaler to full-bleed (initTitleHero -> _zoom).
     The card runs below the fold, so nothing under it shows until you scroll.
     Scoped to .dt-stage / .dv-hero--titlecar so the cinema hero is untouched. */
  .dt-stage{position:relative;z-index:1;height:112vh}
  .dt-scaler{position:relative;height:100vh;transform:scale(.86);
    transform-origin:50% 0;will-change:transform}
  /* Content below the hero paints above the (absolute) cards. */
  body.dora-title .dv-wrap{position:relative;z-index:2}
  .dt-hcard{position:absolute;left:0;top:9vh;width:100vw;height:118vh;
    border-radius:51px 51px 0 0;overflow:hidden;
    box-shadow:0 30px 90px rgba(0,0,0,.5);will-change:transform}
  .dt-hcard[hidden]{display:none}
  .dt-hcard--prev{transform:translateX(calc(-100vw - 24px))}
  .dt-hcard--next{transform:translateX(calc(100vw + 24px))}
  .dt-hcard--peek{display:block;text-decoration:none;cursor:pointer}
  .dt-hcard__art{position:absolute;inset:0;background-size:cover;background-position:center}
  .dt-hcard__dim{position:absolute;inset:0;background:rgba(8,9,16,.5);
    transition:background var(--m-focus) var(--e-out)}
  .dt-hcard--peek:hover .dt-hcard__dim,
  .dt-hcard--peek:focus-visible .dt-hcard__dim{background:rgba(8,9,16,.25)}
  /* The active card = our full hero; neutralise the old inset-card geometry so
     it fills the tall rounded-top card. Border radius is left to .dt-hcard plus
     the JS-set inline value from _zoom. */
  .dt-hcard.dv-hero--detail{width:100vw;max-width:none;height:118vh;margin:0}
  .dv-hero--titlecar{--hero-h:118vh}
  /* Copy sits near the viewport bottom (~13% up the tall card), not at the
     card's true bottom far below the fold. */
  .dv-hero--titlecar .dt-body{bottom:13%;padding:0 5.5%;max-width:600px;margin:0}
  /* One line, never wrapped: let it size to its content and extend leftward. */
  .dv-hero--titlecar .dt-starring{right:5%;bottom:10.5%;left:auto;
    max-width:none;white-space:nowrap}
  /* Design scrim: darken the left third + the bottom, keep the art vivid. */
  .dv-hero--titlecar .dv-hero__veil{
    background:linear-gradient(90deg,rgba(8,9,16,.74) 0%,rgba(8,9,16,.42) 32%,
      rgba(8,9,16,0) 56%),
    linear-gradient(0deg,rgba(8,9,16,.6) 0%,rgba(8,9,16,0) 30%)}

  /* Arrows: glass buttons centred on the visible card; shown on stage hover, and
     always on TV where the D-pad reaches them. */
  .dt-heroarrow{position:absolute;top:42vh;transform:translateY(-50%);z-index:6;
    width:48px;height:48px;padding:0;display:grid;place-items:center;cursor:pointer;
    border:1px solid var(--edge);border-radius:50%;background:rgba(20,20,30,.42);
    color:#fff;backdrop-filter:blur(20px);-webkit-backdrop-filter:blur(20px);
    opacity:0;transition:opacity var(--m-focus) var(--e-out),
      background var(--m-focus) var(--e-out)}
  .dt-heroarrow[hidden]{display:none}
  .dt-heroarrow--prev{left:18px}
  .dt-heroarrow--next{right:18px}
  .dt-stage:hover .dt-heroarrow:not([hidden]),
  .dt-heroarrow:focus-visible{opacity:1}
  .dt-heroarrow:hover{background:rgba(20,20,30,.72)}
  body.dv-tv .dt-heroarrow:not([hidden]){opacity:1}

  /* Staged reveal: landscape first; copy fades up once JS adds dora-hero-ready
     (~2.8s). Reduce Motion / no-JS: shown immediately (rules below). */
  .dv-hero--titlecar .dt-body,
  .dv-hero--titlecar .dt-starring{opacity:0;transform:translateY(28px);
    transition:opacity .9s var(--e-out),transform .9s var(--e-out)}
  body.dora-hero-ready .dv-hero--titlecar .dt-body,
  body.dora-hero-ready .dv-hero--titlecar .dt-starring{opacity:1;transform:none}

  /* Reduce Motion / narrow viewport: no zoom, no peeks - a plain rounded-top
     hero card. TVs are >=1280px, so the max-width rule leaves them alone. */
  @media (prefers-reduced-motion: reduce){
    .dt-scaler{transform:none;height:auto}
    .dt-stage{height:auto}
    .dt-hcard--active{position:relative;width:100%;height:min(80vh,760px);
      border-radius:24px 24px 0 0}
    .dt-hcard.dv-hero--detail{width:100%;height:min(80vh,760px)}
    .dt-hcard--peek,.dt-heroarrow{display:none !important}
    .dv-hero--titlecar .dt-body,
    .dv-hero--titlecar .dt-starring{opacity:1;transform:none;transition:none}
    .dv-hero--titlecar .dt-body{bottom:38px}
    .dv-hero--titlecar .dt-starring{bottom:38px}
  }
  @media (max-width:760px){
    .dt-scaler{transform:none;height:auto}
    .dt-stage{height:auto}
    .dt-hcard--active{position:relative;width:100%;height:min(66vh,520px);
      border-radius:20px 20px 0 0}
    .dt-hcard.dv-hero--detail{width:100%;height:min(66vh,520px)}
    .dt-hcard--peek,.dt-heroarrow{display:none !important}
    .dv-hero--titlecar .dt-body{bottom:24px;padding:0 16px}
    .dv-hero--titlecar .dt-starring{display:none}
  }

  /* TV: the <html> is zoomed 1.25x for the 10-foot read, and `zoom` interacts
     badly with the hero's 100vh/118vh + scale() math (the card overflowed and
     the right peek was pushed off). Cancelling the zoom on the stage alone
     (0.8 = 1/1.25) makes the hero compute in a 1:1 space, so it renders exactly
     like PC while the content below keeps the readable 1.25x. Keyed on the
     pre-paint :root[data-tv] attribute (set in _shell.html BEFORE first paint)
     as well as the post-paint body.dv-tv class, so the hero never flashes at
     1.25x for a frame before the class lands. The scroll-zoom (initTitleHero
     _zoom) now runs on TV too, so scrolling fills the card exactly like PC. */
  :root[data-tv] .dt-stage,
  body.dv-tv .dt-stage{zoom:0.8}
  /* TV title typography: use the width of the landscape hero instead of the
     desktop's narrow 600px copy column. A condensed display face keeps most
     names on one line while still allowing genuinely long titles to wrap. */
  :root[data-tv] .dv-hero--titlecar .dt-body,
  body.dv-tv .dv-hero--titlecar .dt-body{
    width:100%;max-width:min(1360px,84vw)}
  :root[data-tv] .dv-hero--titlecar .dt-title,
  body.dv-tv .dv-hero--titlecar .dt-title{
    max-width:100%;font-family:Impact,Haettenschweiler,"Arial Narrow Bold",
      "Arial Narrow",sans-serif;font-size:clamp(58px,6.2vw,96px);
    font-weight:900;line-height:.92;letter-spacing:.01em;text-wrap:balance}
  /* With real title art the Impact substitute above is only the fallback, so
     the logo gets its own budget: a TV is viewed from across a room, and the
     26vh/560px web cap reads as a postage stamp at 1080p. Capped in vh as well
     so a stacked mark cannot push the blurb and action row off the panel. */
  :root[data-tv] .dv-hero--titlecar .dt-logo,
  body.dv-tv .dv-hero--titlecar .dt-logo{
    max-width:min(1700px,88vw);max-height:44vh}
  :root[data-tv] .dv-hero--titlecar .dt-line,
  body.dv-tv .dv-hero--titlecar .dt-line{font-size:18px}
  :root[data-tv] .dv-hero--titlecar .dt-cert,
  body.dv-tv .dv-hero--titlecar .dt-cert{font-size:14px}
  :root[data-tv] .dv-hero--titlecar .dt-blurb,
  body.dv-tv .dv-hero--titlecar .dt-blurb{
    max-width:min(820px,72vw);font-size:19px;line-height:1.42}
  :root[data-tv] .dv-hero--titlecar .dt-badges,
  body.dv-tv .dv-hero--titlecar .dt-badges{font-size:16px}
  :root[data-tv] .dv-hero--titlecar .dt-chip,
  body.dv-tv .dv-hero--titlecar .dt-chip{font-size:13px;padding:3px 9px}
  :root[data-tv] .dv-hero--titlecar .dt-starring,
  body.dv-tv .dv-hero--titlecar .dt-starring{font-size:17px}
  /* TV hero-select: the whole active card is the default focus. Left/Right switch
     titles, Down drops into its buttons (initTitleHero + D.tvHero). Show a clear
     ring on the card while it holds focus so "the card is selected" reads. */
  body.dv-tv .dt-hcard--active[data-tv-hero-card]{outline:none}
  body.dv-tv .dt-hcard--active[data-tv-hero-card]:focus-visible,
  body.dv-tv .dt-hcard--active[data-tv-hero-card]:focus{
    box-shadow:inset 0 0 0 4px var(--ring),0 22px 70px rgba(0,0,0,.55)}

  /* Section header that is itself the "see all" link. */
  .dt-seeall{display:inline-flex;align-items:center;gap:6px;color:inherit;
    text-decoration:none;min-width:0}
  .dt-seeall:hover{text-decoration:none}
  .dt-seeall svg{opacity:.55;transition:transform var(--m-focus) var(--e-out),
                 opacity var(--m-focus) var(--e-out)}
  .dt-seeall:hover svg{opacity:1;transform:translateX(3px)}
  .dt-people{gap:var(--s4)}

  /* --- Detail About sheet (.dt-sheet) -------------------------------------
     Also shared with the cinema film page. */
  /* ── About sheet ──────────────────────────────────────────────────── */
  .dt-sheet{position:fixed;z-index:120;left:50%;top:50%;
    transform:translate(-50%,-46%) scale(.97);opacity:0;visibility:hidden;
    width:min(680px,94vw);max-height:86vh;display:flex;flex-direction:column;
    background:var(--surface);border:1px solid var(--edge);
    border-radius:var(--r-sheet);box-shadow:var(--shadowF);
    transition:opacity var(--m-focus) var(--e-out),
               transform var(--m-focus) var(--e-spring),
               visibility 0s linear var(--m-focus)}
  .dt-sheet.is-open{opacity:1;visibility:visible;transition-delay:0s;
    transform:translate(-50%,-50%) scale(1)}
  .dt-sheet__head{display:flex;align-items:center;justify-content:space-between;
    gap:var(--s4);padding:var(--s5) var(--s5) var(--s4);
    border-bottom:1px solid var(--hair)}
  .dt-sheet__body{padding:var(--s5);overflow-y:auto}
  .dt-cchip{display:inline-flex;align-items:center;gap:7px}
  .dt-cchip img{width:21px;height:14px;object-fit:cover;border-radius:2px;
    display:block;box-shadow:0 0 0 1px var(--hair)}
  @media(max-width:640px){
    .dt-sheet{top:auto;bottom:0;left:0;right:0;transform:translateY(100%);
      width:100%;max-width:none;max-height:88vh;
      border-radius:var(--r-sheet) var(--r-sheet) 0 0}
    .dt-sheet.is-open{transform:translateY(0)}
    .dt-starring{display:none}
    .dt-body{bottom:24px}
  }

  .dt-h{font-size:20px;font-weight:700;letter-spacing:-0.02em;margin:0 0 var(--s3)}
  .dt-h--tight{margin-bottom:var(--s2)}

  /* Hamburger + drawer — the mobile nav.
     Hidden on a pointer device, where the pill bar is the nav. */
  .dv-nav__burger {
    display: none;
    position: fixed; top: 18px; left: var(--s4); z-index: 61;
    width: 38px; height: 38px;
  }
  /* Menu ↔ close state-morph (design §05): the bars don't swap for an X glyph,
     they fold into one, driven by the button's aria-expanded state. */
  .dv-burger rect {
    transform-box: fill-box; transform-origin: center;
    transition: transform var(--m-move) var(--e-move),
                opacity var(--m-instant) var(--e-out);
  }
  .dv-nav__burger[aria-expanded="true"] .dv-burger__t { transform: translateY(5px) rotate(45deg); }
  .dv-nav__burger[aria-expanded="true"] .dv-burger__m { opacity: 0; transform: scaleX(0.2); }
  .dv-nav__burger[aria-expanded="true"] .dv-burger__b { transform: translateY(-5px) rotate(-45deg); }

  .dv-drawer {
    position: fixed; z-index: 70; top: 0; left: 0; bottom: 0;
    width: min(340px, 88vw);
    display: flex; flex-direction: column;
    padding: calc(var(--s4) + env(safe-area-inset-top)) var(--s3)
             calc(var(--s4) + env(safe-area-inset-bottom));
    background: var(--glassThick); border-right: 1px solid var(--hair);
    backdrop-filter: blur(48px) saturate(1.8);
    -webkit-backdrop-filter: blur(48px) saturate(1.8);
    box-shadow: var(--shadowF);
    transform: translateX(-102%);
    opacity: 0; visibility: hidden;
    transition: transform var(--m-move) var(--e-move),
                opacity   var(--m-instant) var(--e-out),
                visibility 0s linear var(--m-move);
  }
  .dv-drawer.is-open {
    transform: none; opacity: 1; visibility: visible; transition-delay: 0s;
  }
  .dv-drawer__top {
    display: flex; align-items: center; justify-content: space-between;
    padding: 0 var(--s2) var(--s3); flex: none;
  }
  /* The link list scrolls; the header stays put. A long More menu on a short
     phone would otherwise push the close button off screen. */
  .dv-drawer__body { flex: 1; min-height: 0; overflow-y: auto; }
  .dv-drawer__grid { display: grid; grid-template-columns: 1fr; gap: 2px; }
  .dv-drawer__grid a {
    display: block; padding: 12px var(--s3); border-radius: var(--r-card-md);
    font-size: 14.5px; font-weight: 500; color: var(--t2); text-decoration: none;
    transition: background var(--m-instant) var(--e-out), color var(--m-instant) var(--e-out);
  }
  .dv-drawer__grid a:hover { background: var(--glassThin); color: var(--t1); text-decoration: none; }
  .dv-drawer__grid a.is-on { background: var(--glassThin); color: var(--t1); font-weight: 600; }

  .dv-backdrop {
    position: fixed; inset: 0; z-index: 55; background: rgba(0,0,0,0.4);
    opacity: 0; visibility: hidden;
    transition: opacity var(--m-instant) var(--e-out), visibility 0s linear var(--m-instant);
  }
  .dv-backdrop.is-open { opacity: 1; visibility: visible; transition-delay: 0s; }

  /* TabBar — mobile. A floating rounded pill rather than an edge-to-edge bar:
     it clears the screen edges on all three sides so the page reads as one
     continuous surface running underneath it. Centred with left/right:0 plus
     margin-inline:auto rather than a translateX(-50%), which leaves `transform`
     free for the scroll-veil animation just below — the two would otherwise
     overwrite each other and the bar would jump to the left edge on scroll. */
  .dv-tabbar {
    display: none;
    align-items: center; gap: 2px;
    position: fixed; z-index: 60;
    left: 0; right: 0; bottom: calc(14px + env(safe-area-inset-bottom));
    width: max-content; max-width: calc(100% - 24px); margin-inline: auto;
    padding: 6px;
    border-radius: var(--r-pill);
    /* Same liquid glass as the top nav — transparent fill on a heavy blur.
       A real border now, not border-top: every edge of a pill is visible. */
    background: var(--nav-glass); border: 1px solid var(--edge);
    backdrop-filter: blur(22px) saturate(1.8);
    -webkit-backdrop-filter: blur(22px) saturate(1.8);
    box-shadow: var(--nav-shadow);
    transition: transform var(--m-move) var(--e-move),
                opacity var(--m-move) var(--e-move);
  }
  /* 100% clears the pill, the extra 22px clears the gap beneath it — otherwise
     the veiled bar parks half-visible along the bottom edge. */
  .dv-tabbar.is-hidden { transform: translateY(calc(100% + 22px)); opacity: 0; }
  .dv-tabbar__tab {
    flex: 0 0 auto;
    display: flex; align-items: center; justify-content: center;
    width: 52px; height: 44px;
    border-radius: var(--r-pill);
    color: var(--t2); text-decoration: none;
    transition: color var(--m-instant) var(--e-out),
                background-color var(--m-instant) var(--e-out);
  }
  .dv-tabbar__tab .dv-ic { width: 23px; height: 23px; display: block; }
  /* Icon-only. The label text is still in the DOM — flip this to `inline` and
     the bar goes back to icon-over-label without touching the template. */
  .dv-tabbar__lbl { display: none; }
  .dv-tabbar__tab:hover { color: var(--t1); text-decoration: none; }
  /* The active slot is a filled chip, not merely a brighter icon: at icon-only
     size, with no label to read, a colour shift alone is too weak to find. */
  .dv-tabbar__tab.is-on { background: var(--acc); color: var(--btnText); }
  .dv-tabbar__tab:focus-visible { outline: 2px solid var(--acc); outline-offset: 2px; }
  .dv-tabbar__ava { border-radius: 50%; overflow: hidden; }

  /* --- Sheet (Me, modals) ------------------------------------------------ */
  .dv-sheet {
    position: fixed; z-index: 70; right: var(--s4); top: 68px;
    width: min(380px, calc(100vw - 32px));
    max-height: min(76vh, 680px); overflow-y: auto;
    padding: var(--s5); border-radius: var(--r-sheet);
    background: var(--glassThick); border: 1px solid var(--hair);
    backdrop-filter: blur(48px) saturate(1.8);
    -webkit-backdrop-filter: blur(48px) saturate(1.8);
    box-shadow: var(--shadowF), inset 0 1px 0 var(--edge);
    opacity: 0; visibility: hidden; transform: translateY(-8px) scale(0.98);
    transition: opacity var(--m-instant) var(--e-out),
                transform var(--m-focus) var(--e-spring),
                visibility 0s linear var(--m-focus);
  }
  .dv-sheet.is-open {
    opacity: 1; visibility: visible; transform: none; transition-delay: 0s;
  }

  /* Notifications sheet rows. Negative margin lets the hover fill bleed to the
     sheet's padding edge without the row itself being flush with it. */
  .dvn-item {
    display: flex; align-items: center; gap: 10px;
    padding: 9px 8px; margin: 0 -8px;
    border-radius: 12px; color: inherit; text-decoration: none;
    transition: background-color var(--m-instant) var(--e-out);
  }
  .dvn-item:hover { background: var(--glassThin); text-decoration: none; }
  .dvn-item + .dvn-item { box-shadow: inset 0 1px 0 0 var(--hair); }
  /* Poster ratio, so a row with art and a row without are the same height. */
  .dvn-th {
    flex: 0 0 auto; width: 34px; height: 50px; border-radius: 6px;
    background: var(--glassThin) center/cover no-repeat;
  }
  .dvn-body { min-width: 0; display: flex; flex-direction: column; gap: 2px; }
  .dvn-text { color: var(--t1); font-size: 13.5px; line-height: 1.35; }
  .dvn-when { color: var(--t3); font-size: 11.5px; }
  .dvn-empty { color: var(--t2); font-size: 13.5px; padding: 6px 0; }

  .dv-srow {
    display: flex; align-items: center; gap: var(--s3);
    padding: var(--s3) 0; border-bottom: 1px solid var(--hair);
    font-size: 13.5px; color: var(--t2); text-decoration: none;
  }
  .dv-srow:last-child { border-bottom: 0; }
  .dv-srow:hover { color: var(--t1); text-decoration: none; }
  .dv-srow__val { margin-left: auto; color: var(--t3); font-size: 12.5px; }

  /* --- Toggle (§14) ------------------------------------------------------ */
  .dv-toggle {
    position: relative; width: 46px; height: 26px; flex: none;
    border-radius: var(--r-pill); border: 0; cursor: pointer;
    background: color-mix(in oklab, var(--t3) 45%, transparent);
    transition: background var(--m-instant) var(--e-out);
  }
  .dv-toggle::after {
    content: ""; position: absolute; top: 3px; left: 3px;
    width: 20px; height: 20px; border-radius: 50%; background: #fff;
    box-shadow: var(--shadow);
    transition: transform var(--m-focus) var(--e-spring);
  }
  .dv-toggle[aria-checked="true"] { background: var(--acc); }
  .dv-toggle[aria-checked="true"]::after { transform: translateX(20px); }

  /* --- CastCard (§13) ---------------------------------------------------- */
  .dv-cast { text-align: center; cursor: pointer; width: 88px; flex: none;
             transition: transform var(--m-focus) var(--e-spring); }
  .dv-cast:hover { transform: scale(1.07); }
  .dv-cast__img {
    width: 88px; height: 88px; border-radius: 50%; overflow: hidden;
    margin: 0 auto var(--s2); border: 1px solid var(--hair); background: var(--card);
  }
  .dv-cast__img img { width: 100%; height: 100%; object-fit: cover; }
  .dv-cast__name { font-size: 13px; font-weight: 600; }
  .dv-cast__role { font-size: 11.5px; color: var(--t2); }

  /* --- Feedback (§16) ---------------------------------------------------- */
  .dv-toast {
    position: fixed; bottom: 30px; left: 50%; transform: translateX(-50%);
    z-index: 100; display: flex; align-items: center; gap: var(--s3);
    padding: 13px var(--s5); border-radius: var(--r-pill);
    background: var(--glass); border: 1px solid var(--hair);
    backdrop-filter: blur(48px) saturate(1.8);
    -webkit-backdrop-filter: blur(48px) saturate(1.8);
    box-shadow: var(--shadowF), inset 0 1px 0 var(--edge);
    font-size: 13.5px; font-weight: 600;
  }
  .dv-toast__ic {
    width: 24px; height: 24px; border-radius: 50%; flex: none;
    display: flex; align-items: center; justify-content: center;
    background: color-mix(in oklab, var(--ok) 22%, transparent); color: var(--ok);
  }
  .dv-toast--error .dv-toast__ic {
    background: color-mix(in oklab, var(--danger) 22%, transparent); color: var(--danger);
  }

  .dv-empty {
    text-align: center; padding: var(--s9) var(--s4);
    color: var(--t2); font-size: 14px;
  }
  .dv-empty__title { font-size: var(--fz-title2); font-weight: 600; color: var(--t1);
                     margin-bottom: var(--s2); }

  .dv-banner {
    display: flex; align-items: center; gap: var(--s3);
    padding: var(--s3) var(--s4); border-radius: var(--r-card);
    background: color-mix(in oklab, var(--danger) 14%, transparent);
    border: 1px solid color-mix(in oklab, var(--danger) 34%, transparent);
    font-size: 13px; color: var(--t1);
  }

  .dv-skeleton {
    background: linear-gradient(90deg, var(--card) 25%,
                color-mix(in srgb, var(--card) 60%, var(--surface)) 50%, var(--card) 75%);
    background-size: 400px 100%;
    animation: dv-shimmer 1.4s linear infinite;
    border-radius: var(--r-card-md);
  }
  @keyframes dv-shimmer {
    0%   { background-position: -400px 0; }
    100% { background-position:  400px 0; }
  }
}

/* ==========================================================================
   @layer pages — responsive rules that apply across the whole v2 surface
   ========================================================================== */
@layer pages {

  /* TV: 1920x1080 @1x, 90px side / 60px top-bottom safe area (§04) */
  @media (min-width: 1800px) {
    :root { --page-pad: 90px; --gutter: 24px; }
  }

  @media (max-width: 1100px) {
    :root { --page-pad: 40px; --row-gap: 40px; }
  }

  /* Below 900px the nav pill minimises to three tabs + More (§08). */
  @media (max-width: 900px) {
    .dv-nav__tab[data-overflow="1"] { display: none; }
  }

  @media (max-width: 760px) {
    :root {
      --page-pad: 16px; --gutter: 12px; --row-gap: 32px; --s6: 20px;
      /* 10px offset + 38px control + breathing room. */
      --page-top: 76px;
    }
    .dv-tabbar { display: flex; }
    body { padding-bottom: calc(86px + env(safe-area-inset-bottom)); }
    .dv-nav { top: 10px; }
    .dv-nav__side { top: 10px; right: var(--s3); }

    /* The centred pill and the side icons are both fixed to the same top
       offset, so at this width they overlapped. Burger on the left, icons on
       the right, nothing in the middle to collide with. */
    .dv-nav__pill:not(.dv-nav__search) { display: none; }
    .dv-nav__burger { display:inline-flex; top:15px; left:var(--s3); }
    /* Search takes the whole bar; everything else steps aside for it.
       Sized off the parent, NOT 100vw: .dv-nav is padded by --s4 (16px), so a
       100vw - 2*--s3 (24px) pill is 8px wider than the box it sits in and
       overflows horizontally — which is what let the page scroll sideways and
       read as "enlarged" on a phone. 100% tracks the padding automatically. */
    .dv-nav__search { width: 100%; }
    body.dv-searching .dv-nav__burger,
    body.dv-searching .dv-nav__side { display: none; }
    /* Same reasoning: match the pill it hangs off, not the raw viewport. */
    .dv-nav__results { top: 56px; width: 100%; }
    .dv-nav__note { top: 56px; width: 100%; }
    .dv-nav__note:not([hidden]) ~ .dv-nav__results { top: 100px; }
    /* --hero-h must move with height or the trailer stops covering (see
       .dv-hero__trailer). */
    .dv-hero--home   { --hero-h: min(72vh, 560px); height: var(--hero-h); }
    /* Mobile header is a shorter bar (top:10px + ~40px button), so the hero
       needs a smaller clearance than the desktop pill's 86px. Tighter radius
       and inset to match the smaller --page-pad at this width. */
    .dv-hero--detail {
      --hero-h: min(64vh, 520px); height: var(--hero-h);
      margin: calc((1 - var(--dt-x)) * 62px) auto 0;
      border-radius: calc((1 - var(--dt-x)) * 20px);
    }
    .dv-hero--detail .dt-body { padding: 0 18px; bottom: 30px; }
    .dv-hero--detail .dt-starring { right: 18px; bottom: 30px; }
    .dv-more { top: 62px; }
    .dv-sheet { right: var(--s3); left: var(--s3); top: auto;
                /* Clears the floating pill (14px gap + 58px tall), which the
                   old 66px did not once the bar stopped sitting on the edge. */
                bottom: calc(84px + env(safe-area-inset-bottom));
                width: auto; }
    .dv-more__grid { grid-template-columns: 1fr; }
    .dv-cast, .dv-cast__img { width: 72px; }
    .dv-cast__img { height: 72px; }
    /* Two across, always — a 220px floor resolves to a single column at this
       width and turns every card page into a stack. */
    .dv-autogrid { grid-template-columns: repeat(2, 1fr); gap: 10px; }
    /* The oversized ranked numeral would swamp a 2-across phone track, so trim
       it (the card itself now fills the track via the rule near .dv-autogrid). */
    .dv-autogrid .dv-card--ranked .dv-rank { font-size: 52px; }
  }

  /* An installed PWA can report a viewport that skips the breakpoint. */
  @media (display-mode: standalone) {
    .dv-tabbar { display: flex; }
    body { padding-bottom: calc(86px + env(safe-area-inset-bottom)); }
  }
  body.is-ios-pwa .dv-tabbar { display: flex; }
  body.is-ios-pwa { padding-bottom: calc(86px + env(safe-area-inset-bottom)); }
}

/* ==========================================================================
   Liquid glass refinement (2026-07-25)
   Ports the specular-edge + SVG-refraction look from the "liquid glass" recipe
   onto the existing surfaces. Unlayered on purpose: unlayered rules outrank any
   @layer, so these overrides win over the base declarations above without
   chasing specificity.

   Two tiers, chosen with the TV in mind (Round 9 — backdrop-filter is what made
   v2 scroll slower than v1 there):
     • Fixed scroll chrome (nav pill, tabbar) + small controls get a crisper
       SPECULAR EDGE only — pure box-shadow, zero per-frame cost.
     • Opened, non-scroll GLASS surfaces (More, drawer, Me/Account/Notif/
       calendar sheets) also get real backdrop REFRACTION — but only where the
       browser genuinely supports SVG backdrop filters AND the pointer is fine
       (desktop). The iOS PWA (coarse pointer) and the TV remote (coarse) never
       hit that branch, so they keep their plain-blur frosting untouched.
   ========================================================================== */
:root{
  /* Diagonal specular: bright top-left, faint bottom-right — the edge that
     reads as a lit glass bevel. Negative spread keeps it a hairline. */
  --glass-spec:
    inset 1.6px 1.6px 1px -1px rgba(255,255,255,0.55),
    inset -1.2px -1.2px 1px -1px rgba(255,255,255,0.26);
  /* More translucent fill used ONLY on the desktop-Chrome refraction branch —
     the opaque --glassThick hides the backdrop, so the refraction would be
     invisible without opening the glass right up. Kept see-through (matches the
     preview's ~0.3) so the bent/prismatic backdrop actually reads. */
  --glass-refract: rgba(12,12,20,0.30);
  /* The fixed nav pill / tabbar / icon buttons were 0.58 — solid enough that at
     rest (no content behind the bar yet) they read as a plate, and the blur
     only became visible once content scrolled underneath. Opened up so the bar
     is visibly glass at rest; the heavy blur still carries tab-label contrast. */
  --nav-glass: rgba(18,18,28,0.38);
  /* The tab bar goes further than the top pill. On a bar this thin, "see the
     page through it" comes almost entirely from the fill alpha — the blur only
     frosts what is already showing — so this is the value that matters, and it
     is its own token so the nav pill above keeps its 0.38. */
  --tabbar-glass: rgba(18,18,28,0.26);
}
:root[data-theme="light"]{
  --glass-spec:
    inset 1.6px 1.6px 1px -1px rgba(255,255,255,0.95),
    inset -1.2px -1.2px 1px -1px rgba(0,0,0,0.05);
  --glass-refract: rgba(250,250,253,0.40);
  --nav-glass: rgba(255,255,255,0.55);
  --tabbar-glass: rgba(255,255,255,0.44);
}

/* — Refined edges: fixed chrome. Even top sheen only (a diagonal specular on
     this thin bar reads as an uneven grey smear — see the .dv-nav__pill note),
     just crisper than before. — */
.dv-nav__pill{
  box-shadow: inset 0 1.5px 0 -0.5px rgba(255,255,255,0.30),
              inset 0 0 0 1px rgba(255,255,255,0.06),
              var(--nav-shadow);
}
.dv-tabbar{
  /* Unlayered, so this wins over the `background: var(--nav-glass)` set on the
     base rule inside @layer components without any specificity games. */
  background: var(--tabbar-glass);
  box-shadow: var(--glass-spec),
              inset 0 1px 0 0 rgba(255,255,255,0.14),
              var(--nav-shadow);
}
/* At this transparency the icons sit almost directly on whatever is scrolling
   underneath, so a hairline shadow keeps their edges readable when a bright
   backdrop passes behind the bar. The active chip is a filled accent and needs
   no help. */
.dv-tabbar__tab:not(.is-on) .dv-ic{
  filter: drop-shadow(0 1px 2px rgba(0,0,0,0.45));
}
:root[data-theme="light"] .dv-tabbar__tab:not(.is-on) .dv-ic{
  filter: drop-shadow(0 1px 2px rgba(0,0,0,0.16));
}

/* — Mobile top chrome: two objects, not five. ------------------------------
   At phone width the top carried a burger on the left and four separate glass
   chips on the right (search, sound, bell, avatar), each with its own fill,
   blur and shadow. Two fixes, both here rather than in @layer components
   because the unlayered `.dv-btn--icon` rule above would otherwise win and put
   the chip backgrounds straight back.

   1. The avatar goes. It opens dvMe — the identical overlay the bottom bar's
      Me slot opens — and the bottom bar is always present at this width, so
      this is a duplicate control, not a shortcut.
   2. What remains becomes ONE pill rather than three chips, mirroring the
      bottom bar so the screen reads as a matched pair of floating surfaces.

   The sound toggle deliberately stays: it does not merely mute, it gates
   whether carousel trailers mount at all (see initHeroCarousel's play()), so
   hiding it contextually would strand anyone who switched it off. — */
@media (max-width: 760px){
  .dv-nav__side .dv-ava{ display: none; }

  .dv-nav__side{
    gap: 2px;
    padding: 4px;
    border-radius: var(--r-pill);
    background: var(--tabbar-glass);
    border: 1px solid var(--edge);
    backdrop-filter: blur(22px) saturate(1.8);
    -webkit-backdrop-filter: blur(22px) saturate(1.8);
    box-shadow: var(--glass-spec), var(--nav-shadow);
  }
  /* The pill is the surface now, so the buttons inside stop being surfaces.
     Sizes are left alone: they are set by a style="" attribute in the template
     that no stylesheet rule can override anyway. */
  .dv-nav__side .dv-btn--icon{
    background: none;
    backdrop-filter: none;
    -webkit-backdrop-filter: none;
    box-shadow: none;
  }
}

/* — Refined edges: small glass controls get the diagonal bevel (they're small
     rounded chips, where it reads clean). — */
.dv-btn--glass{
  box-shadow: var(--glass-spec), inset 0 1px 0 var(--edge);
}
.dv-btn--icon{
  box-shadow: var(--glass-spec),
              inset 0 1px 0 0 rgba(255,255,255,0.16),
              var(--nav-shadow);
}

/* — Opened glass surfaces: specular bevel always (cheap), plus the About/detail
     dialogs which are opaque surfaces — just the bevel, no refraction. — */
.dv-more, .dv-drawer, .dv-sheet{
  box-shadow: var(--glass-spec), var(--shadowF), inset 0 1px 0 var(--edge);
}
.dt-sheet{
  box-shadow: var(--glass-spec), var(--shadowF);
}

/* — Real backdrop refraction: desktop, and only if the engine truly supports an
     SVG url() backdrop filter. Everyone else keeps the blur set on the base
     rule. blur() stays in the value so that even if the displacement is a
     no-op the surface is still frosted. — */
@media (hover: hover) and (pointer: fine){
  /* Desktop only (fine pointer) — iPhone/TV are coarse and never hit this, so
     they keep the opaque frosting. No @supports gate: the quoted-url @supports
     test is unreliable in Chrome and was silently skipping the whole block,
     leaving the opaque 0.78 base. Safari can't do url() filters, so it gets the
     -webkit plain-blur fallback below and simply shows clear frosted glass. */

  /* The modal veil was a 40% black scrim — a clear sheet had nothing but
     darkness to refract. Lighten it so live content shows through and the
     prismatic edge actually reads. */
  .dv-backdrop{ background: rgba(0,0,0,0.20); }

  .dv-more, .dv-drawer, .dv-sheet{
    /* Opened right up so the backdrop shows through; lighter blur than the base
       48px so the gradient-map displacement stays legible. */
    background: var(--glass-refract);
    -webkit-backdrop-filter: blur(13px) saturate(1.7);
    backdrop-filter: url(#dora-glass) blur(13px) saturate(1.7);
  }
}

/* ── TV / D-pad focus (§ dora.js initTvMode) ────────────────────────────────
   Only active once body.dv-tv is set (a real TV, or the first arrow-key press).
   The remote moves focus programmatically, so :focus — not :focus-visible — is
   the state that has to read from across a room: a thick accent ring plus a lift
   on cards. Kept scoped to .dv-tv so mouse/touch users never see a focus outline
   they didn't ask for. */
.dv-tv :focus{ outline: none; }               /* replace the UA ring wholesale */
.dv-tv a:focus,
.dv-tv button:focus,
.dv-tv input:focus,
.dv-tv select:focus,
.dv-tv textarea:focus,
.dv-tv [tabindex]:focus{
  outline: 3px solid var(--acc);
  outline-offset: 3px;
  border-radius: 12px;
}
/* A focused card must NOT box the whole anchor (poster + title) in a rounded
   rectangle — that was the "weird outline". Ring the POSTER only, reusing the
   exact scale+ring the design already uses for keyboard focus, so a TV tile and
   a desktop focused tile look identical. The anchor itself carries no outline. */
.dv-tv a.dv-card:focus,
.dv-tv .dv-card:focus{ outline: none; z-index: 5; }
.dv-tv .dv-card:focus .dv-card__art{
  transform: scale(var(--hover-scale, 1.05));
  box-shadow: 0 0 0 var(--focus-off, 3px) var(--bg),
              0 0 0 calc(var(--focus-off, 3px) + var(--focus-ring, 3px)) var(--acc),
              var(--shadowF, 0 14px 34px -8px rgba(0,0,0,.7));
  filter: brightness(1.05);
}
/* Cast and crew use circular artwork. Suppress the generic anchor rectangle
   and put the D-pad selector directly around the headshot. */
.dv-tv a.dv-person:focus,
.dv-tv .dv-person:focus{
  outline: none;
  background: none;
  box-shadow: none;
  z-index: 5;
}
.dv-tv .dv-person:focus .dv-person__ava{
  transform: scale(var(--hover-scale, 1.05));
  box-shadow: 0 0 0 var(--focus-off, 3px) var(--bg),
              0 0 0 calc(var(--focus-off, 3px) + var(--focus-ring, 3px)) var(--acc),
              var(--shadowF, 0 14px 34px -8px rgba(0,0,0,.7));
  filter: brightness(1.05);
}
/* Nav tabs / pills already carry their own rounding — match it so the ring
   hugs the control instead of boxing a pill in a rectangle. */
.dv-tv .dv-nav__tab:focus,
.dv-tv .dv-btn:focus{ border-radius: var(--r-pill, 999px); }

/* ── 10-foot scaling ──────────────────────────────────────────────────────
   A TV renders the page at 1920×1080 CSS px, which is small from across a room,
   so TV mode scales the whole UI to 125%. `zoom` scales layout, fixed chrome and
   getBoundingClientRect together, so the D-pad geometry stays correct.

   NB the zoom is applied to <html> by the pre-paint inline script in each page
   head (and re-asserted in dora.js enable()), NOT here on body.dv-tv — the class
   is added by JS after first paint, which made every page flash 100%→125%.
   Applying it on <html> only, and only in one place, avoids both the flash and a
   compounding double-zoom. */

/* ── TV performance ───────────────────────────────────────────────────────
   The magic remote makes webOS report a FINE pointer, so the TV was wrongly
   served the desktop liquid-glass path: SVG-filter `backdrop-filter` refraction
   on every glass surface. That's brutal on a TV GPU and was the "slow / buggy".
   Drop all backdrop blur in TV mode and paint solid, opaque surfaces instead. */
body.dv-tv .dv-nav,
body.dv-tv .dv-tabbar,
body.dv-tv .dv-more,
body.dv-tv .dv-drawer,
body.dv-tv .dv-sheet,
body.dv-tv .dv-nav__pill,
body.dv-tv .dv-glass,
body.dv-tv .dv-glass-thick,
body.dv-tv .dv-backdrop,
body.dv-tv .dv-cardmenu,
body.dv-tv .dv-prev{
  -webkit-backdrop-filter: none !important;
  backdrop-filter: none !important;
}
body.dv-tv .dv-nav__pill,
body.dv-tv .dv-more,
body.dv-tv .dv-drawer,
body.dv-tv .dv-sheet,
body.dv-tv .dv-cardmenu{
  /* --surface is the theme-aware OPAQUE panel colour (dark #13131B / light
     #FFFFFF). Hardcoding a dark hex here is what made the light-mode navbar and
     profile dropdown black-on-black. */
  background: var(--surface);
  border: 1px solid var(--hair);
}
body.dv-tv .dv-backdrop{ background: rgba(0,0,0,.6); }
/* TV remotes send repeat keys quickly. Keep focus feedback crisp and stop CSS
   scroll snapping/smoothing from fighting the spatial navigator's controlled
   edge reveal. */
body.dv-tv{
  --m-instant: 80ms;
  --m-focus: 105ms;
  --m-move: 150ms;
  --m-ambient: 280ms;
}
body.dv-tv .dv-shelf,
body.dv-tv .dv-drawer,
body.dv-tv .dv-sheet,
body.dv-tv .dt-sheet,
body.dv-tv .dv-more,
body.dv-tv .dv-cardmenu{
  scroll-behavior: auto !important;
}
body.dv-tv .dv-shelf{ scroll-snap-type: none; }
body.dv-tv .dv-card__art{
  /* On the LG's 4K DPR=2 surface, scaling and repainting a blurred shadow for
     every repeat-key focus move cut the measured frame rate in half. A solid
     outline remains unmistakable from the couch and does not allocate a large
     repainted texture. */
  transition: none !important;
}
body.dv-tv .dv-card:focus .dv-card__art{
  transform: none !important;
  filter: none;
  box-shadow: none !important;
  outline: 4px solid var(--acc);
  outline-offset: 2px;
}
/* Keep the primary controls reachable and visible when focus travels back to
   the top. A translated-offscreen nav was still in the geometry index. */
body.dv-tv .dv-nav{
  transform: none !important;
  opacity: 1 !important;
}
/* Injected ✕ Close for TV overlays (dora.js ensureTvClose) — pinned top-right,
   large enough to read the focus ring from the couch. */
body.dv-tv .dv-tvclose{
  position: absolute; top: 14px; right: 14px; z-index: 3;
  width: 46px; height: 46px;
}
/* Note: we deliberately DON'T hide the pointer (`cursor:none`) in TV mode. The
   magic-remote pointer is a webOS OS overlay we can't remove anyway, and it's
   the universal fallback for pages the D-pad can't fully cover (login forms /
   the on-screen keyboard). The D-pad focus ring is the primary affordance; the
   pointer simply stays available. */

/* -----------------------------------------------------------------------------
   Detail hero -- reference layout (2026-08-18)

   Unlayered on purpose, like the glass refinement above it: these rules have to
   beat @layer components without a specificity fight.

   The shape: title art, then genres on their own line, the actions, the facts
   badges, the director, the synopsis and the scores -- with a glass panel of
   runtime / language / release / box office pinned to the right on wide
   screens. That panel is rendered twice (see facts_card in _components.html);
   .dt-body is absolutely positioned inside the zoom rig and cannot reflow out
   of the hero, so narrow screens get an in-flow copy below it instead.

   Note: the .dt-starring rules earlier in this file are now dead -- the hero no
   longer renders that strip. They are left in place rather than unpicked from
   nine scattered, comma-joined selector lists for the sake of a few bytes.
   -------------------------------------------------------------------------- */

/* Genres lead, on their own line. They used to share a 14px row with the kind
   and the certificate, which buried them. */
.dt-gline { display: flex; flex-wrap: wrap; align-items: center; gap: 8px;
  font-size: 15px; font-weight: 600; color: rgba(255,255,255,0.92); }
.dt-gline__g { color: inherit; text-decoration: none; }
.dt-gline__g:hover { text-decoration: underline; text-underline-offset: 4px; }
.dt-gline__dot { color: rgba(255,255,255,0.40); }

/* Director(s), directly under the badge row. */
.dt-crew { font-size: 14px; color: rgba(255,255,255,0.72); }
.dt-crew__k { color: rgba(255,255,255,0.45); margin-right: 6px; }
.dt-crew__n { color: rgba(255,255,255,0.86); text-decoration: none; }
.dt-crew__n:hover { color: #fff; text-decoration: underline;
  text-underline-offset: 4px; }

/* The star is the only coloured thing in the badge row, so it reads as a
   rating rather than as one more grey chip. */
.dt-vote { display: inline-flex; align-items: center; gap: 4px; }
.dt-vote__ic { color: #FACC15; }

/* -- Ratings strip -------------------------------------------------------- */
.dt-rateline { display: flex; flex-wrap: wrap; align-items: center;
  gap: 10px 20px; margin-top: var(--s1); }
.dt-rateline__one { display: inline-flex; align-items: center; gap: 7px; }
.dt-rateline__logo { height: 19px; width: auto; display: block;
  object-fit: contain; }
/* The IMDb wordmark is a wide lozenge; matching the icons' height would make it
   tower over them. */
.dt-rateline__logo--imdb { height: 16px; }
/* Ours is a photo, not a mark: crop it round so it sits with the icons. */
.dt-rateline__logo--club { height: 20px; width: 20px; border-radius: 50%;
  object-fit: cover; }
.dt-rateline__val { font-size: 16px; font-weight: 600; color: #fff;
  line-height: 1; }
.dt-rateline__lbl { font-size: 12px; font-weight: 600;
  color: rgba(255,255,255,0.60); }
.dt-rateline__q { display: inline-flex; align-items: center; padding: 2px 7px;
  border: 1px solid rgba(255,255,255,0.30); border-radius: 5px;
  font-size: 11px; font-weight: 700; letter-spacing: .04em; }

/* -- Facts card ----------------------------------------------------------- */
.dt-panel__card { border-radius: 14px; overflow: hidden;
  background: var(--tabbar-glass); border: 1px solid var(--hair);
  backdrop-filter: blur(22px) saturate(1.8);
  -webkit-backdrop-filter: blur(22px) saturate(1.8);
  box-shadow: var(--glass-spec), var(--nav-shadow); }
.dt-panel__row { display: flex; align-items: center;
  justify-content: space-between; gap: var(--s4); padding: 9px 14px;
  border-top: 1px solid rgba(255,255,255,0.07); }
.dt-panel__row:first-child { border-top: 0; }
.dt-panel__k { font-size: 12px; color: rgba(255,255,255,0.45);
  white-space: nowrap; }
.dt-panel__v { font-size: 12px; color: rgba(255,255,255,0.82);
  text-align: right; }
/* Filled in by dora.js from the reader's own clock, and hidden until then. */
.dt-panel__ends { color: rgba(255,255,255,0.50); }
.dt-panel__ends::before { content: "\00b7"; margin: 0 5px;
  color: rgba(255,255,255,0.30); }
.dt-panel__row--go { text-decoration: none; color: inherit;
  transition: background-color var(--m-instant) var(--e-out); }
.dt-panel__row--go:hover { background: rgba(255,255,255,0.07);
  text-decoration: none; }
.dt-panel__row--go:focus-visible { outline: 2px solid var(--acc);
  outline-offset: -2px; }
.dt-panel__k--icon { display: inline-flex; align-items: center; gap: 8px;
  color: rgba(255,255,255,0.82); font-size: 12.5px; white-space: normal; }
.dt-panel__chev { color: rgba(255,255,255,0.40); flex: none; }

/* -- Studio logos --------------------------------------------------------- */
.dt-slogos { display: grid; grid-template-columns: repeat(2, 1fr); gap: 8px;
  margin-top: 14px; }
.dt-slogo { display: flex; align-items: center; justify-content: center;
  height: 40px; padding: 0 8px; }
/* TMDB ships these as coloured wordmarks that fight the artwork behind them.
   brightness(0) crushes any colour to black and invert(1) lifts it to white --
   one flat silhouette whatever the source, which is what makes a row of logos
   from different studios look deliberate rather than pasted on. */
.dt-slogo__img { max-height: 28px; max-width: 100%; width: auto;
  object-fit: contain; filter: none; opacity: 1; }

/* -- Placement ------------------------------------------------------------ */
/* Narrow by default: the hero-pinned column is absent and the in-flow copies
   below the hero carry the content. */
.dt-aside { display: none; }
.dt-panel--flow { margin: 0 0 var(--s5); }
.dt-slogos--flow { margin: 0 0 var(--s6); max-width: 420px; }

@media (min-width: 1024px) {
  .dt-aside { display: block; position: absolute; z-index: 3;
    right: 5%; bottom: 13%; width: 280px; }
  /* Wide screens take the pinned column instead, so the in-flow twins go. */
  .dt-panel--flow, .dt-slogos--flow { display: none; }
  .dt-gline { font-size: 18px; }
  .dt-crew { font-size: 15.5px; }
  .dt-rateline__val { font-size: 18px; }

  /* Rise with the rest of the hero copy rather than appearing before it --
     .dt-body's own reveal is declared with the titlecar rules further up. */
  .dv-hero--titlecar .dt-aside { opacity: 0; transform: translateY(28px);
    transition: opacity .9s var(--e-out), transform .9s var(--e-out); }
  body.dora-hero-ready .dv-hero--titlecar .dt-aside { opacity: 1;
    transform: none; }
}

@media (prefers-reduced-motion: reduce) {
  .dv-hero--titlecar .dt-aside { opacity: 1; transform: none;
    transition: none; }
}

/* TV sits well above the 1024px breakpoint, so it gets the pinned column -- but
   at ten feet the panel needs to be bigger than it is on a laptop, and
   .dt-stage is already zoomed to 0.8, which shrinks it further. */
:root[data-tv] .dt-aside,
body.dv-tv .dt-aside { width: 360px; bottom: 10.5%; right: 5%; }
:root[data-tv] .dt-panel__k, body.dv-tv .dt-panel__k,
:root[data-tv] .dt-panel__v, body.dv-tv .dt-panel__v { font-size: 15px; }
:root[data-tv] .dt-panel__k--icon,
body.dv-tv .dt-panel__k--icon { font-size: 15.5px; }
:root[data-tv] .dt-panel__row, body.dv-tv .dt-panel__row { padding: 12px 18px; }
:root[data-tv] .dt-gline, body.dv-tv .dt-gline { font-size: 22px; }
:root[data-tv] .dt-crew, body.dv-tv .dt-crew { font-size: 18px; }
:root[data-tv] .dt-rateline__val,
body.dv-tv .dt-rateline__val { font-size: 21px; }
:root[data-tv] .dt-rateline__logo,
body.dv-tv .dt-rateline__logo { height: 24px; }
:root[data-tv] .dt-rateline__logo--imdb,
body.dv-tv .dt-rateline__logo--imdb { height: 20px; }
:root[data-tv] .dt-slogo__img, body.dv-tv .dt-slogo__img { max-height: 34px; }

/* Light theme: the hero copy sits on artwork under a dark veil in both themes,
   so the white text above stays white -- but the card's own fill has to come
   down in opacity or it turns into a pale slab against a bright backdrop. */
:root[data-theme="light"] .dt-panel__card {
  background: rgba(20,20,30,0.30);
  border-color: rgba(255,255,255,0.14);
}
:root[data-theme="light"] .dt-panel__row {
  border-top-color: rgba(255,255,255,0.10);
}

/* -----------------------------------------------------------------------------
   Detail hero, second pass (2026-08-18)

   Larger type throughout, the facts panel de-shadowed, and the new clickable
   surfaces: the ratings strip opens the full breakdown, studio logos open the
   company's catalogue.
   -------------------------------------------------------------------------- */

/* -- Type: the hero reads from across a room, not from a code editor ------- */
.dt-gline { font-size: 17px; gap: 10px; }
.dt-crew { font-size: 15px; }
.dt-blurb { font-size: 16.5px; line-height: 1.55; max-width: min(660px, 100%); }
.dt-badges { font-size: 14.5px; gap: 12px; }
.dt-chip { font-size: 12px; padding: 3px 9px; }
.dt-vote { font-size: 15px; font-weight: 700; }

@media (min-width: 1024px) {
  .dt-gline { font-size: 21px; gap: 12px; }
  .dt-crew { font-size: 17px; }
  .dt-blurb { font-size: 18px; line-height: 1.6; }
  .dt-badges { font-size: 16.5px; gap: 14px; }
  .dt-chip { font-size: 13px; padding: 3px 10px; }
  .dt-vote { font-size: 17px; }
  .dt-rateline__val { font-size: 19px; }
  .dt-panel__k, .dt-panel__v { font-size: 13px; }
}

/* Read More was a child of .dt-blurb, which is a -webkit-line-clamp box with
   overflow:hidden — so the clamp that created the need for the button was also
   clipping the button. It is a sibling now, and styled as a plain text link
   because a pill competed with the actual buttons above it. */
.dt-more { margin: 0; padding: 0; background: none; border: 0;
  backdrop-filter: none; -webkit-backdrop-filter: none;
  align-self: flex-start; cursor: pointer;
  font: inherit; font-size: 14px; font-weight: 600; letter-spacing: 0;
  color: rgba(255,255,255,0.62); text-transform: none;
  transition: color var(--m-instant) var(--e-out); }
.dt-more:hover { background: none; color: #fff; text-decoration: underline;
  text-underline-offset: 4px; }
.dt-more:focus-visible { outline: 2px solid var(--acc); outline-offset: 3px;
  border-radius: 4px; }
@media (min-width: 1024px) { .dt-more { font-size: 15px; } }

/* -- The panel loses its drop shadow -------------------------------------- */
/* Only the inner bevel is kept: it is what reads as glass. The outer shadow
   made the panel look like a card sitting on the artwork rather than a pane
   cut into it. */
.dt-panel__card { box-shadow: var(--glass-spec); }

/* -- Ratings strip as a control ------------------------------------------- */
.dt-rateline--go { -webkit-appearance: none; appearance: none;
  border: 0; padding: 6px 10px; margin-left: -10px; cursor: pointer;
  border-radius: 12px; background: transparent; color: inherit; font: inherit;
  text-align: left;
  transition: background-color var(--m-instant) var(--e-out); }
.dt-rateline--go:hover { background: rgba(255,255,255,0.08); }
.dt-rateline--go:focus-visible { outline: 2px solid var(--acc);
  outline-offset: 2px; }
.dt-rateline__chev { color: rgba(255,255,255,0.45); flex: none;
  transition: transform var(--m-instant) var(--e-out); }
.dt-rateline--go:hover .dt-rateline__chev { transform: translateX(3px); }

/* -- Ratings breakdown modal ---------------------------------------------- */
.dt-rmodal { padding: var(--s5); max-width: 460px; }
.dt-rmodal__head { display: flex; align-items: center;
  justify-content: space-between; gap: var(--s4); margin-bottom: var(--s4); }
.dt-rmodal__title { margin: 0; font-size: 18px; font-weight: 700; color: var(--t1);
  overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.dt-rmodal__list { display: flex; flex-direction: column; }
.dt-rrow { display: flex; align-items: center; justify-content: space-between;
  gap: var(--s4); padding: 11px 4px; text-decoration: none; color: inherit;
  border-top: 1px solid var(--hair); }
.dt-rrow:first-child { border-top: 0; }
.dt-rrow--go { border-radius: 8px;
  transition: background-color var(--m-instant) var(--e-out); }
.dt-rrow--go:hover { background: rgba(127,127,140,0.12); text-decoration: none; }
.dt-rrow__src { display: inline-flex; align-items: center; gap: 10px;
  min-width: 0; }
.dt-rrow__logo { height: 20px; width: auto; object-fit: contain; flex: none; }
.dt-rrow__logo--imdb { height: 16px; }
.dt-rrow__logo--club { height: 22px; width: 22px; border-radius: 50%;
  object-fit: cover; }
.dt-rrow__label { font-size: 14px; font-weight: 600; color: var(--t1);
  overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.dt-rrow__right { display: flex; flex-direction: column; align-items: flex-end;
  gap: 2px; flex: none; }
.dt-rrow__val { font-size: 16px; font-weight: 700; color: var(--t1);
  line-height: 1; }
.dt-rrow__val i { font-style: normal; font-size: 11px; font-weight: 600;
  color: var(--t3); margin-left: 1px; }
.dt-rrow__votes { font-size: 11px; color: var(--t3); }

/* -- Clickable studio logos ------------------------------------------------ */
a.dt-slogo { border-radius: 8px; text-decoration: none;
  transition: background-color var(--m-instant) var(--e-out); }
a.dt-slogo:hover { background: rgba(255,255,255,0.08); text-decoration: none; }
/* The logos sit at 50% opacity so they do not fight the artwork; hovering
   brings the one under the cursor fully forward, which is the affordance that
   says "this is a link" without adding chrome. */
a.dt-slogo:hover .dt-slogo__img { opacity: .95; }
a.dt-slogo:focus-visible { outline: 2px solid var(--acc); outline-offset: 2px; }
.dt-slogo__img { transition: opacity var(--m-instant) var(--e-out); }
.dt-panel__a { color: inherit; text-decoration: none; }
.dt-panel__a:hover { color: #fff; text-decoration: underline;
  text-underline-offset: 3px; }

/* -- Studio page ----------------------------------------------------------- */
.dt-studiohead { display: flex; align-items: center; gap: var(--s5);
  margin-bottom: var(--s6); flex-wrap: wrap; }
.dt-studiohead__logo { max-height: 64px; max-width: 220px; width: auto;
  object-fit: contain; }
:root:not([data-theme="light"]) .dt-studiohead__logo {
  filter: brightness(0) invert(1); opacity: .85;
}

/* TV: everything above is sized for a desk. Push it up for ten feet. */
:root[data-tv] .dt-blurb, body.dv-tv .dt-blurb { font-size: 21px; }
:root[data-tv] .dt-more, body.dv-tv .dt-more { font-size: 18px; }
:root[data-tv] .dt-badges, body.dv-tv .dt-badges { font-size: 19px; }
:root[data-tv] .dt-vote, body.dv-tv .dt-vote { font-size: 20px; }
:root[data-tv] .dt-rrow__label, body.dv-tv .dt-rrow__label { font-size: 17px; }
:root[data-tv] .dt-rrow__val, body.dv-tv .dt-rrow__val { font-size: 19px; }
:root[data-tv] .dt-rmodal, body.dv-tv .dt-rmodal { max-width: 620px; }

/* -----------------------------------------------------------------------------
   Hero stage height (2026-08-18)

   The first heading under the hero (Trailers, or Seasons on a series) was
   rendering on top of the hero art instead of below it.

   The maths: .dt-hcard--active is absolute at top:9vh with height:118vh, and
   the scaler applies `scale(s) translateY(-9e vh)` where s runs 0.86 -> 1 as
   you scroll. At full zoom (s=1, e=1) the card's visual box is 0 -> 118vh, but
   .dt-stage reserved only 112vh — so the following content started 6vh early.
   It drew *over* the hero rather than under it because `body.dora-title
   .dv-wrap` is z-index:2 against the stage's z-index:1.

   Reserving the full 118vh is what puts the heading right underneath. The
   extra 6vh is invisible at rest: at the top of the page the card is scaled to
   0.86 and ends at 109vh, and by the time it reaches full height you have
   scrolled past (RANGE is 130px).
   -------------------------------------------------------------------------- */
.dt-stage { height: 118vh; }

/* Both of these already collapse the rig to normal flow, so the taller stage
   must not follow them there. */
@media (prefers-reduced-motion: reduce) { .dt-stage { height: auto; } }
@media (max-width: 760px) { .dt-stage { height: auto; } }

/* -----------------------------------------------------------------------------
   Ratings modal: centred, not anchored (2026-08-18)

   .dv-sheet is a top-right dropdown panel — `position:fixed; right:var(--s4);
   top:68px` — because every other sheet hangs off the nav. This one is a
   dialog opened from the middle of the page, so it was landing in the corner
   with nothing to hang from.

   Both the resting and open transforms have to be restated: .dv-sheet animates
   with `transform: translateY(-8px) scale(.98)` -> `none`, and `none` would
   throw away the centring translate the moment it opened.
   -------------------------------------------------------------------------- */
.dv-sheet.dt-rmodal {
  right: auto; bottom: auto;
  left: 50%; top: 50%;
  width: min(460px, calc(100vw - 32px));
  max-height: min(80vh, 720px);
  transform: translate(-50%, -48%) scale(0.98);
}
.dv-sheet.dt-rmodal.is-open {
  transform: translate(-50%, -50%) scale(1);
}

/* Letterboxd ships as a single dark mark (#202830), which all but disappears
   on the dark sheet. Flatten it to white there; the light theme keeps it as
   drawn. TMDB and Trakt carry their own gradients and are left alone. */
:root:not([data-theme="light"]) .dt-rrow__logo--letterboxd {
  filter: brightness(0) invert(1);
}
/* The three new marks are square-ish SVGs rather than wordmarks, so they need
   a width cap too or they set the row height. */
.dt-rrow__logo--letterboxd,
.dt-rrow__logo--tmdb,
.dt-rrow__logo--trakt { height: 18px; width: 18px; object-fit: contain; }
.dt-rrow__logo--tmdb { width: 26px; }

/* -----------------------------------------------------------------------------
   Mobile hero: settled, not curved (2026-08-18)

   The phone now gets the hero in its finished state — edge to edge, square
   across the top — instead of the inset card with rounded corners that the
   desktop zoom rig starts from. On a 390px screen those corners cost real
   artwork and read as a mistake rather than a flourish.

   The matching JS guard is in initTitleHero: the rig writes INLINE transform
   and border-radius, which beat any stylesheet, so `transform:none` here was
   never enough on its own.

   Deliberately not touching the desktop rig, which still zooms 0.86 -> 1.
   -------------------------------------------------------------------------- */
@media (max-width: 760px) {
  .dt-scaler { transform: none !important; height: auto; }

  /* Full-bleed regardless of any padded ancestor: 100vw from the viewport's
     left edge, rather than 100% of whatever contains it. */
  .dt-hcard--active,
  .dt-hcard.dv-hero--detail {
    position: relative;

    /* Full-bleed via margins alone. The `left:50%; margin-left:-50vw` variant
       of this trick relies on the containing block being exactly 100vw; when
       it is not, the card slides sideways and you end up looking at the left
       of the artwork instead of the middle. `margin-inline` needs no offsets
       and cannot drift, because the 50% and the 50vw are measured on the same
       box the margin applies to. */
    left: auto; right: auto;
    width: 100vw; max-width: 100vw;
    margin-inline: calc(50% - 50vw);

    /* --hero-h must move with height or the trailer stops covering: it sizes
       itself off this variable (min-height / min-width * 1.7778), and the
       mobile default is min(64vh,520px) — 80px shorter than this card, which
       left the video letterboxed inside its own hero. See the note at
       .dv-hero. */
    --hero-h: min(72vh, 600px);
    height: var(--hero-h);
    border-radius: 0 !important;

    /* Two separate sources of dead space above the hero, both of which only
       ever looked right because the scroll rig was papering over them:

       1. `top: 9vh` on .dt-hcard. That is an absolute offset on desktop, but
          the card is position:relative here, so it became a 9vh downward
          shove. The rig's `translateY(-9vh)` used to cancel it; with the rig
          off on phones there is nothing left to cancel it.

       2. `margin-top: calc((1 - var(--dt-x)) * 62px)` from @layer pages, which
          beats the components-layer `margin: 0` because pages is declared
          last. --dt-x never moves off 0 here: initHeroExpand() returns early
          for .dv-hero--titlecar, deliberately, so the two scroll effects do
          not fight. So that calc is a permanent 62px. */
    top: 0;
    margin-top: 0;
  }

  /* The copy sits lower and tighter to the edge now there is no inset card
     shoulder to clear. */
  .dv-hero--titlecar .dt-body { bottom: 26px; padding: 0 18px; }

  /* A 16:9 backdrop in a portrait box keeps roughly the middle third of the
     frame, so say which third explicitly rather than inheriting a default.
     Slightly above centre: faces and title treatments sit in the upper half of
     a backdrop far more often than the lower one, and the bottom of this hero
     is under the veil and the copy anyway. */
  .dv-hero__art { object-position: 50% 42%; }

  /* The veil has to work harder without the card's own edge to separate the
     hero from the page beneath it. */
  .dv-hero--titlecar .dv-hero__veil {
    background:
      linear-gradient(0deg, var(--bg) 2%,
        color-mix(in srgb, var(--bg) 55%, transparent) 30%,
        color-mix(in srgb, var(--bg) 12%, transparent) 62%,
        transparent 100%);
  }
}

/* -----------------------------------------------------------------------------
   Episode ratings heatmap (2026-08-18)

   Seasons across, episodes down. The colour ramp is the one everybody already
   reads on this kind of chart — green good, red bad — so it is deliberately
   NOT rebuilt out of the accent palette: --acc is blue, and a blue "good" on a
   score grid reads as a selection, not a verdict.

   The bands are stated as literal colours for the same reason, but they are
   pinned to the same oklch lightness so no band shouts over its neighbours,
   and the text colour flips with the band rather than being white throughout
   (white on the yellow band fails contrast badly).
   -------------------------------------------------------------------------- */

.dt-heat {
  right: auto; bottom: auto;
  left: 50%; top: 50%;
  transform: translate(-50%, -48%) scale(0.98);
  width: min(720px, calc(100vw - 32px));
  max-height: min(86vh, 760px);
  padding: var(--s5);
  display: flex; flex-direction: column; gap: var(--s4);
}
.dt-heat.is-open { transform: translate(-50%, -50%) scale(1); }

.dt-heat__head { display: flex; align-items: flex-start;
  justify-content: space-between; gap: var(--s4); }
.dt-heat__title { margin: 0; font-size: 19px; font-weight: 700; color: var(--t1); }
.dt-heat__sub { margin: 3px 0 0; font-size: 13px; color: var(--t3); }

.dt-heat__legend { display: flex; flex-wrap: wrap; gap: 6px 14px;
  justify-content: center; }
.dt-heat__key { display: inline-flex; align-items: center; gap: 6px;
  font-size: 11.5px; color: var(--t2); }
.dt-heat__sw { width: 11px; height: 11px; border-radius: 3px; display: block; }

/* The ramp. Two greens rather than one so a 9.0 is visibly not an 8.0 — the
   difference people most want to see is at the top of the scale. */
.dt-heat__sw.is-s,  .dt-heat__cell.is-s  { background: #15803d; color: #fff; }
.dt-heat__sw.is-a,  .dt-heat__cell.is-a  { background: #22c55e; color: #06210f; }
.dt-heat__sw.is-b,  .dt-heat__cell.is-b  { background: #eab308; color: #241d00; }
.dt-heat__sw.is-c,  .dt-heat__cell.is-c  { background: #f97316; color: #2a1000; }
.dt-heat__sw.is-d,  .dt-heat__cell.is-d  { background: #dc2626; color: #fff; }
.dt-heat__sw.is-na, .dt-heat__cell.is-na { background: rgba(127,127,140,0.28);
  color: var(--t3); }

.dt-heat__scroll { overflow: auto; margin: 0 calc(var(--s5) * -1);
  padding: 0 var(--s5); }
.dt-heat__grid { border-collapse: separate; border-spacing: 3px;
  margin-inline: auto; }

/* Sticky headers are what make a 21-season show usable: scroll to S17 and the
   episode numbers are still on screen. */
.dt-heat__col, .dt-heat__row, .dt-heat__corner {
  position: sticky; z-index: 2; background: var(--glassThick);
  font-size: 10.5px; font-weight: 700; color: var(--t2);
}
.dt-heat__col { top: 0; padding: 4px 2px; }
.dt-heat__row { left: 0; padding: 0 8px 0 2px; text-align: right;
  font-variant-numeric: tabular-nums; }
.dt-heat__corner { top: 0; left: 0; z-index: 3; }

/* Sized so the common case fits the sheet outright rather than scrolling:
   at 44px a 12-season run still lands inside 720px. Long runs shrink again
   via .dt-heat--dense, and the scroller remains the backstop beyond that. */
.dt-heat__cell {
  width: 44px; height: 33px; padding: 0;
  border-radius: 8px; text-align: center; vertical-align: middle;
  font-size: 12.5px; font-weight: 700; font-variant-numeric: tabular-nums;
  cursor: default;
  transition: transform var(--m-instant) var(--e-out);
}
.dt-heat__cell:hover { transform: scale(1.06); }
/* A season that simply ended earlier is blank, not "?" — "?" is reserved for
   an episode that exists and has no rating yet. The distinction matters on a
   currently-airing season, which is exactly when people open this. */
.dt-heat__cell.is-none { background: none; }
/* The show's best episode. A ring rather than another colour, so it reads on
   top of whichever band it lands in. */
.dt-heat__cell.is-best { box-shadow: 0 0 0 2px var(--t1), 0 0 0 4px rgba(0,0,0,0.35); }

.dt-heat__avgrow .dt-heat__cell { height: 29px; font-size: 11.5px; opacity: .92; }

/* Long-running shows: one more step down so Grey's Anatomy (21 seasons) still
   reads as a single grid instead of a horizontal scroll. */
.dt-heat--dense .dt-heat__cell { width: 34px; height: 27px; font-size: 11px;
  border-radius: 6px; }
.dt-heat--dense .dt-heat__avgrow .dt-heat__cell { height: 24px; font-size: 10px; }
.dt-heat--dense .dt-heat__grid { border-spacing: 2px; }
.dt-heat--dense .dt-heat__col,
.dt-heat--dense .dt-heat__row { font-size: 9.5px; }
.dt-heat--dense .dt-heat__row { padding-right: 5px; }
.dt-heat__avgrow .dt-heat__row { color: var(--t1); }

.dt-heat__foot { margin: 0; font-size: 12px; color: var(--t3); text-align: center; }
.dt-heat__foot strong { color: var(--t1); }

/* Trigger in the Seasons header, matching the season pills it sits beside. */
.dt-heatbtn { display: inline-flex; align-items: center; gap: 7px;
  padding: 7px 13px; border-radius: var(--r-pill); cursor: pointer;
  font: inherit; font-size: 12.5px; font-weight: 600; color: var(--t1);
  background: var(--glass); border: 1px solid var(--edge);
  transition: background-color var(--m-instant) var(--e-out); }
.dt-heatbtn:hover { background: var(--glassThick); }
.dt-heatbtn:focus-visible { outline: 2px solid var(--acc); outline-offset: 2px; }

@media (max-width: 760px) {
  /* Phones get smaller cells rather than a smaller grid: shrinking the whole
     table would make the numbers unreadable, and the scroller already handles
     the width. */
  .dt-heat { padding: var(--s4); }
  .dt-heat__scroll { margin: 0 calc(var(--s4) * -1); padding: 0 var(--s4); }
  .dt-heat__cell { width: 38px; height: 30px; font-size: 11.5px; }
  .dt-heat--dense .dt-heat__cell { width: 31px; height: 26px; font-size: 10px; }
  .dt-heat__title { font-size: 17px; }
  .dt-heat__legend { gap: 5px 10px; font-size: 11px; }
}

/* TV: read from ten feet, and the D-pad cannot hover, so the cells have to
   carry their numbers at size. */
:root[data-tv] .dt-heat, body.dv-tv .dt-heat { width: min(900px, 92vw); }
:root[data-tv] .dt-heat__cell, body.dv-tv .dt-heat__cell {
  width: 56px; height: 42px; font-size: 16px; }
:root[data-tv] .dt-heat__col, body.dv-tv .dt-heat__col,
:root[data-tv] .dt-heat__row, body.dv-tv .dt-heat__row { font-size: 14px; }

/* Light theme: the bands stay as they are — they are a data scale, not chrome,
   and they read on both grounds — but the sticky headers need the light
   surface behind them or the cells show through as they scroll under. */
:root[data-theme="light"] .dt-heat__col,
:root[data-theme="light"] .dt-heat__row,
:root[data-theme="light"] .dt-heat__corner { background: var(--surface); }

/* -----------------------------------------------------------------------------
   Episode popup from the heatmap (2026-08-18)

   Sits above the grid rather than replacing it, so the cell you came from is
   still visible behind — the point is "this score, this episode, play it", and
   losing the chart would break that thread.
   -------------------------------------------------------------------------- */
.dt-eppop {
  position: fixed; z-index: 80; left: 50%; top: 50%;
  transform: translate(-50%, -50%);
  width: min(400px, calc(100vw - 32px));
  border-radius: var(--r-sheet-md, 20px); overflow: hidden;
  background: var(--glassThick); border: 1px solid var(--hair);
  backdrop-filter: blur(48px) saturate(1.8);
  -webkit-backdrop-filter: blur(48px) saturate(1.8);
  box-shadow: var(--glass-spec), var(--shadowF);
  opacity: 0; visibility: hidden;
  transition: opacity var(--m-instant) var(--e-out),
              transform var(--m-focus) var(--e-spring),
              visibility 0s linear var(--m-focus);
}
.dt-eppop.is-open { opacity: 1; visibility: visible;
  transform: translate(-50%, -50%) scale(1); transition-delay: 0s; }
.dt-eppop__x { position: absolute; z-index: 2; top: 10px; right: 10px;
  width: 30px; height: 30px; display: grid; place-items: center;
  border: 0; border-radius: 50%; cursor: pointer;
  background: rgba(10,10,16,0.55); color: #fff;
  backdrop-filter: blur(10px); -webkit-backdrop-filter: blur(10px); }
.dt-eppop__x:hover { background: rgba(10,10,16,0.8); }
.dt-eppop__art { aspect-ratio: 16 / 9; background-size: cover;
  background-position: center; background-color: var(--card); }
/* No still on file: a plain plate reads as deliberate, a broken image does not. */
.dt-eppop__art.is-empty { background-image:
  linear-gradient(135deg, var(--card), var(--surface)); }
.dt-eppop__body { padding: var(--s4) var(--s5) var(--s5); }
.dt-eppop__no { font-size: 11px; font-weight: 700; letter-spacing: .06em;
  color: var(--t3); text-transform: uppercase; }
.dt-eppop__t { margin: 4px 0 0; font-size: 17px; font-weight: 700;
  color: var(--t1); line-height: 1.25; }
.dt-eppop__p { margin: 8px 0 0; font-size: 13px; line-height: 1.5;
  color: var(--t2);
  display: -webkit-box; -webkit-line-clamp: 3; -webkit-box-orient: vertical;
  overflow: hidden; }
.dt-eppop__p:empty { display: none; }
.dt-eppop__act { margin-top: var(--s4); }
.dt-eppop__act .dv-btn { width: 100%; justify-content: center; }

/* Only a cell we can actually play invites a click. */
.dt-heat__cell.is-play { cursor: pointer; }
.dt-heat__cell.is-play:hover { transform: scale(1.12); }
.dt-heat__cell.is-play:focus-visible { outline: 2px solid var(--t1);
  outline-offset: 2px; }

/* -----------------------------------------------------------------------------
   Season picker with poster cards + episode-controls group (2026-08-18)

   The Seasons header is justify-content:space-between, so adding the Ratings
   button as a third child pushed the season control into the middle of the
   row. Both controls now live in one group, which restores the picker to the
   far right where it belongs.
   -------------------------------------------------------------------------- */
.dt-epctl { display: flex; align-items: center; gap: var(--s3);
  margin-left: auto; flex: none; }

.dt-seasonpick { position: relative; }
.dt-seasonpick__btn { display: inline-flex; align-items: center; gap: 7px;
  padding: 7px 13px; border-radius: var(--r-pill); cursor: pointer;
  font: inherit; font-size: 12.5px; font-weight: 600; color: var(--t1);
  background: var(--glass); border: 1px solid var(--edge);
  transition: background-color var(--m-instant) var(--e-out); }
.dt-seasonpick__btn:hover { background: var(--glassThick); }
.dt-seasonpick__btn:focus-visible { outline: 2px solid var(--acc);
  outline-offset: 2px; }

/* Right-anchored, not centred: it hangs off a control that is itself at the
   right edge, so centring it would push it off screen on a narrow window. */
.dt-seasonpick__menu { position: absolute; top: calc(100% + 8px); right: 0;
  z-index: 40; width: min(290px, 88vw);
  max-height: min(56vh, 440px); overflow-y: auto;
  display: flex; flex-direction: column; gap: 2px;
  padding: 8px; border-radius: var(--r-sheet-sm, 16px);
  background: var(--surface); border: 1px solid var(--edge);
  box-shadow: var(--shadowF); }

.dt-seasoncard { display: flex; align-items: center; gap: 11px; width: 100%;
  padding: 7px 8px; border: 0; border-radius: 11px; cursor: pointer;
  background: none; color: var(--t2); font: inherit; text-align: left;
  transition: background-color var(--m-instant) var(--e-out); }
.dt-seasoncard:hover { background: var(--glassThick); color: var(--t1); }
.dt-seasoncard.is-on { background: var(--btn); color: var(--btnText); }
.dt-seasoncard__art { flex: none; width: 40px; height: 60px; border-radius: 6px;
  overflow: hidden; background: var(--card); display: grid; place-items: center; }
.dt-seasoncard__art img { width: 100%; height: 100%; object-fit: cover;
  display: block; }
.dt-seasoncard__plate { font-size: 15px; font-weight: 800; color: var(--t3); }
.dt-seasoncard__txt { min-width: 0; display: flex; flex-direction: column;
  gap: 2px; }
.dt-seasoncard__name { font-size: 13px; font-weight: 600;
  overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.dt-seasoncard__sub { font-size: 11px; opacity: .7; }

/* TV drives the native select, which stays in the DOM for exactly that reason. */
:root[data-tv] .dt-seasonpick, body.dv-tv .dt-seasonpick { display: none; }
:root[data-tv] .dt-seasonsel[hidden],
body.dv-tv .dt-seasonsel[hidden] { display: inline-flex !important; }
/* Offscreen cards keep their layout size without painting hundreds of posters. */
@supports (content-visibility: auto) {
  .cl-cell { content-visibility: auto; contain-intrinsic-size: auto 300px; }
}

/* Full-bleed shelves extend outside the text wrap: paint containment clips them. */
.dt-episodes{content-visibility:visible;overflow:visible}

/* Scrolling controls use a tinted fill instead of sampling moving content.
   Dialogs retain their glass treatment; hero size/shape animation is unchanged. */
.dv-nav__pill, .dv-nav__side, .dv-tabbar,
.dv-btn--icon, .dv-btn--glass, body .dv-card__add, body .dv-conti__x,
.dv-hero__arrow, .dv-hero__mute, .dt-heroarrow, .dt-more {
  backdrop-filter: none;
  -webkit-backdrop-filter: none;
}
.dv-nav__pill, .dv-tabbar, .dv-btn--icon, .dv-btn--glass,
.dv-hero__arrow, .dv-hero__mute, .dt-heroarrow {
  background: var(--glassThick);
}
@media (max-width: 760px) {
  .dv-nav__side { background: var(--glassThick); }
}
.dt-episodes .dv-row__head{flex-wrap:wrap;gap:12px}
.dt-episode-controls{display:flex;align-items:center;gap:8px;flex-wrap:wrap}
.dt-episode-controls select{max-width:100%;min-height:44px}
.dt-eprail{scroll-behavior:auto;overscroll-behavior-x:contain;
  margin-inline:calc((100% - var(--episode-viewport,100vw))/2);
  padding-inline:calc((var(--episode-viewport,100vw) - 100%)/2)}
.dt-eprail[aria-busy=true]{opacity:.65}
.dt-ep{width:min(300px,80vw);flex:none}
.dt-ep .dv-card__art{aspect-ratio:16/9}
.dt-ep__plate{position:absolute;inset:0;display:grid;place-items:center;background:var(--card);font-size:34px}
.dt-ep__plot{display:-webkit-box;-webkit-line-clamp:2;-webkit-box-orient:vertical;overflow:hidden}
.dt-ep__play{position:absolute;inset:0;border:0;background:transparent;color:white;display:grid;place-items:center;cursor:pointer}
.dt-ep__play svg{padding:14px;width:48px;height:48px;box-sizing:border-box;background:rgba(0,0,0,.55);border-radius:50%}
body .dv-hero--titlecar .dt-body,
body .dv-hero--titlecar .dt-starring,
body .dv-hero--titlecar .dt-aside{opacity:1;transform:none;transition:none}
/* Hero polish: static colour surfaces and small compositor-only controls. */
body:has(.dv-ambient){isolation:isolate}
.dv-ambient{background:var(--dora-ambient,transparent);transition:none}
.dv-hero--home .dv-hero__art{object-position:50% 15%}
.dv-hero__art.dv-hero__art--poster{object-fit:contain;object-position:85% 30%;padding:70px 5% 55px;box-sizing:border-box;opacity:.65}
.dv-hero--home .dv-hero__body{align-items:flex-start;text-align:left;left:var(--page-pad);right:auto;bottom:68px;padding:0;max-width:min(590px,75vw);gap:14px}
.dv-hero--home .dv-meta{justify-content:flex-start}
.dv-hero--home .dv-body{display:-webkit-box;-webkit-line-clamp:3;-webkit-box-orient:vertical;overflow:hidden;font-size:15px;line-height:1.65;text-wrap:pretty}
.dv-hero--home .dv-hero__veil{background:linear-gradient(90deg,rgba(0,0,0,.64),rgba(0,0,0,.08) 70%),linear-gradient(0deg,var(--bg) 0%,transparent 48%)}
.dv-hero--home .dv-hero__title{font-size:clamp(32px,4.8vw,70px);letter-spacing:-.035em;text-transform:none;line-height:1.03}
.dv-hero--home .dv-hero__dots{left:auto;right:var(--page-pad);bottom:80px;align-items:center;gap:2px}
.dv-hero__dots button{position:relative;min-width:26px;height:28px;background:transparent;border-radius:0}
.dv-hero__dots button::before{content:"";position:absolute;left:9px;right:9px;top:11px;height:6px;border-radius:8px;background:#ffffff50}
.dv-hero__dots button.is-on{width:44px;background:transparent}
.dv-hero__dots button.is-on::before{left:4px;right:4px}
.dv-hero__dots button.is-on::after{content:"";position:absolute;left:4px;right:4px;top:11px;height:6px;border-radius:8px;background:white;transform-origin:left}
.is-timing .dv-hero__dots button.is-on::after{animation:dv-hero-progress var(--hero-interval,9000ms) linear both}
@keyframes dv-hero-progress{from{transform:scaleX(0)}to{transform:scaleX(1)}}
.dv-btn:active{transform:scale(.98)}
.dv-hero__dots button:focus-visible{outline:2px solid var(--acc);outline-offset:2px;border-radius:8px}
@media(max-width:760px){
 .dv-hero--home .dv-hero__body{left:22px;right:22px;max-width:none;bottom:64px;gap:11px}
 .dv-hero--home .dv-hero__dots{left:18px;right:auto;bottom:20px}
 .dv-hero--home .dv-body{-webkit-line-clamp:2;font-size:13px}
 .dv-hero--home .dv-hero__art{object-position:60% top}
 .dv-hero--home .dv-hero__veil{background:linear-gradient(0deg,var(--bg),rgba(0,0,0,.6) 38%,transparent 80%)}
 .dv-hero--titlecar .dt-body{gap:9px}
 .dv-hero--titlecar .dt-gline{font-size:13px;gap:7px}
 .dv-hero--titlecar .dt-blurb{-webkit-line-clamp:2;font-size:13px;line-height:1.55}
 .dv-hero--titlecar .dt-crew{display:none}
 .dv-hero--titlecar .dt-badges{gap:7px;font-size:12px}
 .dv-hero--titlecar .dt-title{max-height:110px}
 .dv-hero--titlecar .dt-title img{max-height:100px;max-width:240px;object-fit:contain;object-position:left}
}
@media(prefers-reduced-motion:reduce){.is-timing .dv-hero__dots button.is-on::after{animation:none}.dv-btn:active{transform:none}}
.dt-mobile-scores{display:none}
.pv-page{padding-top:112px;padding-bottom:80px}
.pv-head{display:flex;align-items:center;gap:22px;margin-bottom:28px}
.pv-head h1{margin:5px 0;letter-spacing:-.04em}
.pv-head p{margin:0}
.pv-logo{width:82px;height:82px;object-fit:contain;border-radius:22px;background:#181820;padding:12px;box-sizing:border-box}
.pv-services{display:flex;gap:10px;overflow-x:auto;padding:4px 2px 18px;scrollbar-width:thin}
.pv-services a{display:flex;flex:none;align-items:center;gap:9px;padding:10px 14px;background:var(--glassThin);border:1px solid var(--hair);border-radius:16px;color:var(--t2);font-size:13px;transition:transform .16s ease}
.pv-services a:hover{transform:translateY(-2px);text-decoration:none}
.pv-services a.is-on{border-color:var(--acc);color:var(--t1)}
.pv-services img{width:28px;height:28px;object-fit:contain;background:#181820;border-radius:7px;padding:3px;box-sizing:border-box}
.pv-controls{display:flex;flex-wrap:wrap;align-items:flex-end;gap:12px;margin:24px 0 16px}
.pv-controls label{display:flex;flex-direction:column;gap:6px;font-size:12px;color:var(--t2)}
.pv-controls select{font:inherit;font-size:14px;color:var(--t1);background:var(--bg);border:1px solid var(--hair);border-radius:12px;padding:11px 14px;min-height:44px}
.pv-count{font-size:12px;margin-bottom:22px}
.pv-pagination{display:flex;gap:12px;justify-content:center;margin-top:32px}
@media(max-width:760px){
 .dv-hero--titlecar .dt-rateline{display:none}
 .dt-mobile-scores{display:block;padding:16px 0 4px}
 .pv-page{padding-top:86px}
 .pv-head{gap:16px}.pv-logo{width:62px;height:62px;border-radius:16px}
 .pv-services a{padding:8px 11px;font-size:12px}
 .pv-controls{gap:10px}.pv-controls label{flex:1;min-width:125px}
 .pv-controls select{width:100%;max-width:100%}
}
.dt-episodes-heading{display:flex;align-items:baseline;gap:12px;flex-wrap:wrap}
.dt-episodes-heading>span{font-size:12px}
.dt-episode-controls{gap:8px}
.dt-episode-controls .dt-season-select{appearance:none;-webkit-appearance:none;border:1px solid var(--hair);background:var(--glassThin) url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24' fill='none' stroke='%23888' stroke-width='2'%3E%3Cpath d='m6 9 6 6 6-6'/%3E%3C/svg%3E") no-repeat right 12px center/14px;padding:10px 34px 10px 16px;border-radius:14px;font-size:13px;font-weight:600;color:var(--t1);min-height:44px}
.dt-episode-controls .dt-episode-arrow{width:44px;height:44px;padding:0;font-size:24px;border-radius:14px}
.dt-episode-ratings{min-height:44px;border-radius:14px;font-size:13px;gap:7px}
@media(max-width:760px){
 .dt-episodes .dv-row__head{align-items:stretch}
 .dt-episodes-heading{width:100%;justify-content:space-between}
 .dt-episode-controls{width:100%;display:flex;flex-wrap:nowrap;gap:6px}
 .dt-episode-controls .dt-season-select{flex:1;min-width:0;width:0;order:0;padding-left:12px;font-size:12px}
 .dt-episode-controls .dt-episode-ratings{order:1;padding:9px 10px;font-size:12px}
 .dt-episode-controls .dt-episode-arrow{order:2;flex:none;width:40px}
}
.dv-hero--home .dv-hero__title{margin:0}
.dv-hero--home .dt-logo{max-width:420px;max-height:180px;object-position:left center}
@media(max-width:760px){.dv-hero--home .dt-logo{max-width:260px;max-height:110px}.dv-hero--titlecar .dt-title{margin:0}}
.dt-primary-actions #dtDl{width:48px;height:48px;flex:none;padding:0}
.dt-primary-actions #dtDl svg{width:19px;height:19px}
.hm-svc img[src$="hbo-max.svg"],.hm-svc img[src$="starz.svg"],.hm-svc img[src$="apple-tv.svg"],
.pv-page img[src$="hbo-max.svg"],.pv-page img[src$="starz.svg"],.pv-page img[src$="apple-tv.svg"]{filter:brightness(0) invert(1);background:transparent}

/* 2026-09: readable typography and compact catalogue controls. */
:root{--fz-body:15px;--fz-label:12px}
.dv-title1,.dv-h1{font-size:clamp(28px,3vw,44px);line-height:1.15;letter-spacing:-.035em}
.dv-row__title{font-size:clamp(20px,1.7vw,26px);font-weight:700;letter-spacing:-.025em}
.dv-btn,.dv-chip{font-family:var(--ff);font-size:14px;font-weight:600;letter-spacing:0}
.dv-body{font-size:15px;line-height:1.65}
.dv-person__name{font-size:14px;line-height:1.4}
.hm-svc{width:112px!important;height:112px!important;border-radius:50%!important;padding:18px!important}
.hm-svc img{width:100%!important;height:100%!important;padding:0!important;object-fit:contain!important}
.dt-slogo{background:rgba(255,255,255,.92);border-radius:8px;padding:7px 10px}
a.dt-slogo:hover{background:#fff}
.dt-slogo__img{filter:none;opacity:1}
.dt-studiohead__logo,.dt-studio img{filter:none!important;background:#f4f4f4;border-radius:8px;padding:8px;box-sizing:border-box}
.pr-follow{margin:14px 0 0}.pr-follow-status{display:block;font-size:13px;margin-top:6px;color:var(--t2)}
.dt-season-menu{position:relative}.dt-season-menu summary{list-style:none;cursor:pointer;gap:12px}
.dt-season-menu summary::-webkit-details-marker{display:none}
.dt-season-menu[open]{z-index:30}
.dt-season-options{position:absolute;right:0;top:calc(100% + 8px);width:240px;max-height:340px;overflow-y:auto;background:var(--glassThick);backdrop-filter:blur(18px);-webkit-backdrop-filter:blur(18px);border:1px solid var(--edge);border-radius:16px;padding:6px;box-shadow:0 12px 32px #0005}
.dt-season-options button{display:flex;align-items:center;gap:12px;width:100%;border:0;border-radius:10px;background:transparent;color:var(--t1);padding:8px;text-align:left;font:600 14px var(--ff);cursor:pointer}
.dt-season-options button:hover,.dt-season-options button:focus-visible{background:var(--card)}
.dt-season-options img{width:36px;height:54px;object-fit:cover;border-radius:5px}
.dt-season-options small{display:block;color:var(--t2);font-size:12px;font-weight:400;margin-top:4px}
.dt-episode-carousel{position:relative}
.dt-episode-carousel .dt-episode-arrow{position:absolute;top:30%;z-index:5;width:42px;height:56px;padding:0;border-radius:12px;background:rgba(20,20,26,.85);color:#fff}
.dt-episode-carousel [data-shelf-prev]{left:4px}.dt-episode-carousel [data-shelf-next]{right:4px}
@media(hover:hover){.dt-episode-carousel .dt-episode-arrow{opacity:0;pointer-events:none}.dt-episode-carousel:hover .dt-episode-arrow,.dt-episode-carousel:focus-within .dt-episode-arrow{opacity:1;pointer-events:auto}}
.dt-season-select[hidden]{display:none!important}
@media(max-width:640px){.hm-svc{width:82px!important;height:82px!important;padding:13px!important}.dv-body{font-size:14px}.dv-row__title{font-size:20px}.dt-episode-controls{justify-content:flex-end}.dt-episode-carousel .dt-episode-arrow{width:34px;height:44px}.dv-hero__actions .dv-btn--icon{width:44px;height:44px}}

/* Shared geometry: one control rhythm across catalogue, forms and navigation. */
.dv-btn:not(.dv-btn--quiet),.dv-chip{min-height:var(--control-h);box-sizing:border-box}
.dv-btn--lg:not(.dv-btn--quiet){min-height:var(--control-lg)}
.dv-btn--icon{flex-shrink:0;aspect-ratio:1;border-radius:50%}
.dv-btn>svg,.dv-chip>svg{flex-shrink:0;display:block}
.dv-row__head>.dv-btn,.dv-row__head>button{flex-shrink:0}
.dv-row__head>.dv-row__titles{flex:1;min-width:0}
.dv-row__head .dv-btn--quiet{min-height:var(--control-compact)}
.dv-card--poster{width:var(--poster-width,168px)}
.dv-grid>.dv-card--poster{width:100%;align-self:start}
.dv-grid{row-gap:var(--s5)}
.dv-tabs{display:flex;align-items:center;flex-wrap:wrap;gap:var(--control-gap);margin-block:var(--s5)}
.pv-pagination{align-items:center;flex-wrap:wrap;gap:var(--s3);margin-top:var(--s6)}
.pv-pagination>span{display:inline-flex;align-items:center;min-height:var(--control-h);font-size:14px;color:var(--t2)}
.dv-nav__side>.dv-ava{width:var(--control-h);height:var(--control-h)}
#dvMain :is(input[type=text],input[type=search],input[type=url],input[type=tel],input[type=email],input[type=password],input[type=number],input[type=date],input:not([type]),select){min-height:var(--control-h);min-width:0;max-width:100%;box-sizing:border-box}
#dvMain select.dv-chip{padding-block:0;line-height:1.25}
.dv-hero__actions{--hero-control:var(--control-lg);align-items:center;gap:var(--s3)}
.dv-hero__actions .dv-btn{min-height:var(--hero-control);height:var(--hero-control);padding-block:0}
.dv-hero__actions .dv-btn--icon{width:var(--hero-control);height:var(--hero-control)}
.dt-episode-controls .dt-season-menu summary,.dt-episode-controls .dt-episode-ratings{height:var(--control-h);min-height:var(--control-h);padding-block:0;border-radius:var(--r-pill);line-height:1}
.dt-season-options{border-radius:var(--r-sheet-sm);padding:var(--s2)}
.dt-season-options button{border-radius:var(--r-card-sm)}
@media(max-width:760px){
 .dv-hero__actions{--hero-control:var(--control-h)}
 .dv-grid{row-gap:var(--s4)}
 .dv-tabs{margin-block:var(--s4) var(--s5)}
 #logSearchForm{display:grid!important;grid-template-columns:repeat(3,minmax(0,1fr))}
 #logSearchBox{grid-column:1/-1}
 #logSearchForm>select{width:100%;padding-inline:var(--s2);font-size:13px}
}

.dv-nav__burger{width:var(--control-h);height:var(--control-h)}

/* Search wrappers share the field height instead of adding padding around it. */
#dvMain form>span.dv-glass-thin:has(>input){height:var(--control-h);padding-block:0!important;box-sizing:border-box}
#dvMain form>span.dv-glass-thin>input{height:100%;min-height:0}
