/* NITRATE — the foyer.  v2, 2026-08-10.
 *
 * Everything still comes out of tokens.css: safety orange stays the only
 * saturated colour in the interface, rules stay hairlines, figures stay mono
 * and tabular. What changed is scale and material.
 *
 * THE SIGNATURE is the frame and the case. The marquee is a film gate — 35 mm
 * perforations punched through to the page ground along both edges, and slides
 * ADVANCE through it rather than fading. The poster hangs in a lit case with
 * glass and a lamp along its top edge, which is the object a cinema foyer is
 * actually built around. The carousel is steered by edge numbering, not dots.
 *
 * THE HOUSE LIGHTS: --house is set from the current title's own extracted
 * poster tint and washes the top of the page. The collection colours its own
 * foyer. Kept under 9% so it can never fight the accent.
 *
 * Every class is prefixed hm- so nothing here can collide with landing.css.
 */

:root {
  /* The board is mounted by the foyer AND by the Movies / Series / Anime front
     pages, so its two knobs live at the root rather than on the foyer's own
     wrapper — otherwise the landing pages inherit nothing and lose their
     padding entirely. */
  --dwell: 9s;                        /* must match DWELL in home.js */
  --pad-x: clamp(18px, 4vw, 52px);
}
.hm { display: block; position: relative; }
/* the wash. Behind everything, never over it, and it does not take clicks. */
.hm::before {
  content: '';
  position: absolute; inset: 0 0 auto 0;
  height: 60vh;
  background: linear-gradient(180deg, var(--house, transparent), transparent 72%);
  opacity: .085;
  pointer-events: none;
  z-index: 0;
  transition: background .8s ease;
}

.hm-loading {
  padding: 96px var(--pad-x);
  color: var(--muted);
  font-family: var(--font-mono);
  font-size: 12px; letter-spacing: .14em; text-transform: uppercase;
}

/* ═══════════════════════════════════════════════════════════════ marquee ══ */
.hm-marquee {
  position: relative; z-index: 1;
  border-bottom: var(--hair) solid var(--rule);
  background: var(--panel);
}

/* the gate: perforated on both edges, and nothing escapes it */
.hm-gate { position: relative; overflow: hidden; }
.hm-gate::before,
.hm-gate::after {
  content: '';
  position: absolute; left: 0; right: 0;
  height: 18px; z-index: 4;
  pointer-events: none;
  background-image: repeating-linear-gradient(
    90deg, var(--ground) 0 13px, transparent 13px 28px);
  background-size: 28px 10px;
  background-repeat: repeat-x;
  background-position: 0 center;
}
.hm-gate::before { top: 0; }
.hm-gate::after  { bottom: 0; }

.hm-stage {
  position: relative;
  display: grid;                       /* every frame in one cell, stacked */
  min-height: min(74vh, 660px);
}
.hm-slide {
  grid-area: 1 / 1;
  position: relative;
  display: flex; align-items: center; gap: clamp(22px, 3.4vw, 48px);
  padding: 46px var(--pad-x) 44px;
}
/* film advancing through the gate: the spent frame pulls out, the next rises
   into it. One motion, on one clock. */
@keyframes hm-rise { from { transform: translateY(102%); } to { transform: none; } }
@keyframes hm-pull { from { transform: none; } to { transform: translateY(-102%); } }
.hm-slide.is-in       { animation: hm-rise .46s cubic-bezier(.22,.61,.24,1) both; }
.hm-slide.is-out      { animation: hm-pull .46s cubic-bezier(.22,.61,.24,1) both; }
.hm-slide.is-in-back  { animation: hm-pull .46s cubic-bezier(.22,.61,.24,1) reverse both; }
.hm-slide.is-out-back { animation: hm-rise .46s cubic-bezier(.22,.61,.24,1) reverse both; }

.hm-plate {
  position: absolute; inset: 0; z-index: 0;
  background: radial-gradient(120% 150% at 16% 0%, var(--tint, transparent) 0%, transparent 66%);
  opacity: .40;
}
.hm-plate.has-shot {
  background-image: var(--shot);
  background-size: cover;
  background-position: center 28%;
  opacity: 1;
}
/* the scrim. Type sits on the left, so the ground is opaque there and clears
   toward the right where the picture should be readable. */
.hm-scrim {
  position: absolute; inset: 0; z-index: 1; pointer-events: none;
  background:
    linear-gradient(90deg, var(--panel) 0%,
                    color-mix(in srgb, var(--panel) 94%, transparent) 24%,
                    color-mix(in srgb, var(--panel) 78%, transparent) 40%,
                    color-mix(in srgb, var(--panel) 50%, transparent) 56%,
                    color-mix(in srgb, var(--panel) 22%, transparent) 74%,
                    transparent 100%),
    linear-gradient(0deg, var(--panel) 0%, transparent 26%);
}
.is-owned .hm-plate:not(.has-shot) + .hm-scrim { background: none; }

.hm-stack {
  position: relative; z-index: 2;
  display: flex; align-items: center; gap: clamp(22px, 3.4vw, 48px);
  width: 100%;
}

/* ── the lit case ────────────────────────────────────────────────────────── */
.hm-case {
  flex: 0 0 auto;
  position: relative;
  padding: 9px;
  background: var(--panel-2);
  border: var(--hair) solid var(--rule);
  box-shadow:
    0 0 0 1px color-mix(in srgb, var(--tint, transparent) 40%, transparent),
    0 22px 48px -20px rgba(0, 0, 0, .55),
    0 0 64px -28px color-mix(in srgb, var(--tint, transparent) 90%, transparent);
}
/* the lamp along the top edge of the case */
.hm-case::before {
  content: '';
  position: absolute; left: 9px; right: 9px; top: 3px; height: 3px;
  background: linear-gradient(90deg, transparent,
              color-mix(in srgb, var(--ink) 55%, transparent), transparent);
  opacity: .5;
}
.hm-art { position: relative; width: clamp(150px, 16vw, 246px); }
.hm-art img { width: 100%; display: block; }
.hm-case.is-blank .hm-art { aspect-ratio: 2 / 3; background: var(--ground); }
.hm-case.is-blank img { display: none; }
/* glass: one soft diagonal highlight across the print */
.hm-glass {
  position: absolute; inset: 0;
  background: linear-gradient(122deg,
              color-mix(in srgb, #fff 16%, transparent) 0%,
              transparent 34%, transparent 68%,
              color-mix(in srgb, #fff 7%, transparent) 100%);
  pointer-events: none;
}

/* ── the copy ────────────────────────────────────────────────────────────── */
.hm-body { min-width: 0; flex: 1 1 auto; max-width: 62ch; }

.hm-eye {
  font-family: var(--font-mono);
  font-size: 10px; letter-spacing: .28em; text-transform: uppercase;
  font-weight: 700; color: var(--accent);
  margin-bottom: 14px;
}
.is-want .hm-eye { color: var(--cool); }

.hm-title {
  font-family: var(--font-marquee, var(--font-display));
  font-size: clamp(38px, 6.4vw, 92px);
  line-height: .9;
  /* -.04em was tuned for system-ui; on an already-condensed face it crowds any
     title with adjacent round letters */
  letter-spacing: -.02em;
  margin: 0 0 14px;
  text-wrap: balance;
  color: var(--ink);
}
/* A long title is set smaller rather than allowed to take a third line and
   shove the rest of the page down 106px — see the block above hm-title in
   home.js for the measurement.  Nothing is truncated: the title is complete,
   it is just cut to the board. */
.hm-title.is-long { font-size: clamp(30px, 4.3vw, 62px); }

.hm-meta {
  display: flex; flex-wrap: wrap; gap: 7px; align-items: baseline;
  font-family: var(--font-mono);
  font-size: 12px; letter-spacing: .05em; color: var(--muted);
  margin-bottom: 12px;
}
.hm-sep { opacity: .45; }

.hm-rating {
  display: flex; align-items: baseline; gap: 7px;
  margin-bottom: 14px;
}
.hm-rating-n {
  font-family: var(--font-mono); font-variant-numeric: tabular-nums;
  font-size: 21px; font-weight: 700; color: var(--accent); line-height: 1;
}
.hm-rating-alt { color: var(--cool); margin-left: 10px; }
.hm-rating-l {
  font-family: var(--font-mono);
  font-size: 9.5px; letter-spacing: .16em; text-transform: uppercase;
  color: var(--muted);
}

.hm-sum {
  margin: 0 0 22px;
  font-size: 14.5px; line-height: 1.62; color: var(--ink-2);
  max-width: 54ch;
}

.hm-acts { display: flex; flex-wrap: wrap; gap: 9px; align-items: center; }
.hm-ghost {
  font: inherit; font-size: 13px;
  background: transparent; color: var(--ink-2);
  border: var(--hair) solid var(--rule);
  padding: 9px 17px;
}
.hm-ghost:hover { color: var(--ink); outline: var(--hair) solid var(--rule); }
/* Over a backdrop a hairline button all but disappears. Give it its own ground
   when the slide actually carries a picture — and only then. */
.hm-slide.has-shot .hm-ghost {
  background: color-mix(in srgb, var(--panel) 72%, transparent);
  backdrop-filter: blur(6px);
  border-color: color-mix(in srgb, var(--ink) 28%, transparent);
  color: var(--ink);
}
.hm-slide.has-shot .hm-ghost-lead { background: var(--accent); backdrop-filter: none; }
.hm-ghost-lead {
  background: var(--accent); color: var(--accent-ink);
  border-color: var(--accent); font-weight: 650;
}
.hm-ghost-lead:hover { filter: brightness(1.08); color: var(--accent-ink); }

/* ── the road out: WATCH, and the Plex door beside it ────────────────────────
   HEROROAD_20260820.  The hero's one primary action is the house player, for a
   reason that is not aesthetic: Plex transcodes on blobeye, the NAS, and the
   house player transcodes on bugeye's RX 580.

   ⛔ THE PLEX ROAD IS NOT REMOVED — John, 2026-08-11, "both need to work
   flawlessly".  It steps back to plexlink.css's own quiet variant, and takes
   the fill back the moment WATCH stands down (a stored "always Plex", or a
   show whose episode the board cannot name).  ⭐ ONE FILLED ACCENT PER VIEW is
   plexlink.css's rule and this row keeps it: whichever control is the road is
   the only saturated object here.  home.js decides which of the two that is —
   nothing in this file may put a second fill in the row. */
.hm-road { display: contents; }   /* so its contents are flex items of the row */

/* WATCH borrows the shared control's exact geometry — same height, same
   uppercase 11px, same square corners — so the row reads as a row of peers
   rather than as a new thing bolted onto the end of it. */
.hm-watch {
  display: inline-flex; align-items: center;
  height: 28px; padding: 0 12px;
  background: var(--accent); color: var(--accent-ink);
  border: var(--hair) solid var(--accent);
  border-radius: 0;                       /* film lab: nothing is rounded */
  font-family: var(--font-ui);
  font-size: 11px; font-weight: 700; line-height: 1;
  letter-spacing: .08em; text-transform: uppercase;
  white-space: nowrap; cursor: pointer;
  /* colour only, never size — the one control that must never shift the row */
  transition: filter .12s linear;
}
.hm-watch:hover { filter: brightness(1.08); }
.hm-watch:active { transform: translateY(1px); }
/* accent on accent swallows the global ring, so hold it off with a gap of the
   board's own ground */
.hm-watch:focus-visible {
  outline: 2px solid var(--accent); outline-offset: 3px;
  box-shadow: 0 0 0 2px var(--panel);
}

/* the figures that qualify the press: which episode, how far into it.  Mono and
   tabular because they are figures, muted because the button is the act. */
.hm-road-at {
  align-self: center;
  font-family: var(--font-mono); font-variant-numeric: tabular-nums;
  font-size: 10.5px; letter-spacing: .05em; color: var(--muted);
  white-space: nowrap;
}

/* Over a photograph a hairline control all but disappears — the same problem
   .hm-ghost has above and the same answer, so the two secondaries stay a
   matched pair rather than one solid button and one ghost.
   ⚠️ FIVE CLASSES, AND THE HOVER STATE HAS TO BE RESTATED: this outranks
   plexlink.css's own `.plexlink.is-quiet:hover`, so without the second rule the
   quiet Plex button would stop committing to accent under the pointer. */
.hm-slide.has-shot .hm-acts .plexlink.is-quiet {
  background: color-mix(in srgb, var(--panel) 72%, transparent);
  backdrop-filter: blur(6px);
  border-color: color-mix(in srgb, var(--ink) 28%, transparent);
  color: var(--ink);
}
.hm-slide.has-shot .hm-acts .plexlink.is-quiet:hover,
.hm-slide.has-shot .hm-acts .plexlink.is-quiet:focus-visible {
  background: var(--accent); color: var(--accent-ink);
  border-color: var(--accent); backdrop-filter: none;
}

@media (prefers-reduced-motion: reduce) {
  .hm-watch { transition: none; }
  .hm-watch:active { transform: none; }
}

/* ── the counter: edge numbering ─────────────────────────────────────────── */
.hm-ctl {
  position: relative; z-index: 5;
  display: flex; align-items: stretch;
  border-top: var(--hair) solid var(--rule);
  background: var(--panel-2);
}
.hm-step {
  flex: 0 0 auto; border: 0; background: transparent;
  color: var(--muted); font-size: 19px; line-height: 1; padding: 0 15px;
}
.hm-step:hover { color: var(--accent); }

.hm-frames { flex: 1 1 auto; display: flex; overflow: hidden; }
.hm-frame {
  flex: 1 1 0; min-width: 0;
  border: 0; border-left: var(--hair) solid var(--rule);
  background: transparent;
  padding: 9px 10px 8px;
  text-align: left;
}
.hm-frame:last-child { border-right: var(--hair) solid var(--rule); }
.hm-frame-top { display: flex; align-items: baseline; gap: 8px; min-width: 0; }
.hm-frame-n {
  font-family: var(--font-mono); font-variant-numeric: tabular-nums;
  font-size: 11px; letter-spacing: .1em; color: var(--muted); flex: 0 0 auto;
}
.hm-frame-t {
  font-size: 11px; color: var(--muted);
  white-space: nowrap; overflow: hidden; text-overflow: ellipsis;
  opacity: 0; transition: opacity .18s ease;
}
.hm-frame:hover .hm-frame-n { color: var(--ink-2); }
.hm-frame:hover .hm-frame-t { opacity: .8; }
.hm-frame.is-on .hm-frame-n { color: var(--accent); font-weight: 700; }
.hm-frame.is-on .hm-frame-t { opacity: 1; color: var(--ink); }

.hm-frame-bar { display: block; height: 2px; width: 0; margin-top: 7px; background: var(--accent); }
.hm-frame.is-on .hm-frame-bar { animation: hm-drain var(--dwell) linear forwards; }
.hm-marquee:not(.is-running) .hm-frame.is-on .hm-frame-bar { animation-play-state: paused; }
@keyframes hm-drain { from { width: 0; } to { width: 100%; } }

/* ═════════════════════════════════════════════════════════════════ rails ══ */
.hm-block {
  position: relative; z-index: 1;
  padding: 30px 0 6px;
  border-bottom: var(--hair) solid var(--rule);
  /* SAFARICSS_20260905 — a block off-screen skips layout and paint. Measured in
     WebKit at 1440: the foyer's 20-step scroll 33 s → 20 s (its frame rate stays
     low for another reason — see the Safari findings: landing.js and wanted.js
     work running on the foyer, which is script, not paint). */
  content-visibility: auto; contain-intrinsic-size: auto 1px 540px;
}
.hm-block:last-child { border-bottom: 0; padding-bottom: 34px; }
.hm-block-resume { background: var(--panel); }

.hm-head {
  display: flex; align-items: center; gap: 14px;
  padding: 0 var(--pad-x) 16px;
}
.hm-head-l { display: flex; align-items: baseline; gap: 12px; flex-wrap: wrap; min-width: 0; }
.hm-h {
  font-family: var(--font-marquee, var(--font-display));
  font-size: clamp(17px, 2vw, 23px); letter-spacing: -.02em;
}
.hm-note {
  font-family: var(--font-mono);
  font-size: 11px; color: var(--muted); letter-spacing: .04em;
}
/* the rule carries the eye to the way out of the section */
.hm-hr { flex: 1 1 auto; height: var(--hair); background: var(--rule); min-width: 20px; }
.hm-seeall {
  flex: 0 0 auto;
  font-family: var(--font-mono);
  font-size: 10.5px; letter-spacing: .12em; text-transform: uppercase;
  color: var(--ink-2); background: transparent;
  border: var(--hair) solid var(--rule);
  padding: 5px 11px;
}
.hm-seeall::after { content: ' →'; }
.hm-seeall:hover { color: var(--accent); border-color: var(--accent); }

.hm-empty {
  padding: 0 var(--pad-x) 20px; margin: 0;
  font-size: 13px; color: var(--muted); font-style: italic;
}

.hm-railbox { position: relative; }
.hm-rail {
  display: flex; gap: 16px;
  overflow-x: auto;
  padding: 4px var(--pad-x) 24px;
  scroll-snap-type: x proximity;
  scroll-padding-left: var(--pad-x);
  overscroll-behavior-x: contain;
}
.hm-rail > * { scroll-snap-align: start; }
/* looking at one poster should feel like looking at one poster */
.hm-rail.is-focused > * { opacity: .42; transition: opacity .16s ease; }
.hm-rail.is-focused > *:hover { opacity: 1; }

/* ── a card ──────────────────────────────────────────────────────────────── */
.hm-card { position: relative; flex: 0 0 158px; }
.hm-card-open {
  display: flex; flex-direction: column; gap: 6px;
  width: 100%; border: 0; background: transparent; padding: 0; text-align: left;
}
.hm-card-art {
  position: relative;
  width: 158px; aspect-ratio: 2 / 3;
  border: var(--hair) solid var(--rule);
  background: var(--panel-2);
  overflow: hidden;
}
.hm-card-art img { width: 100%; height: 100%; object-fit: cover; display: block; }
.hm-card-open:hover .hm-card-art { outline: var(--hair) solid var(--accent); }
.hm-chip {
  position: absolute; left: 0; bottom: 0;
  font-family: var(--font-mono); font-variant-numeric: tabular-nums;
  font-size: 10.5px; font-weight: 700; letter-spacing: .04em;
  padding: 2px 6px;
  background: var(--panel); color: var(--accent);
  border-top: var(--hair) solid var(--rule);
  border-right: var(--hair) solid var(--rule);
}
.hm-card-t {
  font-size: 13px; font-weight: 620; color: var(--ink); line-height: 1.28;
  display: -webkit-box; -webkit-line-clamp: 2; -webkit-box-orient: vertical; overflow: hidden;
}
.hm-card-s {
  font-family: var(--font-mono);
  font-size: 10.5px; color: var(--muted); letter-spacing: .05em;
}
.hm-card-when {
  order: -1;
  font-family: var(--font-mono);
  font-size: 10px; letter-spacing: .12em; text-transform: uppercase; color: var(--cool);
}
/* prints not yet struck: art held back until you look at it */
.hm-card-want .hm-card-art { opacity: .8; }
.hm-card-want .hm-card-open:hover .hm-card-art { opacity: 1; }

/* ── carry on: the one rail with a state to report ───────────────────────── */
.hm-res { position: relative; flex: 0 0 178px; }
.hm-res-open {
  display: flex; flex-direction: column; gap: 6px;
  width: 100%; border: 0; background: transparent; padding: 0; text-align: left;
}
.hm-res-art {
  position: relative;
  width: 178px; aspect-ratio: 2 / 3;
  border: var(--hair) solid var(--rule);
  background: var(--panel-2);
  overflow: hidden;
}
.hm-res-art img { width: 100%; height: 100%; object-fit: cover; display: block; }
.hm-res-open:hover .hm-res-art { outline: var(--hair) solid var(--accent); }
.hm-bar {
  position: absolute; left: 0; right: 0; bottom: 0; height: 5px;
  background: color-mix(in srgb, var(--ink) 44%, transparent);
}
.hm-bar-fill { display: block; height: 100%; background: var(--accent); }
.hm-res-t {
  font-size: 13.5px; font-weight: 650; color: var(--ink); line-height: 1.28;
  display: -webkit-box; -webkit-line-clamp: 2; -webkit-box-orient: vertical; overflow: hidden;
}
.hm-res-s {
  font-family: var(--font-mono);
  font-size: 10.5px; color: var(--accent); letter-spacing: .05em;
}
.hm-res-e {
  font-size: 11.5px; color: var(--muted); line-height: 1.3;
  display: -webkit-box; -webkit-line-clamp: 1; -webkit-box-orient: vertical; overflow: hidden;
}

/* ── still threaded: the HOUSE PLAYER's own record ───────────────────────────
   Deliberately the same card grammar as "carry on" above — same width, same
   run mark, same type scale — because they answer the same question and a
   reader should not have to learn two shapes to read two rails.  What differs
   is what they are a record OF, and that is carried in words, in the block's
   note, not in a second visual language.

   It takes the panel ground so it reads as one slab against the booth's block
   below it; the hairline between the two is .hm-block's own border.          */
.hm-block-threaded { background: var(--panel); }
.hm-block-threaded .hm-nudge-back { background: linear-gradient(90deg, var(--panel) 58%, transparent); }
.hm-block-threaded .hm-nudge-fwd  { background: linear-gradient(270deg, var(--panel) 58%, transparent); }

.hm-thr { position: relative; flex: 0 0 178px; display: flex; flex-direction: column; gap: 5px; }
.hm-thr-open {
  display: flex; flex-direction: column; gap: 6px;
  width: 100%; border: 0; background: transparent; padding: 0; text-align: left;
}
.hm-thr-art {
  position: relative;
  width: 178px; aspect-ratio: 2 / 3;
  border: var(--hair) solid var(--rule);
  background: var(--panel-2);
  overflow: hidden;
}
.hm-thr-art img { width: 100%; height: 100%; object-fit: cover; display: block; }
.hm-thr-open:hover .hm-thr-art,
.hm-thr-open:focus-visible .hm-thr-art { outline: var(--hair) solid var(--accent); }
/* an episode that has not been started has no run mark to draw, so it says so
   in a word instead — the chip geometry the rails already use, in the cool ink
   the foyer keeps for "when", because this is a state and not an alarm */
.hm-thr-flag {
  position: absolute; left: 0; top: 0;
  font-family: var(--font-mono); font-variant-numeric: tabular-nums;
  font-size: 10px; font-weight: 700; letter-spacing: .1em;
  padding: 2px 6px;
  background: var(--panel); color: var(--cool);
  border-bottom: var(--hair) solid var(--rule);
  border-right: var(--hair) solid var(--rule);
}
.hm-thr-t {
  font-size: 13.5px; font-weight: 650; color: var(--ink); line-height: 1.28;
  display: -webkit-box; -webkit-line-clamp: 2; -webkit-box-orient: vertical; overflow: hidden;
}
.hm-thr-s {
  font-family: var(--font-mono); font-variant-numeric: tabular-nums;
  font-size: 10.5px; color: var(--accent); letter-spacing: .05em;
}
.hm-thr-e {
  font-size: 11.5px; color: var(--muted); line-height: 1.3;
  display: -webkit-box; -webkit-line-clamp: 1; -webkit-box-orient: vertical; overflow: hidden;
}
.hm-thr-w {
  font-family: var(--font-mono);
  font-size: 10px; letter-spacing: .06em; color: var(--muted);
}
.hm-thr-dual {
  font-family: var(--font-mono);
  font-size: 9.5px; letter-spacing: .04em; color: var(--muted);
  border-left: 2px solid var(--rule); padding-left: 6px;
}
/* ⛔ NOT hover-only.  Every other secondary control on a foyer card is parked
   on hover and hidden outright on touch, because the sheet carries the same
   pair.  This one is the ONLY way from this card to the sheet — the card
   itself now plays the film — so a finger has to be able to reach it. */
.hm-thr-more {
  align-self: flex-start;
  font-family: var(--font-mono);
  font-size: 10px; letter-spacing: .12em; text-transform: uppercase;
  color: var(--ink-2); background: transparent;
  border: 0; border-bottom: var(--hair) solid var(--rule);
  padding: 2px 0;
}
.hm-thr-more:hover { color: var(--accent); border-bottom-color: var(--accent); }
/* 20px of text is a keyboard target, not a thumb one.  This is the only route
   from a threaded card to its sheet, so on touch it gets the height the rest of
   the site's real controls get — the FULL 44, not the 40 it shipped with. */
@media (hover: none) {
  .hm-thr-more { min-height: var(--tap, 44px); display: inline-flex; align-items: center; }
}

/* ── new instalments: what a show GAINED, one card per show ──────────────────
   John, 2026-08-12: "i want rail recently added movies and series (new
   episodes) ... so that i can easily keep track of the newly downloaded
   movies / episode for series & anime."

   Deliberately the SAME card grammar as "still threaded" above and "carry on"
   below it — same 178px width, same four-line stack, same orange mono figure
   in the third line — because all three answer a question about one title on
   this shelf and a reader should not have to learn a third shape to read a
   third rail.  What differs is which question, and that is carried in words,
   in the block's own note.

   ⛔ NO BADGE ON THE ART.  The one chip this file draws over a poster is a
   RATING (.hm-chip), and a bare number in that slot would be read as one.  The
   count of what arrived is a figure, so it sits on the figure line where every
   other figure on this page sits, in mono, in the accent.                    */
.hm-arr { position: relative; flex: 0 0 178px; }
.hm-arr-open {
  display: flex; flex-direction: column; gap: 6px;
  width: 100%; border: 0; background: transparent; padding: 0; text-align: left;
}
.hm-arr-art {
  position: relative;
  width: 178px; aspect-ratio: 2 / 3;
  border: var(--hair) solid var(--rule);
  background: var(--panel-2);
  overflow: hidden;
}
.hm-arr-art img { width: 100%; height: 100%; object-fit: cover; display: block; }
.hm-arr-open:hover .hm-arr-art,
.hm-arr-open:focus-visible .hm-arr-art { outline: var(--hair) solid var(--accent); }
.hm-arr-t {
  font-size: 13.5px; font-weight: 650; color: var(--ink); line-height: 1.28;
  display: -webkit-box; -webkit-line-clamp: 2; -webkit-box-orient: vertical; overflow: hidden;
}
/* two shows on this shelf are called Rurouni Kenshin.  The year is what tells
   them apart, so it sits beside the title in the register's own grammar —
   present, quiet, never competing with the name. */
.hm-arr-y {
  font-family: var(--font-mono); font-variant-numeric: tabular-nums;
  font-size: 11px; font-weight: 400; color: var(--muted);
  margin-left: 6px; letter-spacing: .02em;
}
/* the figure: what arrived.  Tabular so a column of these lines up down the
   rail and the counts can be compared without reading them one at a time.
   Two lines is the ceiling: an enumerated span ("S01E1171, S23E1173") is the
   longest form the contract can produce and it fits in two. */
.hm-arr-s {
  font-family: var(--font-mono); font-variant-numeric: tabular-nums;
  font-size: 10.5px; color: var(--accent); letter-spacing: .05em;
  display: -webkit-box; -webkit-line-clamp: 2; -webkit-box-orient: vertical; overflow: hidden;
}
.hm-arr-e {
  font-size: 11.5px; color: var(--muted); line-height: 1.3;
  display: -webkit-box; -webkit-line-clamp: 1; -webkit-box-orient: vertical; overflow: hidden;
}
.hm-arr-w {
  font-family: var(--font-mono);
  font-size: 10px; letter-spacing: .06em; color: var(--muted);
}

/* ══════════════════════════════════════════════════════════ IN THE BOOTH ══
   John, 2026-08-12: "can you make in the booth to be inserted below the
   spectrum on that right side (adjust the size so it fit there and look neat,
   for the rest of the list just make a small rail in that section."

   IT IS A COLUMN OF THE SPECTRUM'S BLOCK, NOT A BLOCK OF ITS OWN, and it is
   built to read that way: the head is the spectrum's head — 12px label, hair
   rule, figures on the right — not the foyer's 23px display heading, because
   two headings of different sizes side by side under one rule read as two
   blocks that collided rather than one composed object.  Everything else is
   the foyer's own grammar: the poster in a hairline frame, the run mark along
   its foot, the figure in mono orange.

   THE SPLIT IS ARITHMETIC, NOT A BREAKPOINT (MOBILE_SPEC §1 — 24 already
   exist and there must not be a 25th).  The caption asks for one column of the
   house grid and the booth for 380px, so the pair sits side by side while the
   block's own gutters leave 746px and wraps to a full-width band when they do
   not: measured, that is a window of about 840px.  1500, 1200 and 1000 are two
   columns; 800 and 390 are one.  ⛔ The booth lives INSIDE .nspec-foot.

   ⭐ 2026-08-12 — WHICH COLUMN IS WIDE.  John: "caption does not look good,
   create empty space".  Moving the booth in here filled the space to the RIGHT
   of the caption but not the space UNDER it: at 1482 the caption set 161px
   tall against a 357px booth, a 197 x 681 hole, because the two boxes were the
   wrong way round.  The caption had the wide column and ran to 108 characters
   a line — half again past anything anyone reads comfortably — while a rail of
   posters was squeezed into 662px and clipped mid-poster.  So the proportion is
   inverted, not the placement: the caption keeps to ONE column of the shared
   grid (--cap-col, 52 characters at 10.5px, tokens.css §A COLUMN IS A WIDTH)
   and every pixel of slack goes to the rail, which is the thing that can spend
   it.  At that measure the caption sets 18 lines / 321px against the booth's
   357, so the hole closes from both sides at once: 197px down to 36px at 1482,
   and the caption reads at 52 characters instead of 108.  ⚠️ The 36px that are
   left are the ragged bottom of two columns of different content, not slack —
   the only way to close them is a measure narrower than the house grid, which
   is a worse page for a better number.

   ⚠️ THE GROW RATIO IS THE WHOLE MECHANISM, do not "tidy" it to 1 1.  A
   max-width would pin the caption when the pair WRAPS too, and a 330px block
   of type alone on a 920px line is the same void turned on its side (measured
   239px before this).  Grow 1 against 999 means the booth eats the slack while
   they are side by side, and the caption — alone on its own flex line once
   they wrap — still takes the full width, where `columns` below sets it in as
   many of the same measure as the line will hold. */
.hm-block-spectrum .nspec-foot {
  display: flex;
  flex-wrap: wrap;
  align-items: flex-start;
  gap: 20px 36px;
}
.hm-block-spectrum .nspec-cap {
  flex: 1 1 var(--cap-col);
  /* ⚠️ 108ch is spectrum.css's cap for a caption with nothing beside it, and it
     is RIGHT there — a single 108-character line is what it is guarding against.
     Here `columns` is already guarding that, and the cap only ever bound in the
     narrow band where the pair has wrapped: at 800px it held the caption to
     681px inside a 736px line and left 56px of white beside it, which is the
     same void lying down.  A previous reading found the cap load-bearing, and
     under the old `1 1 560px` it was — dropping it took the caption to 740px and
     60px off the booth.  With the grow ratio above, the caption's share of the
     slack is one part in a thousand, so it no longer is: measured across
     390–1920, dropping it moves nothing except those 56px. */
  max-width: none;
  /* the house grid: one column while it is a column, as many as fit once it is
     a full-width band.  ⛔ NO `break-inside: avoid` — tokens.css says why. */
  columns: var(--cap-col);
  column-gap: var(--cap-gap);
  orphans: 2;
  widows: 2;
}

.hm-booth { flex: 999 1 380px; min-width: 0; }
/* the fallback mount: no spectrum on the page, so no gutter to inherit */
.hm-block-booth > .hm-booth { padding: 0 var(--pad-x); }

.hm-bth-hd {
  display: flex; flex-wrap: wrap;
  align-items: baseline; gap: 4px 12px;
  margin-bottom: 12px;
}
.hm-bth-h {
  font-size: 12px; letter-spacing: .13em;
  color: var(--ink); white-space: nowrap;
  flex: 0 1 auto; min-width: 0;
}
.hm-bth-rule {
  flex: 1 1 20px; min-width: 12px; height: 0;
  border-top: var(--hair) solid var(--rule);
  transform: translateY(-4px);
}
/* shrinkable, so a narrow column drops the tally onto its own line rather
   than pushing the head past its box */
.hm-bth-meta {
  font-size: 10.5px; color: var(--muted);
  white-space: nowrap; flex: 0 1 auto;
}

/* ── the gate: the print you walked out of last ──────────────────────────────
   Drawn apart from the rest because it is not the same kind of object — it is
   the one you are most likely to finish tonight.  The controls sit BESIDE the
   print where the column is wide enough to hold them and drop under it where
   it is not — the same arithmetic wrap as the column itself, so there is one
   number to change and no third breakpoint. */
.hm-bth-gate {
  display: flex; flex-wrap: wrap;
  align-items: flex-start;
  gap: 12px 16px;
  padding-bottom: 14px;
  border-bottom: var(--hair) solid var(--rule);
  margin-bottom: 14px;
}
.hm-bth-open {
  display: flex; flex-direction: column; gap: 5px;
  width: 100%; border: 0; background: transparent; padding: 0; text-align: left;
}
.hm-bth-open.is-lg {
  flex: 1 1 300px; width: auto;
  flex-direction: row; align-items: flex-start; gap: 12px;
}
/* content-width, so the print takes the slack and the controls end on the
   column's own right edge — the same edge the tally above and the rail below
   are cut to */
.hm-bth-acts { flex: 0 1 auto; display: flex; flex-wrap: wrap; gap: 8px; }

.hm-bth-art {
  position: relative; display: block;
  width: 92px; aspect-ratio: 2 / 3; flex: 0 0 auto;
  border: var(--hair) solid var(--rule);
  background: var(--panel-2);
  overflow: hidden;
}
.hm-bth-art.is-lg { width: 64px; }
.hm-bth-art img { width: 100%; height: 100%; object-fit: cover; display: block; }
.hm-bth-open:hover .hm-bth-art,
.hm-bth-open:focus-visible .hm-bth-art { outline: var(--hair) solid var(--accent); }

.hm-bth-tx { min-width: 0; flex: 1 1 auto; display: flex; flex-direction: column; gap: 4px; }
.hm-bth-eye {
  font-family: var(--font-mono);
  font-size: 9.5px; letter-spacing: .16em; text-transform: uppercase;
  color: var(--muted);
}
.hm-bth-t {
  font-size: 11.5px; font-weight: 620; color: var(--ink); line-height: 1.28;
  display: -webkit-box; -webkit-line-clamp: 2; -webkit-box-orient: vertical; overflow: hidden;
}
.hm-bth-t.is-lg { font-size: 15px; font-weight: 650; }
.hm-bth-e {
  font-size: 11.5px; color: var(--muted); line-height: 1.3;
  display: -webkit-box; -webkit-line-clamp: 1; -webkit-box-orient: vertical; overflow: hidden;
}
/* HOW MUCH IS LEFT, not how far in you got — the whole reason this block is
   not a poster rail.  The unit says what the figure is measured against, so
   "47 min left / of 1 h 42 m" carries the scale a bar used to. */
.hm-bth-fig { display: flex; flex-wrap: wrap; align-items: baseline; gap: 7px; margin-top: 2px; }
.hm-bth-fig-n {
  font-family: var(--font-mono); font-variant-numeric: tabular-nums;
  font-size: 17px; font-weight: 700; color: var(--accent); line-height: 1;
}
.hm-bth-fig-u {
  font-family: var(--font-mono);
  font-size: 9.5px; letter-spacing: .14em; text-transform: uppercase;
  color: var(--muted);
}
/* the rest of the list: one line of figures, the same mono orange third line
   every other rail on this page uses.  ⚠️ 92px is a MEASUREMENT, not a round
   number: "101 min left" sets to 82px at this size and "≈101 min left" to 89,
   so the figure a film row always carries stays on one line.  An episode row
   adds its code and takes two, which is what the clamp is for. */
.hm-bth { position: relative; flex: 0 0 92px; }
.hm-bth-s {
  font-family: var(--font-mono); font-variant-numeric: tabular-nums;
  font-size: 10.5px; color: var(--accent); letter-spacing: .05em;
  display: -webkit-box; -webkit-line-clamp: 2; -webkit-box-orient: vertical; overflow: hidden;
}
/* the rail is inside a column, so it takes the column's edges and not the
   page gutter — and the nudges follow its shorter bottom padding */
.hm-booth .hm-rail { gap: 12px; padding: 2px 0 10px; scroll-padding-left: 0; }
.hm-booth .hm-nudge { bottom: 10px; width: 30px; font-size: 18px; }

/* ── rail nudges ─────────────────────────────────────────────────────────── */
.hm-nudge {
  position: absolute; top: 0; bottom: 24px; z-index: 3;
  width: 38px; padding: 0; border: 0;
  color: var(--ink); font-size: 21px; line-height: 1;
  opacity: 0; pointer-events: none;
  transition: opacity .15s ease;
}
.hm-nudge-back { left: 0;  background: linear-gradient(90deg, var(--ground) 58%, transparent); }
.hm-nudge-fwd  { right: 0; background: linear-gradient(270deg, var(--ground) 58%, transparent); }
.hm-block-resume .hm-nudge-back { background: linear-gradient(90deg, var(--panel) 58%, transparent); }
.hm-block-resume .hm-nudge-fwd  { background: linear-gradient(270deg, var(--panel) 58%, transparent); }
.hm-railbox.can-back:hover .hm-nudge-back,
.hm-railbox.can-fwd:hover  .hm-nudge-fwd { opacity: 1; pointer-events: auto; }
.hm-nudge:hover { color: var(--accent); }

/* ═══════════════════════════════════════════════ NO POINTER, NO HOVER ══════
   A phone in LANDSCAPE is 844 CSS px wide, so a width query cannot answer "is
   this a finger" — `(hover: none)` is the only honest test (MOBILE_SPEC §10.3).
   These are the foyer's three hover-driven behaviours, and on touch each one
   is a defect rather than a missing nicety.

   1. THE RAIL NUDGES can never appear: they are revealed by .hm-railbox:hover,
      which will not happen.  Two permanently invisible <button>s per rail,
      left in the tree for a pointer that does not exist.
   2. THE SHARED PAIR on a poster card follows the grammar the shell already
      settled for every list surface — index.html's own (hover: none) block
      hides the Wall's, the Screener's and the landing cards' pairs, because
      the card opens the sheet and the SHEET carries the pair at 44px.  Here
      it was only ever revealed by :hover / :focus-within, so on a phone it
      was a control you could open by accident and never on purpose.
      ⚠️ The shell's list names .hm-res but not .hm-card: this is that line.
   3. THE NEIGHBOUR DIM is driven by `mouseover`, which iOS Safari FIRES ON A
      TAP — and the `mouseleave` that undoes it never comes.  One tap on a
      card left every other card in the rail stuck at 42% opacity for good.
      Looking at one poster is a mouse idea; a finger is already pointing. */
@media (hover: none) {
  .hm-nudge { display: none; }
  .hm-card > .nact.hm-act { display: none; }
  .hm-rail.is-focused > * { opacity: 1; }
  /* 4. THE BOOTH'S GATE is the exception to (2): it is the one pair on this
        page that is NOT a duplicate of the sheet's, because carrying on is
        what the block is for.  So it stays visible and takes the full 44. */
  .hm-bth-acts .plexlink,
  .hm-bth-acts .nstream-btn { height: var(--tap, 44px); padding: 0 14px; }
}

/* ══════════════════════════════════════════════════════════════════ narrow */
@media (max-width: 1000px) {
  .hm-stage { min-height: 0; }
  .hm-art { width: clamp(126px, 20vw, 172px); }
}
@media (max-width: 760px) {
  .hm-stack { flex-direction: column; align-items: flex-start; }
  .hm-slide { padding: 30px var(--pad-x) 26px; }
  .hm-scrim {
    background: linear-gradient(0deg, var(--panel) 8%,
                color-mix(in srgb, var(--panel) 76%, transparent) 58%, transparent);
  }
  /* the case is the object a foyer is built around and 126px was a thumbnail;
     20vw only starts paying at a tablet, so drive it from the phone instead */
  .hm-art { width: clamp(138px, 39vw, 176px); }
  .hm-title { font-size: clamp(30px, 9vw, 46px); }
  .hm-sum { display: none; }               /* the poster and the title carry it */
  .hm-frame-t { display: none; }
  .hm-eye { margin-bottom: 11px; font-size: 11px; letter-spacing: .22em; }
  .hm-meta { font-size: 12.5px; }
  .hm-rating-l { font-size: 11px; letter-spacing: .12em; }

  /* ── the three controls ────────────────────────────────────────────────
     Watch, Play and Details are 28, 28 and 39px tall against a 44px thumb.
     They are the only things on the marquee anyone actually presses, so they
     are sized as one row of real targets.  The two shared controls set their
     own height in plexlink.css / stream.css, which this agent does not own —
     a two-class selector scoped to the marquee outranks them without editing
     either file, and nothing outside .hm-acts is touched. */
  /* 🔴 AND THE BLOCK IS A CONSTANT HEIGHT, WHATEVER IT HOLDS.  Three 44px
     targets do not fit one 354px line, so the owned slides wrap to two while a
     booked slide keeps its single "Read the dossier" — measured 2026-08-20 at
     390px: the gate went 563 / 473 / 563 / 473 as the reel advanced, a 115px
     shove down the whole page every nine seconds, worse than the 106px one the
     note above hm-title in home.js was written about.  A floor of two lines
     costs a booked slide some room under its button and takes the movement to
     zero — and unlike shortening a label it holds for "Play next here", for a
     resume figure, and at 360px.  ⛔ Do not replace it with tighter labels. */
  .hm-acts {
    gap: 10px;
    min-height: calc(var(--tap, 44px) * 2 + 10px);
    align-items: flex-start;
    align-content: flex-start;
  }
  .hm-acts .plexlink,
  .hm-acts .nstream-btn,
  .hm-acts .hm-watch {
    height: 44px;
    padding: 0 15px;
    font-size: 12.5px;
  }
  .hm-acts .plexlink-label,
  .hm-acts .nstream-label { font-size: 12.5px; }
  /* the qualifying figures are read, not pressed, so they keep their size and
     wrap with the row rather than being sized for a thumb */
  .hm-road-at { font-size: 11px; }
  .hm-ghost {
    min-height: 44px;
    padding: 0 18px;
    font-size: 13.5px;
    display: inline-flex; align-items: center;
  }

  /* ── the counter ───────────────────────────────────────────────────────
     Nine controls in 390px: seven frames at 45×42 and two step arrows at
     36×42.  Every one of them failed the target floor, and the two that
     failed worst were the two that duplicate work the frames already do —
     tapping "05" IS "go forward two".  So on a phone the arrows go and the
     seven frames take the whole rail: 54×50 at 390, 49×50 at 360, each one
     fenced by its own hairline.  Nothing is lost — every destination the
     arrows could reach is one tap away on the strip itself, and a flick
     across the board does the same job with the thumb already there.

     ⭐ 2026-08-11, MOBILE_SPEC §1: the arrows used to come back at 561px.
     That was the 560 breakpoint, one of the 24, and it is now FOLDED into
     this tier — so the counter is one shape for every phone and every small
     tablet instead of two shapes with a seam nobody could predict.  At 760px
     the seven frames are 108px each, which is a better target than 96px of
     frame plus two 44px arrows doing what the frames already do. */
  .hm-ctl { min-height: 50px; }
  .hm-step { display: none; }
  .hm-frame:first-child { border-left: 0; }
  .hm-frame { padding: 12px 6px 10px; }
  .hm-frame-n { font-size: 11.5px; }
  .hm-frame-bar { margin-top: 8px; }

  /* ── the rails ─────────────────────────────────────────────────────────
     FOLDED FROM 520 (MOBILE_SPEC §1).  A card was 158/178px down to 520 and
     122/142px below it, which is a step in the middle of the phone range and
     nothing at the top of it.  One fluid rule covers the whole tier instead:
     the cap IS the desktop width, so 760px lands exactly on desktop and the
     card shrinks with the screen from there down — 140px at 390, 155px at
     430, no seam anywhere.  ⛔ Not a new breakpoint: min() is arithmetic. */
  .hm-card { flex-basis: min(158px, 36vw); }
  .hm-card-art { width: min(158px, 36vw); }
  /* ⚠️ .hm-arr joins the LIST rather than getting a rule of its own: the three
     rails are meant to be one object at every width, and two places to change
     178px is how they drift apart. */
  .hm-res, .hm-thr, .hm-arr { flex-basis: min(178px, 40vw); }
  .hm-res-art, .hm-thr-art, .hm-arr-art { width: min(178px, 40vw); }

  /* the tally used to be squeezed against the rule and ellipsised away; give
     it the whole second line instead (also folded from 520) */
  .hm-head { flex-wrap: wrap; gap: 4px 12px; }
  .hm-head-l { flex: 1 1 100%; gap: 4px 10px; }
  .hm-hr { display: none; }
  .hm-note { flex: 1 1 100%; font-size: 11.5px; }
  .hm-seeall { min-height: var(--tap, 44px); padding: 0 14px; display: inline-flex; align-items: center; }
  .hm-block { padding-top: 26px; }
  .hm-block-spectrum { padding-top: 22px; }

  /* the booth's head is the SPECTRUM's head, so it takes the same phone
     treatment spectrum.css gives .nspec-hd — otherwise the two labels sitting
     one above the other in the same block are set in two different faces */
  .hm-bth-h {
    font-family: var(--font-marquee, var(--font-display));
    font-size: 17px;
    letter-spacing: -.01em;
  }
  .hm-bth-rule { transform: translateY(-5px); }
  .hm-bth-meta { font-size: 11px; }
}

/* the shared pair on a poster card — same grammar as the Wall: hidden until
   hover, parked over the top-right corner, never inside the <button> */
.hm-card > .nact.hm-act {
  position: absolute;
  right: 5px; top: 5px;
  z-index: 3;
  gap: 4px;
  visibility: hidden;
  background: var(--panel);
}
.hm-card:hover > .nact.hm-act,
.hm-card:focus-within > .nact.hm-act { visibility: visible; }

/* No backdrop cached yet? Then there is no picture to sit beside, and a hero
   whose right two thirds are empty reads as broken rather than spacious. */
.hm-slide:not(.has-shot) .hm-stack { max-width: 1140px; margin-inline: auto; }

/* ═════════════════════════════════════════ the projector strikes up ══════
   Once per session. The gate is dark, the film threads through it, the lamp
   catches, and the first frame locks. ~1.1s, and never for anyone who has
   asked for less motion. */
.hm-flare { position: absolute; inset: 0; z-index: 6; pointer-events: none; opacity: 0; }

.hm-marquee.is-threading .hm-stage { animation: hm-lock 1.15s cubic-bezier(.16,.8,.3,1) both; }
.hm-marquee.is-threading .hm-gate { background: #000; }
.hm-marquee.is-threading .hm-gate::before,
.hm-marquee.is-threading .hm-gate::after {
  animation: hm-thread .19s linear 5;
}
.hm-marquee.is-threading .hm-flare {
  animation: hm-strike 1.15s cubic-bezier(.4,0,.7,1) both;
  background:
    radial-gradient(120% 90% at 50% 50%,
      color-mix(in srgb, #fff 70%, transparent) 0%,
      color-mix(in srgb, var(--accent) 45%, transparent) 38%,
      transparent 72%);
}

/* the film running through: one perforation period per 19ms reads as sprocket
   speed rather than a sliding texture */
@keyframes hm-thread { to { background-position-x: 28px; } }

@keyframes hm-lock {
  0%   { opacity: 0; transform: translateY(14px) scaleY(.965); filter: brightness(.2); }
  46%  { opacity: .35; transform: translateY(6px)  scaleY(.99); filter: brightness(.5); }
  72%  { opacity: 1;   transform: none;            filter: brightness(1.5); }
  100% { opacity: 1;   transform: none;            filter: none; }
}
@keyframes hm-strike {
  0%   { opacity: 0; }
  55%  { opacity: 0; }
  70%  { opacity: .85; }
  100% { opacity: 0; }
}

/* the poster wall sits UNDER the per-slide plate, so a title with its own
   backdrop still gets its own backdrop and only the rest fall back to the wall */
/* THE POSTER WALL — kept, but honestly demoted.
   The idea was that every frame would stand in front of John's own shelf. It
   lost to the backdrops: with 2,701 plates cached, a slide's own 1280px still
   covers the gate edge to edge, and measured through the scrim the wall
   contributed about 3 levels out of 255 — invisible. Masking the backdrop to
   expose it only made the left muddy without making the wall read as posters.
   So it stays as the ground for a slide that has NO backdrop, where it is the
   difference between the collection and a flat colour, and nothing else. */
.hm-wall { position: absolute; inset: 0; z-index: 0; }
.hm-stage { z-index: 1; }
.hm-marquee.is-threading .hm-wall { opacity: 0; }

.hm-block-spectrum, .hm-block-vault { padding-top: 26px; }
/* the spectrum now closes with a rail inside it, which needs a little more
   air under it than the 6px a block of headings and rails wants */
.hm-block-spectrum { padding-bottom: 12px; }

/* 🔴 A HIDDEN BLOCK STILL MATCHES :last-child.  STILL THREADED is born hidden
   and stays hidden when the house player has nothing part-run, and with the
   register and the slate gone it can be the last section in the tree — on a
   phone, and for any role whose vault renders nothing.  Without this the page
   ends on a rule with 6px under it. */
.hm-block:has(+ .hm-block[hidden]:last-child) { border-bottom: 0; padding-bottom: 34px; }

/* ══════════════════════════════════════════════ ROLLING RECENTLY ADDED ═══
   John, 2026-08-13: "in landing page #/index, make 'recently added' as
   scrolling rail (do not change the current UX)."

   ⛔ NOTHING ABOUT THE RAIL'S APPEARANCE CHANGES HERE — no new spacing, no
   new card, no new heading. The markup, the cards and the manual scroll are
   exactly what ownedRail() already built; the only addition is motion, and
   every way of stopping it (hover, touch, keyboard focus, scrolled out of
   sight, reduced-motion) is inherited from landing.js :: makeRolling().

   ⭐ These mirror the .lnd-rail rules in landing.css rather than widening
   them. Broadening those selectors to match any rail would have changed the
   landing page too, which is precisely what "do not change the current UX"
   forbids. The KEYFRAMES and the .lnd-roll-track / .lnd-roll-seq layout are
   shared — index.html loads landing.css on every route — so this is four
   selectors, not a second copy of the animation. */
.hm-rail.is-roll { scroll-snap-type: none; }
.hm-rail.is-roll.is-rolling.is-roll-left  .lnd-roll-track { animation-name: lnd-roll-l; }
.hm-rail.is-roll.is-rolling.is-roll-right .lnd-roll-track { animation-name: lnd-roll-r; }

/* 🔴 THE VIEWER WINS — play-state only, so these never fight for the name. */
.hm-rail.is-held  .lnd-roll-track,
.hm-rail.is-off   .lnd-roll-track,
.hm-rail.is-focus .lnd-roll-track { animation-play-state: paused; }
/* ⚠️ Real hover pointers only: a phone leaves a phantom :hover on the last
   thing tapped, and "it moves until you touch it once" is worse than static. */
@media (hover: hover) {
  .hm-rail.is-roll:hover .lnd-roll-track { animation-play-state: paused; }
}
