/* =========================================================================
   THE SHONEN TABLE — FLAGSHIP LAYER
   Loaded AFTER styles.css and dashboard.css. Nothing in here replaces the
   Hub; it enlarges it.

   The Hub is the foundation and its DNA is untouched:
     --hub-ink #0A0A09 · --hub-cream #F4F1EA · --hub-red #C8102E
     --hub-burgundy #7E0C1E · --hub-gold #E3A93B
     Archivo Black + Work Sans + the mono dossier labels
     the drifting cloud field, the kunai, the shuriken, the paper grain,
     the slash dividers, the stamps, the taijitu in the O

   What this file adds is scale, depth and cinema:
     1. the CLOUD LINE — a real sky over the hero, clean ground below it
     2. a full-bleed hero lit in the neutral core, with one red accent
     3. the live airing board
     4. manga-panel news instead of stacked rectangles
     5. section transitions built from the TST marks

   ON RED. The Hub's red is kept and it is still the brand's colour, but it
   is spent once per viewport, on the thing that matters most there. Depth,
   structure and hierarchy are carried by the neutral ramp below. DESIGN.md
   §12 is the rule this file follows: "if two red elements are visible on
   screen together, one of them should probably not be red."

   Everything is prefixed .tstf- except a handful of deliberate Hub
   overrides, each of which says why it exists.
   ========================================================================= */

/* ------------------------------------------------------------ 0. tokens */

:root {
  /* Reads from the Hub with fallbacks, so renaming a Hub token upstream
     carries through here automatically. */
  --f-ink: var(--hub-ink, #0A0A09);
  --f-cream: var(--hub-cream, #F4F1EA);
  --f-dim: var(--hub-cream-dim, rgba(244, 241, 234, 0.68));
  /* --f-faint is NOT the Hub's --hub-cream-faint, and that is deliberate.
     Cream at 0.42 alpha measures 3.72:1 on the board and 3.67:1 on the news
     panels — under WCAG AA on every background the flagship uses, on all
     seven places it appears: the board head, the airing kind, the episode
     line, the panel meta. Quiet is a design intention; illegible is a bug,
     and an alpha that composites differently on every surface is how a
     whole tier of text fails at once without anyone noticing.

     A solid #8A8E95 measures 5.45–6.02:1 across every surface in play and
     still reads clearly as the quietest tier. The Hub's own token is left
     untouched upstream, so the Hub still renders as the Hub. */
  --f-faint: #8A8E95;
  --f-red: var(--hub-red, #C8102E);
  --f-burg: var(--hub-burgundy, #7E0C1E);
  --f-gold: var(--hub-gold, #E3A93B);
  --f-line: var(--hub-line, rgba(244, 241, 234, 0.12));
  --f-ease: var(--hub-ease, cubic-bezier(0.4, 0, 0.2, 1));
  --f-out: cubic-bezier(0.16, 1, 0.3, 1);
  --f-head: 'Archivo Black', 'Arial Black', system-ui, sans-serif;
  --f-mono: var(--hub-mono, ui-monospace, monospace);

  /* PREMIUM NEUTRAL CORE.
     DESIGN.md §12 already sets the rule: one saturated colour, and "if two
     red elements are visible on screen together, one of them should
     probably not be red." The flagship hero was breaking that six ways at
     once (tagline, stamp, CTA, live dot, two lighting pools, plus red in
     the sky shader). This ramp gives structure, depth and hierarchy
     somewhere neutral to live, so red can go back to meaning something. */
  --f-graphite: #16171A;
  --f-smoke:    #23252A;
  --f-slate:    #33363D;
  --f-stone:    #6E7178;
  --f-silver:   #A8ACB3;
  --f-bone:     #EFEAE1;   /* warm off-white, stays in the Hub cream family */

  --f-shell: 1180px;
  --f-shell-wide: 1440px;
  /* The shell's side gutter, shared by every section built after step 8.
     Stated once so a full-bleed element can cancel it exactly. */
  --f-pad: clamp(18px, 4vw, 56px);
}

/* =========================================================================
   1. CLOUD DEPTH — AND THE CLOUD LINE

   The Hub draws one flat field of clouds, position:fixed across the whole
   page. A real sky has distance: the far ones small, dim and softened, the
   near ones larger and sharper. That gives the hero depth.

   But depth was never the problem. DENSITY was. Two fixed full-page cloud
   fields (the Hub's CSS one and the flagship's WebGL one) behind every
   single section is not atmosphere — it is permanent fog, and it spends
   the strongest thing in the brand on wallpaper.

   So: THE CLOUD LINE. The sky belongs to the hero. You descend through it
   once, and below it there is clean ground — which is the only way the
   between-section cloud banks and the entry portal can land as events.
   Both fields dissolve together on `.tst-below-cloudline`, set by the
   IntersectionObserver in tst-sky.js. The in-front near field that used to
   double the density is gone entirely.
   ========================================================================= */

:root {
  --tstf-sky-fade: 900ms;   /* tst-sky.js reads this duration as a constant */
}

/* Far layer — pushed back with blur and desaturation. */
.tst-sky__cloud[data-layer="far"] {
  filter: blur(3px);
  opacity: calc(var(--c-op, 0.05) * 1.15);
}

.tst-sky__cloud[data-layer="mid"] {
  filter: blur(0.6px);
}

/* Both sky fields fade as one. Opacity is the only animated property, so
   this stays a compositor-only transition.

   `visibility` is transitioned too, with a zero duration and a delay equal
   to the fade, which is the standard trick for "hide it, but only once it
   has finished fading". Setting visibility:hidden outright would cut the
   dissolve off at frame one. Coming back the other way the delay is 0, so
   it becomes visible immediately and fades in. */
.tst-sky,
.tst-sky-gl {
  transition:
    opacity var(--tstf-sky-fade) cubic-bezier(0.4, 0, 0.6, 1),
    visibility 0s linear 0s;
}

.tst-below-cloudline .tst-sky,
.tst-below-cloudline .tst-sky-gl {
  opacity: 0;
  visibility: hidden;
  transition:
    opacity var(--tstf-sky-fade) cubic-bezier(0.4, 0, 0.6, 1),
    visibility 0s linear var(--tstf-sky-fade);
}

@media (prefers-reduced-motion: reduce) {
  :root { --tstf-sky-fade: 1ms; }
}


/* =========================================================================
   2. THE HERO — full bleed, layered, cinematic
   Hub override, deliberate: the Hub hero is a centred card about 560px
   tall. The flagship needs the whole screen, because the brief's first
   ask is "larger visual moments" and this is the largest one available.
   ========================================================================= */

.tst-hub-hero.tstf-hero {
  min-height: 100svh;
  display: grid;
  align-content: center;
  padding-block: clamp(84px, 12vh, 150px) clamp(56px, 8vh, 96px);
  overflow: clip;
  isolation: isolate;
}

/* Studio key light: a deep burgundy pool from below-left and a red rim from
   the upper right, over the Hub's ink. Stronger than the Hub's single wash
   because a full-height hero needs something to fill it. */
.tstf-hero::after {
  content: "";
  position: absolute;
  inset: 0;
  z-index: -1;
  pointer-events: none;
  /* STUDIO LIGHTING IN THE NEUTRAL CORE.
     First attempt at this was too timid: graphite pools at 88% of a very
     dark grey, over near-black, which is a difference you cannot see.
     Looking at the render, the hero read as flat black — the whole point
     of a neutral ramp is that the greys are legible AS greys.

     So: a real key from below-left in slate, a cooler stone fill from
     above-right, ONE small red rim (the single accent this viewport is
     allowed), and a vignette that pulls the corners back down. The
     vignette is what makes it read as lighting rather than as a gradient
     — without falloff there is no implied source. */
  background:
    radial-gradient(76rem 50rem at -2% 108%,
      color-mix(in oklab, var(--f-slate) 92%, transparent) 0%,
      color-mix(in oklab, var(--f-smoke) 70%, transparent) 42%,
      transparent 70%),
    radial-gradient(60rem 44rem at 102% -8%,
      color-mix(in oklab, var(--f-stone) 26%, transparent) 0%,
      color-mix(in oklab, var(--f-smoke) 58%, transparent) 46%,
      transparent 72%),
    /* Sized against the viewport as well as the root, because 14rem on a
       390px phone is 57% of the screen width — the render showed a red
       wash across the top of the mobile hero, which is the exact fault
       this pass exists to remove. On desktop min() picks the rem; on a
       phone it picks the vw and the rim stays a rim. */
    radial-gradient(min(14rem, 30vw) min(11rem, 22vw) at 100% 6%,
      color-mix(in oklab, var(--f-red) 26%, transparent), transparent 70%),
    radial-gradient(120% 96% at 50% 46%,
      transparent 38%,
      color-mix(in oklab, var(--f-ink) 62%, transparent) 88%,
      var(--f-ink) 100%);
}

.tstf-hero__inner {
  position: relative;
  z-index: 4;
  width: 100%;
  max-width: var(--f-shell-wide);
  margin-inline: auto;
  padding-inline: clamp(18px, 4vw, 56px);
  display: grid;
  gap: clamp(22px, 3.4vw, 40px);
}

@media (min-width: 1000px) {
  .tstf-hero__inner {
    grid-template-columns: minmax(0, 1.35fr) minmax(300px, 0.65fr);
    align-items: center;
    gap: clamp(32px, 4vw, 72px);
  }
}

.tstf-hero__mark {
  display: flex;
  align-items: center;
  gap: 14px;
  flex-wrap: wrap;
}

.tstf-hero__logo {
  width: clamp(54px, 7vw, 82px);
  aspect-ratio: 1;
  border-radius: 18px;
  overflow: hidden;
  box-shadow: 0 0 0 1px var(--f-line), 0 18px 46px -18px rgba(0, 0, 0, 0.9);
  flex: none;
}
.tstf-hero__logo img { width: 100%; height: 100%; object-fit: cover; }

/* The wordmark. Archivo Black, as big as the viewport will carry, wiped in
   line by line. The Hub set this at ~54px; the flagship runs to 132px. */
.tstf-wordmark {
  margin: 0;
  font-family: var(--f-head);
  font-size: clamp(42px, 9.2vw, 132px);
  line-height: 0.88;
  letter-spacing: -0.028em;
  text-transform: uppercase;
  color: var(--f-cream);
  text-wrap: balance;
}

.tstf-wordmark__row { display: block; overflow: hidden; }
.tstf-wordmark__row > span { display: block; will-change: transform; }

/* The taijitu already replaces the O in the Hub's logo treatment; keep it
   sized to the bigger type. */
.tstf-wordmark .tst-yy { width: 0.78em; height: 0.78em; vertical-align: -0.08em; }

.tstf-hero__tagline {
  margin: 0;
  font-family: var(--f-mono);
  font-size: clamp(11px, 1.15vw, 14px);
  font-weight: 700;
  letter-spacing: 0.3em;
  text-transform: uppercase;
  /* Was red. The primary CTA below it is the accent moment; two reds in one
     viewport is what DESIGN.md §12 rules out. */
  color: var(--f-silver);
}

.tstf-hero__blurb {
  margin: 0;
  max-width: 44ch;
  font-size: clamp(15px, 1.35vw, 19px);
  line-height: 1.58;
  color: var(--f-dim);
}

.tstf-hero__actions { display: flex; flex-wrap: wrap; gap: 12px; }

.tstf-btn {
  display: inline-flex;
  align-items: center;
  gap: 10px;
  min-height: 48px;
  padding: 13px 24px;
  border-radius: 999px;
  background: var(--f-red);
  color: #fff;
  font-weight: 700;
  font-size: 14px;
  letter-spacing: 0.02em;
  border: 1px solid transparent;
  transition: transform 0.3s var(--f-out), background-color 0.3s var(--f-ease),
              box-shadow 0.3s var(--f-ease);
}
.tstf-btn:hover { transform: translateY(-2px); box-shadow: 0 14px 34px -14px var(--f-red); }
.tstf-btn svg { width: 16px; height: 16px; fill: currentColor; flex: none; }

.tstf-btn--ghost {
  background: rgba(244, 241, 234, 0.04);
  border-color: var(--f-line);
  color: var(--f-cream);
}
.tstf-btn--ghost:hover { background: rgba(244, 241, 234, 0.09); box-shadow: none; }

/* =========================================================================
   3. THE AIRING BOARD — the live countdown, in TST language
   Not a scoreboard: a dossier panel with poster art, a kunai tick marker
   and the Hub's own Archivo Black clock treatment.
   ========================================================================= */

.tstf-board {
  position: relative;
  border: 1px solid var(--f-line);
  border-radius: var(--hub-radius, 18px);
  background:
    linear-gradient(180deg, rgba(244, 241, 234, 0.055), rgba(244, 241, 234, 0.02));
  backdrop-filter: blur(7px);
  overflow: hidden;
}

.tstf-board__head {
  display: flex;
  align-items: center;
  gap: 10px;
  padding: 13px 16px;
  border-bottom: 1px solid var(--f-line);
  font-family: var(--f-mono);
  font-size: 10px;
  font-weight: 700;
  letter-spacing: 0.16em;
  text-transform: uppercase;
  color: var(--f-faint);
}

.tstf-board__live {
  width: 7px; height: 7px; border-radius: 50%;
  background: var(--f-red);
  flex: none;
}

@media (prefers-reduced-motion: no-preference) {
  .tstf-board__live { animation: tstf-beat 2.2s var(--f-ease) infinite; }
}
@keyframes tstf-beat {
  0%, 100% { opacity: 1; box-shadow: 0 0 0 0 color-mix(in oklab, var(--f-red) 55%, transparent); }
  55% { opacity: 0.5; box-shadow: 0 0 0 6px transparent; }
}

.tstf-board__kind { margin-left: auto; letter-spacing: 0.1em; }

/* The headline slot: the next thing airing, with its poster behind it. */
.tstf-board__lead {
  position: relative;
  display: grid;
  grid-template-columns: auto minmax(0, 1fr);
  gap: 14px;
  padding: 16px;
  isolation: isolate;
}

.tstf-board__art {
  position: absolute;
  inset: 0;
  z-index: -1;
  opacity: 0.22;
  filter: saturate(0.85);
}
.tstf-board__art img { width: 100%; height: 100%; object-fit: cover; }
.tstf-board__lead::after {
  content: "";
  position: absolute; inset: 0; z-index: -1;
  background: linear-gradient(100deg, var(--f-ink) 34%, rgba(10, 10, 9, 0.6) 100%);
}

.tstf-board__poster {
  width: 62px;
  aspect-ratio: 2 / 3;
  border-radius: 8px;
  overflow: hidden;
  background: rgba(244, 241, 234, 0.06);
  box-shadow: 0 10px 26px -12px rgba(0, 0, 0, 0.9);
  flex: none;
}
.tstf-board__poster img { width: 100%; height: 100%; object-fit: cover; }

.tstf-board__body { min-width: 0; display: grid; gap: 4px; align-content: center; }

.tstf-board__show {
  margin: 0;
  font-family: var(--f-head);
  font-size: clamp(15px, 1.7vw, 20px);
  line-height: 1.08;
  text-transform: uppercase;
  color: var(--f-cream);
  overflow-wrap: anywhere;
}

.tstf-board__ep {
  font-family: var(--f-mono);
  font-size: 10.5px;
  letter-spacing: 0.1em;
  text-transform: uppercase;
  color: var(--f-faint);
}

/* The clock. Tabular so the digits never shuffle width as it ticks. */
.tstf-clock {
  font-family: var(--f-head);
  font-size: clamp(26px, 3.6vw, 44px);
  line-height: 1;
  letter-spacing: -0.02em;
  font-variant-numeric: tabular-nums;
  color: var(--f-cream);
  white-space: nowrap;
}
.tstf-clock--sm { font-size: clamp(14px, 1.5vw, 17px); letter-spacing: 0; }
.tstf-clock em { font-style: normal; color: var(--f-red); }

/* The queue underneath: the next few, compact. */
.tstf-board__queue { list-style: none; margin: 0; padding: 0; }

.tstf-queue-row {
  display: grid;
  grid-template-columns: 34px minmax(0, 1fr) auto;
  gap: 11px;
  align-items: center;
  padding: 10px 16px;
  border-top: 1px solid var(--hub-line-soft, rgba(244, 241, 234, 0.07));
  transition: background-color 0.25s var(--f-ease);
}
.tstf-queue-row:hover { background: rgba(244, 241, 234, 0.045); }

.tstf-queue-row__art {
  width: 34px; aspect-ratio: 2/3; border-radius: 5px; overflow: hidden;
  background: rgba(244, 241, 234, 0.06);
}
.tstf-queue-row__art img { width: 100%; height: 100%; object-fit: cover; }

.tstf-queue-row__name {
  min-width: 0;
  font-size: 13px;
  font-weight: 600;
  line-height: 1.25;
  color: var(--f-cream);
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}
.tstf-queue-row__name small {
  display: block;
  font-family: var(--f-mono);
  font-size: 9.5px;
  letter-spacing: 0.08em;
  text-transform: uppercase;
  color: var(--f-faint);
}

.tstf-queue-row__t {
  font-family: var(--f-mono);
  font-size: 12px;
  font-weight: 700;
  font-variant-numeric: tabular-nums;
  color: var(--f-silver);
  white-space: nowrap;
  transition: color 0.3s var(--f-ease);
}

/* Gold is reserved for "within the hour" — see tick() in flagship.js. One
   amber number in a column of silver is a signal; four is wallpaper. */
.tstf-queue-row__t[data-soon],
.tstf-strip__item span[data-soon] { color: var(--f-gold); }

/* The honesty line. Never hidden, never softened into meaninglessness. */
.tstf-board__note {
  margin: 0;
  padding: 10px 16px 13px;
  border-top: 1px solid var(--hub-line-soft, rgba(244, 241, 234, 0.07));
  /* THE HONESTY LINE HAS TO BE READABLE.
     This was --f-faint (cream at 0.42), which measured 3.72:1 against the
     board — under WCAG AA. Setting the one line that distinguishes a
     Japanese broadcast time from a UK streaming release below the legal
     contrast floor is the worst possible place to save a few percent of
     visual quiet. Silver at 11.5px measures comfortably over 4.5:1. */
  font-size: 11.5px;
  line-height: 1.5;
  color: var(--f-silver);
}
.tstf-board__note b { color: var(--f-bone); font-weight: 700; }

/* =========================================================================
   4. THE NOW/NEXT STRIP — a live ticker under the hero
   ========================================================================= */

.tstf-strip {
  position: relative;
  z-index: 4;
  border-block: 1px solid var(--f-line);
  background: rgba(10, 10, 9, 0.72);
  backdrop-filter: blur(9px);
  overflow: hidden;
}

.tstf-strip__rail {
  display: flex;
  align-items: stretch;
  gap: 0;
  overflow-x: auto;
  scrollbar-width: none;
  overscroll-behavior-x: contain;
}
.tstf-strip__rail::-webkit-scrollbar { display: none; }

.tstf-strip__tag {
  display: flex;
  align-items: center;
  gap: 8px;
  padding: 12px 18px;
  flex: none;
  border-right: 1px solid var(--f-line);
  background: var(--f-graphite);
  color: var(--f-bone);
  font-family: var(--f-mono);
  font-size: 10px;
  font-weight: 700;
  letter-spacing: 0.16em;
  text-transform: uppercase;
  position: sticky;
  left: 0;
  z-index: 2;
}

.tstf-strip__item {
  display: flex;
  align-items: center;
  gap: 9px;
  padding: 12px 18px;
  flex: none;
  border-right: 1px solid var(--hub-line-soft, rgba(244, 241, 234, 0.07));
  font-size: 12.5px;
  color: var(--f-dim);
  transition: background-color 0.25s var(--f-ease), color 0.25s var(--f-ease);
  white-space: nowrap;
}
.tstf-strip__item:hover { background: rgba(244, 241, 234, 0.05); color: var(--f-cream); }
.tstf-strip__item b { color: var(--f-cream); font-weight: 600; }
.tstf-strip__item span {
  font-family: var(--f-mono);
  font-weight: 700;
  font-variant-numeric: tabular-nums;
  color: var(--f-silver);
  transition: color 0.3s var(--f-ease);
}

/* =========================================================================
   5. MANGA PANELS — the news, framed like a page rather than stacked boxes
   Uneven panels, ink gutters, a slight tilt on the frame. This is the
   "manga framing" the Hub gestured at and never fully built.
   ========================================================================= */

.tstf-panels {
  display: grid;
  gap: 10px;
  grid-template-columns: minmax(0, 1fr);
}

@media (min-width: 760px) {
  .tstf-panels {
    grid-template-columns: repeat(6, minmax(0, 1fr));
    grid-auto-rows: minmax(120px, auto);
  }
  /* A real manga page: one big establishing panel, two verticals, then a
     row of beats. Deliberately not a uniform grid. */
  .tstf-panel:nth-child(1) { grid-column: span 4; grid-row: span 2; }
  .tstf-panel:nth-child(2) { grid-column: span 2; }
  .tstf-panel:nth-child(3) { grid-column: span 2; }
  .tstf-panel:nth-child(4) { grid-column: span 2; }
  .tstf-panel:nth-child(5) { grid-column: span 2; }
  .tstf-panel:nth-child(6) { grid-column: span 2; }
}

.tstf-panel {
  position: relative;
  display: flex;
  flex-direction: column;
  justify-content: flex-end;
  min-height: 190px;
  padding: 16px;
  overflow: hidden;
  isolation: isolate;
  border: 2px solid rgba(244, 241, 234, 0.9);
  border-radius: 3px;
  background: #16130F;
  transition: transform 0.45s var(--f-out), border-color 0.35s var(--f-ease);
}

.tstf-panel:nth-child(1) { min-height: clamp(260px, 34vw, 430px); }

.tstf-panel:hover { transform: translateY(-3px); border-color: var(--f-silver); }

.tstf-panel__img {
  position: absolute;
  inset: 0;
  z-index: -2;
}
.tstf-panel__img img {
  width: 100%; height: 100%; object-fit: cover;
  transition: scale 0.8s var(--f-out), filter 0.6s var(--f-ease);
  filter: saturate(0.92) contrast(1.06);
}
.tstf-panel:hover .tstf-panel__img img { scale: 1.055; }

/* Ink wash so white headline type always holds, whatever the photo. */
.tstf-panel::before {
  content: "";
  position: absolute; inset: 0; z-index: -1;
  background: linear-gradient(0deg, rgba(10, 10, 9, 0.95) 6%, rgba(10, 10, 9, 0.62) 44%, rgba(10, 10, 9, 0.12) 78%);
}

/* Halftone — the manga texture, as a dot screen over the image only. It is
   generated, not an asset, so it costs nothing to ship. */
.tstf-panel__img::after {
  content: "";
  position: absolute; inset: 0;
  background-image: radial-gradient(rgba(10, 10, 9, 0.55) 0.9px, transparent 1px);
  background-size: 4px 4px;
  opacity: 0.4;
  mix-blend-mode: multiply;
}

.tstf-panel__meta {
  display: flex; align-items: center; gap: 7px; flex-wrap: wrap;
  margin-bottom: 8px;
}

.tstf-tag {
  display: inline-flex; align-items: center;
  padding: 3px 8px;
  border-radius: 3px;
  background: var(--f-bone);
  color: var(--f-graphite);
  font-family: var(--f-mono);
  font-size: 9px;
  font-weight: 700;
  letter-spacing: 0.1em;
  text-transform: uppercase;
}
.tstf-tag--ghost { background: rgba(10, 10, 9, 0.7); border: 1px solid var(--f-line); color: var(--f-dim); }
.tstf-tag--gold { background: var(--f-gold); color: var(--f-ink); }

.tstf-panel__title {
  margin: 0;
  font-family: var(--f-head);
  font-size: clamp(14px, 1.5vw, 19px);
  line-height: 1.12;
  text-transform: uppercase;
  letter-spacing: -0.008em;
  color: var(--f-cream);
  text-wrap: balance;
}

.tstf-panel:nth-child(1) .tstf-panel__title {
  font-size: clamp(22px, 3.1vw, 42px);
  line-height: 1.02;
}

.tstf-panel__dek {
  margin: 8px 0 0;
  max-width: 54ch;
  font-size: 14px;
  line-height: 1.5;
  color: var(--f-dim);
}
.tstf-panel:not(:nth-child(1)) .tstf-panel__dek { display: none; }

.tstf-panel__foot {
  display: flex; align-items: center; gap: 10px; flex-wrap: wrap;
  margin-top: 10px;
  font-family: var(--f-mono);
  font-size: 9.5px;
  letter-spacing: 0.08em;
  text-transform: uppercase;
  color: var(--f-faint);
}

/* Whole panel is the link; the title carries the underline on hover. */
.tstf-panel__link { position: absolute; inset: 0; z-index: 3; }
.tstf-panel:hover .tstf-panel__title { text-decoration: underline; text-underline-offset: 4px; }

/* No artwork: the TST marks fill the panel instead of a grey void. */
.tstf-panel--noart .tstf-panel__img {
  display: grid; place-items: center;
  background: repeating-linear-gradient(-45deg, #15120E 0 12px, #100E0B 12px 24px);
}
.tstf-panel--noart .tstf-panel__img svg {
  width: 34%; max-width: 120px; fill: rgba(244, 241, 234, 0.07);
}

/* ------------------------------------------------- the river beneath it */
.tstf-river {
  margin-top: 12px;
  display: grid;
  gap: 0;
  grid-template-columns: minmax(0, 1fr);
  border-top: 1px solid var(--f-line);
}
@media (min-width: 700px) { .tstf-river { grid-template-columns: repeat(3, minmax(0, 1fr)); } }

.tstf-river__item {
  display: grid;
  grid-template-columns: 56px minmax(0, 1fr);
  gap: 12px;
  padding: 14px 0;
  border-bottom: 1px solid var(--hub-line-soft, rgba(244, 241, 234, 0.07));
  align-items: start;
}
@media (min-width: 700px) {
  .tstf-river__item { padding-inline: 16px; border-left: 1px solid var(--hub-line-soft, rgba(244, 241, 234, 0.07)); }
  .tstf-river__item:nth-child(3n+1) { padding-left: 0; border-left: 0; }
}

.tstf-river__art {
  width: 56px; aspect-ratio: 1; border-radius: 3px; overflow: hidden;
  background: var(--f-graphite);
  border: 1px solid var(--f-line);
  display: grid;
  place-items: center;
}
.tstf-river__art img { width: 100%; height: 100%; object-fit: cover; }

/* The fallback mark inherits `currentColor`, and a river item is an <a> —
   so with no colour set it came out in the browser's default link BLUE.
   Bright blue, off-palette, three of them in a row. Anchors need their
   colour stated, not assumed. */
.tstf-river__item { color: var(--f-cream); text-decoration: none; }
.tstf-river__art svg {
  width: 62%;
  height: 62%;
  fill: color-mix(in oklab, var(--f-stone) 46%, transparent);
  color: color-mix(in oklab, var(--f-stone) 46%, transparent);
}

/* These two are <span>s inside an <a>, so they need display:block — as
   inline elements their block margins did nothing and the source ran
   straight on from the end of the headline: "…SHONEN SERIESANIME NEWS
   NETWORK". Visible in the first render of this section. */
.tstf-river__title {
  display: block;
  margin: 0;
  font-size: 13px;
  font-weight: 700;
  line-height: 1.3;
  color: var(--f-cream);
}
.tstf-river__item:hover .tstf-river__title { color: var(--f-gold); }

.tstf-river__meta {
  display: block;
  margin: 4px 0 0;
  font-family: var(--f-mono);
  font-size: 9.5px;
  letter-spacing: 0.07em;
  text-transform: uppercase;
  color: var(--f-faint);
}

/* =========================================================================
   6. SECTION TRANSITIONS — built from the TST marks
   The Hub has .tst-slash. This adds a cloud-and-shuriken seam that the eye
   reads as a page turn rather than a border.
   ========================================================================= */

.tstf-seam {
  position: relative;
  height: clamp(40px, 6vw, 78px);
  overflow: hidden;
  pointer-events: none;
}

.tstf-seam::before {
  content: "";
  position: absolute;
  inset-inline: -4%;
  top: 50%;
  height: 1px;
  background: linear-gradient(90deg, transparent, var(--f-line) 14%, var(--f-line) 86%, transparent);
}

/* The seam mark is structure, not an accent. */
.tstf-seam__mark {
  position: absolute;
  left: 50%;
  top: 50%;
  translate: -50% -50%;
  width: clamp(22px, 3vw, 34px);
  aspect-ratio: 1;
  fill: var(--f-stone);
  opacity: 0.8;
}

@media (prefers-reduced-motion: no-preference) {
  @supports ((animation-timeline: view()) and (animation-range: entry)) {
    @keyframes tstf-spin { from { rotate: 0deg; } to { rotate: 180deg; } }
    /* The seam mark is structure, not an accent. */
.tstf-seam__mark {
      animation: tstf-spin linear both;
      animation-timeline: view();
      animation-range: entry 0% exit 100%;
    }
  }
}

/* =========================================================================
   7. FLAGSHIP SECTION HEADS
   Bigger than the Hub's, and — importantly — with scroll-margin that
   actually clears the sticky nav. The restored Hub clipped its own
   subheadings under the rail when jumped to from the nav.
   ========================================================================= */

.tstf-section { position: relative; z-index: 1; padding-block: clamp(46px, 7vw, 92px); }
.tstf-section[id] { scroll-margin-top: 92px; }

.tstf-wrap {
  width: 100%;
  max-width: var(--f-shell-wide);
  margin-inline: auto;
  padding-inline: clamp(18px, 4vw, 56px);
}

.tstf-head { margin-bottom: clamp(20px, 2.6vw, 34px); max-width: 46rem; }

.tstf-eyebrow {
  display: flex; align-items: center; gap: 9px;
  margin: 0 0 10px;
  font-family: var(--f-mono);
  font-size: 10px;
  font-weight: 700;
  letter-spacing: 0.16em;
  text-transform: uppercase;
  color: var(--f-faint);
}
.tstf-eyebrow::before {
  content: "";
  width: 20px; height: 2px;
  background: var(--f-stone);
  flex: none;
}

.tstf-title {
  margin: 0 0 10px;
  font-family: var(--f-head);
  font-size: clamp(26px, 4.4vw, 58px);
  line-height: 0.98;
  letter-spacing: -0.018em;
  text-transform: uppercase;
  color: var(--f-cream);
  text-wrap: balance;
}

.tstf-sub {
  margin: 0;
  max-width: 52ch;
  font-size: clamp(14px, 1.2vw, 16.5px);
  line-height: 1.55;
  color: var(--f-dim);
}

/* =========================================================================
   8. REVEALS
   One entrance, native where available, observer fallback otherwise.
   ========================================================================= */

@media (prefers-reduced-motion: no-preference) {
  .tstf-in { opacity: 0; transform: translate3d(0, 20px, 0); }
  .tstf-in.is-in {
    opacity: 1; transform: none;
    transition: opacity 0.7s var(--f-out), transform 0.7s var(--f-out);
  }

  @supports ((animation-timeline: view()) and (animation-range: entry)) {
    .tstf-in {
      opacity: 1; transform: none;
      animation: tstf-rise 0.8s var(--f-out) backwards;
      animation-timeline: view();
      animation-range: entry 6% entry 58%;
    }
  }

  /* The hero wipes in on load, one line at a time. */
  .tstf-wordmark__row > span {
    transform: translateY(104%);
    animation: tstf-wipe 0.95s var(--f-out) forwards;
  }
  .tstf-wordmark__row:nth-child(1) > span { animation-delay: 0.10s; }
  .tstf-wordmark__row:nth-child(2) > span { animation-delay: 0.20s; }
  .tstf-wordmark__row:nth-child(3) > span { animation-delay: 0.30s; }

  /* `backwards`, not `forwards`, and no opacity:0 on the element itself.
     tstf-rise declares only a `from`, so its implicit `to` is whatever the
     element computes to. Setting the element to opacity:0 therefore made
     the animation run 0 → 0 and the hero never appeared. With the element
     left at its natural opacity:1, `backwards` holds the from-state during
     the delay and releases into it. */
  .tstf-hero__tagline, .tstf-hero__blurb, .tstf-hero__actions, .tstf-board, .tstf-hero__mark {
    animation: tstf-rise 0.85s var(--f-out) 0.42s backwards;
  }
  .tstf-hero__mark { animation-delay: 0.04s; }
  .tstf-hero__blurb { animation-delay: 0.50s; }
  .tstf-hero__actions { animation-delay: 0.58s; }
  .tstf-board { animation-delay: 0.30s; }
}

/* Top level on purpose — see the note above. A keyframe set declared
   inside @supports or @media does not exist when that condition is false,
   and an animation naming a set that does not exist silently leaves the
   element at its start state. That is how an entire hero disappears. */
@keyframes tstf-wipe { to { transform: translateY(0); } }
@keyframes tstf-rise { from { opacity: 0; transform: translate3d(0, 22px, 0); } }

/* =========================================================================
   9. ACCESSIBILITY FLOOR
   ========================================================================= */

.tstf-skip {
  position: fixed; top: 12px; left: 12px; z-index: 300;
  padding: 12px 20px;
  background: var(--f-red); color: #fff;
  font-weight: 700; font-size: 14px;
  border-radius: 4px;
  transform: translateY(-180%);
  transition: transform 0.2s var(--f-out);
}
.tstf-skip:focus-visible { transform: translateY(0); }

.tstf-sr {
  position: absolute !important;
  width: 1px; height: 1px; padding: 0; margin: -1px;
  overflow: hidden; clip-path: inset(50%); white-space: nowrap;
}

/* The Hub sets its own focus ring; make sure the flagship components keep
   a visible one on the darker panels. */
.tstf-section :focus-visible,
.tstf-hero :focus-visible,
.tstf-strip :focus-visible {
  outline: 2px solid var(--f-gold);
  outline-offset: 3px;
  border-radius: 3px;
}

@media (prefers-reduced-motion: reduce) {
  .tstf-wordmark__row > span { transform: none; animation: none; }
  .tstf-hero__tagline, .tstf-hero__blurb, .tstf-hero__actions,
  .tstf-board, .tstf-hero__mark { animation: none; }
  .tstf-in { opacity: 1; transform: none; }
}

/* =========================================================================
   10. THE WEBGL SKY
   Sits behind everything, in front of nothing. When it mounts it takes over
   from the Hub's CSS cloud field — that field stays in the DOM as the
   fallback, just dimmed, so there is never a moment with no sky at all.
   ========================================================================= */

.tst-sky-gl {
  position: fixed;
  inset: 0;
  z-index: 0;
  width: 100%;
  height: 100%;
  pointer-events: none;
  display: block;
}

/* Once the GL field is live the CSS clouds would double the density, so
   they drop back to a faint trace rather than disappearing outright —
   they still carry the hero cluster and the near-cursor interactions.
   Was 0.5, which was still enough to read as a second field. */
.tst-has-gl-sky .tst-sky__cloud { opacity: calc(var(--c-op, 0.05) * 0.22); }

/* Content has to sit above the canvas. The Hub already gives its sections
   z-index 1; this covers the flagship ones and the chrome. */
.tstf-hero, .tstf-section, .tstf-strip, .tst-hub-nav, .tst-hub-footer { position: relative; z-index: 2; }

/* =========================================================================
   11. LETTING THE SKY THROUGH
   The Hub's hero ends its background stack with an OPAQUE
   linear-gradient(#0C0C0A → #090908). That is correct for the Hub, which
   has no canvas behind it — but it covered the WebGL sky completely, so
   the whole depth concept was invisible. The flagship keeps the Hub's two
   red pools (they are the brand's key light) and drops only the opaque
   base, so the sky reads through.

   The Hub hero is also text-align:center; the flagship composition is
   asymmetric, so that is reset here rather than fought per element.
   ========================================================================= */

.tst-hub-hero.tstf-hero {
  background: none;
  text-align: left;
}

/* Sections sit over the sky rather than on an opaque slab. A translucent
   ink veil keeps every contrast ratio intact while still letting the
   clouds move behind the content — that motion under the text is what
   makes the page feel like one continuous descent. */
.tstf-section, .tstd-section, .tst-hub-section {
  background: linear-gradient(180deg,
    rgba(10, 10, 9, 0.90) 0%, rgba(10, 10, 9, 0.955) 18%,
    rgba(10, 10, 9, 0.955) 82%, rgba(10, 10, 9, 0.90) 100%);
}

/* The one exception: the news panels are photographic and need a solid
   ground behind them, so they keep their own opaque ink. */
.tstf-panel { background: #16130F; }

/* =========================================================================
   12. DEMOTING THE DASHBOARD'S RED — Hub override, deliberate

   The Hub's `.tstd-next` card — the largest single element below the hero —
   fills 62% of itself with a burgundy radial wash and rings itself in red.
   On the Hub, where it is the one hero-scale object on the page, that
   works. On the flagship it is the second red field in a design whose whole
   correction is that red had become the background rather than the accent,
   and it sits directly under a hero that already spends the viewport's one
   red moment on the primary CTA.

   So the wash goes and the red becomes an EDGE: a single hard rule down
   the leading side, graphite behind it. Same information, same status,
   stated once instead of smeared across a card. dashboard.css itself is
   untouched — this is an override, so the Hub still renders as the Hub.
   ========================================================================= */

.tstd-next {
  border-color: color-mix(in oklab, var(--f-silver) 22%, transparent);
  background:
    linear-gradient(90deg,
      color-mix(in oklab, var(--f-red) 30%, transparent) 0 3px,
      transparent 3px),
    radial-gradient(130% 150% at 0% 0%,
      color-mix(in oklab, var(--f-slate) 62%, transparent) 0%,
      transparent 64%),
    rgba(244, 241, 234, 0.035);
}

/* The eyebrow was #FF5C72 — a lighter red than --hub-red, which is a
   second red rather than the same one. It carries the status now in the
   neutral core, with the red reserved for the live dot beside it. */
.tstd-next__eyebrow { color: var(--f-silver); }
.tstd-next__eyebrow::before { color: var(--f-red); }

/* =========================================================================
   13. THE NEWS SECTION IS THE FLAGSHIP'S — Hub override, deliberate

   anime.js renders the Hub's `#tstd-news` asynchronously from the same news
   function the flagship uses, so both were writing one element and the last
   fetch home won. The flagship now renders `#tstf-news` in that slot and
   hides the Hub's section from here.

   It has to be CSS, and it has to be a class on <html>. The Hub's own
   `mount()` sets `node.hidden = false` on every render, so the `hidden`
   attribute would be cleared the moment its news arrived; a class on the
   root element is something no Hub code path touches. `display: none`
   rather than visibility, so the hidden section takes no vertical space
   and the anchors above it still land in the right place.
   ========================================================================= */

.tstf-owns-news #tstd-news { display: none !important; }

/* Anchor offset, so a jump lands clear of the fixed chrome rather than with
   its heading tucked behind it. Stated for every section the flagship owns,
   so the landing position is designed rather than whatever the browser and
   the sticky strip happen to agree on. */
#tstf-news,
#tstf-today,
#tstf-week { scroll-margin-top: clamp(56px, 8vh, 96px); }

/* =========================================================================
   14. THE ENTRY PORTAL

   The page loads BEHIND this, which is a requirement and not a detail. The
   cover is a pseudo-element on <html>, armed by the inline snippet in the
   head, so it is painted on the very first frame — before this stylesheet
   has even finished, and without waiting on tst-portal.js. Nothing about
   the site's loading is deferred by the portal; it is simply covered.

   Everything here lives or dies with `.tst-portal-armed`. Remove that one
   class and the portal is gone, the scroll is back and the page is the
   page — which is exactly what SKIP, Escape, the express pass and the head
   watchdog all do.
   ========================================================================= */

.tst-portal-armed,
.tst-portal-armed body {
  overflow: hidden;
  /* Belt and braces on iOS, where overflow:hidden on <body> alone is not
     always enough to stop the rubber band. */
  overscroll-behavior: none;
}

/* The zero-flash cover. Fixed, opaque, under the portal and over the site. */
.tst-portal-armed::after {
  content: "";
  position: fixed;
  inset: 0;
  z-index: 9998;
  background: var(--f-ink, #0A0A09);
  pointer-events: none;
}

.tstp {
  position: fixed;
  inset: 0;
  z-index: 9999;
  overflow: hidden;
  background: var(--f-ink);
  /* The whole surface is the drag handle, so the browser must not claim
     the gesture for scrolling or pull-to-refresh. */
  touch-action: none;
  cursor: grab;
  animation: tstp-in 520ms var(--f-out) backwards;
}

.tstp.is-dragging { cursor: grabbing; }

.tstp__gl {
  position: absolute;
  inset: 0;
  width: 100%;
  height: 100%;
  display: block;
}

.tstp__ui {
  position: absolute;
  inset: 0;
  display: grid;
  grid-template-rows: auto 1fr auto;
  padding: clamp(20px, 3.4vw, 40px);
  /* The UI must never eat the drag — each control re-enables itself. */
  pointer-events: none;
}

.tstp__ui > * { pointer-events: auto; }

/* The wordmark is deliberately SMALL here. The hero owns the wordmark
   moment; this screen belongs to the world, and a second full-scale
   lockup two seconds before the real one would flatten both. */
.tstp__mark {
  margin: 0;
  justify-self: start;
  font-family: var(--f-mono);
  font-size: clamp(9px, 1vw, 11px);
  font-weight: 700;
  letter-spacing: 0.42em;
  text-transform: uppercase;
  color: var(--f-silver);
  animation: tstp-rise 900ms 220ms var(--f-out) backwards;
}

.tstp__mark span { color: var(--f-bone); }

/* HOLD TO ENTER is a rule that fills, not a button with chrome. The
   affordance is the label and the line; anything more would compete with
   the world, which is the only object on screen that should be looked at. */
/* The footer cell: the hold and its hint, stacked and centred. */
.tstp__foot {
  grid-row: 3;
  display: grid;
  justify-items: center;
  gap: 14px;
}

.tstp__hold {
  position: relative;
  display: grid;
  gap: 10px;
  /* ≥44px tall including the padding, on every screen. */
  padding: 14px 26px 12px;
  border: 0;
  background: none;
  color: var(--f-bone);
  font-family: var(--f-mono);
  font-size: clamp(11px, 1.3vw, 13px);
  font-weight: 700;
  letter-spacing: 0.34em;
  text-transform: uppercase;
  cursor: pointer;
  animation: tstp-rise 900ms 420ms var(--f-out) backwards;
}

.tstp__hold-label { transition: letter-spacing 700ms var(--f-out), color 300ms; }
.tstp__hold.is-holding .tstp__hold-label { letter-spacing: 0.52em; color: #fff; }

.tstp__hold-fill {
  position: relative;
  display: block;
  height: 1px;
  background: color-mix(in oklab, var(--f-silver) 34%, transparent);
}

/* The DOM half of the gauge. The shader draws the arc around the world and
   the blade's acceleration; this is the plain, unmissable version of the
   same number, for anyone who is not watching the sphere. */
.tstp__hold-fill::after {
  content: "";
  position: absolute;
  inset: -1px 0;
  background: var(--f-bone);
  transform: scaleX(var(--tstp-hold, 0));
  transform-origin: left center;
}

/* No transition on the fill: it is driven every frame from the same rAF
   that turns the world, so a CSS transition would only fight it. */
.tstp__hold-fill::after { transition: none; }

.tstp__hint {
  margin: 0;
  font-size: 12px;
  color: var(--f-faint);
  animation: tstp-rise 900ms 620ms var(--f-out) backwards;
}

.tstp__skip {
  position: absolute;
  right: clamp(20px, 3.4vw, 40px);
  bottom: clamp(20px, 3.4vw, 40px);
  padding: 12px 16px;
  min-height: 44px;
  border: 1px solid color-mix(in oklab, var(--f-silver) 26%, transparent);
  border-radius: 999px;
  background: none;
  color: var(--f-silver);
  font-family: var(--f-mono);
  font-size: 10px;
  font-weight: 700;
  letter-spacing: 0.22em;
  cursor: pointer;
  transition: color 240ms var(--f-ease), border-color 240ms var(--f-ease);
}

.tstp__skip:hover { color: var(--f-bone); border-color: var(--f-silver); }

/* Keyboard only. `root` takes focus when the portal opens (see
   tst-portal.js) precisely so this ring does not greet every visitor. */
.tstp__hold:focus-visible,
.tstp__skip:focus-visible {
  outline: 2px solid var(--f-bone);
  outline-offset: 4px;
}

/* The dialog itself is focused programmatically and must not draw a ring
   for that — it never receives focus from a keypress. */
.tstp:focus,
.tstp:focus-visible { outline: none; }

/* ---------------------------------------------------------------- exits
   The strike and the skip. Both end with the portal at opacity 0 and the
   class removed; the difference is only how much ceremony they get. */
.tstp.is-entering {
  animation: tstp-strike 620ms cubic-bezier(0.7, 0, 0.3, 1) forwards;
}

.tstp.is-entering .tstp__ui {
  animation: tstp-uiout 220ms var(--f-ease) forwards;
}

.tstp.is-leaving {
  animation: tstp-uiout 260ms var(--f-ease) forwards;
}

/* Top level, never inside @supports or @media — a keyframe set declared
   under a condition that is false does not exist, and an element left at
   opacity 0 waiting for it never comes back. That bug cost a whole hero
   earlier in this project. */
@keyframes tstp-in { from { opacity: 0; } }
@keyframes tstp-rise { from { opacity: 0; transform: translate3d(0, 14px, 0); } }
@keyframes tstp-uiout { to { opacity: 0; } }
@keyframes tstp-strike {
  0%   { opacity: 1; transform: scale(1); }
  38%  { opacity: 1; transform: scale(1.03); }
  100% { opacity: 0; transform: scale(1.16); }
}

/* Reduced motion never arms the portal at all (the head snippet checks it
   before adding the class), so there is nothing to soften here — but if
   the preference is switched on mid-session, everything collapses to a
   plain cut rather than leaving a half-animated overlay on screen. */
@media (prefers-reduced-motion: reduce) {
  .tstp,
  .tstp__mark,
  .tstp__hold,
  .tstp__hint,
  .tstp.is-entering,
  .tstp.is-entering .tstp__ui,
  .tstp.is-leaving { animation-duration: 1ms !important; }
}

@media (max-width: 749px) {
  /* Thumb reach: the hold sits low and wide, the skip clears it. */
  .tstp__hold { padding: 16px 22px 14px; letter-spacing: 0.28em; }
  .tstp__hint { font-size: 11px; }
  .tstp__skip { right: auto; left: 50%; transform: translateX(-50%); bottom: 14px; padding: 10px 14px; }
  .tstp__ui { padding-bottom: 76px; }
}

/* =========================================================================
   15. THE UTILITY LAYER, RE-ART-DIRECTED
   THE DROP (today) · OUR WEEK (the running order)

   The Hub's dashboard built its hierarchy out of borders: a rounded card
   for the next show, a second card adrift in an empty column, trapezoid
   day TABS, and three identical bordered rectangles with the poster shrunk
   to a thumbnail inside each. Correct software, wrong register — after the
   portal and the hero it read as an admin panel bolted to a film.

   NOT ONE BORDERED RECTANGLE IN EITHER SECTION. Hierarchy comes from:
     SCALE        the countdown and the day name are the biggest things on
                  the screen, because they are the news
     IMAGERY      cover art is full-height, bleeds off the edge and is
                  graded into the neutral core — composition, not a chip
     COMPOSITION  masthead left, artwork right, the clock straddling both,
                  the rundown full-bleed beneath
     TYPOGRAPHY   oversized ghost day codes do the work the tabs did

   The palette does not change and red stays where it was: the live dot,
   today's hairline, and the imminent countdown. Nothing else.
   ========================================================================= */

.tstf-owns-today #tstd-today,
.tstf-owns-week  #tstd-week { display: none !important; }

/* ---------------------------------------------------------- THE DROP */

.tstf-drop {
  position: relative;
  overflow: clip;
  isolation: isolate;
  padding-block: clamp(56px, 9vh, 116px) clamp(40px, 6vh, 80px);
  /* Tall enough to be an event, not so tall it becomes a void — the
     rundown at the bottom fills whatever the masthead and clock leave. */
  min-height: min(92svh, 860px);
  display: grid;
  align-content: center;
}

/* THE ARTWORK IS THE COMPOSITION.
   Full height, hard against the right edge, running off it. Graded to the
   neutral core and masked so its left side dissolves into the ink rather
   than ending on a line — that dissolve is what stops it being a picture
   in a box. */
/* Confined to the upper two thirds. It used to run the full height and
   the rundown's hairlines crossed the artwork — the placeholder's own
   lettering was legible through the times, which is exactly the kind of
   noise a broadcast rundown cannot have. */
.tstf-drop__art {
  position: absolute;
  inset: 0 0 34% auto;
  width: clamp(280px, 46vw, 760px);
  z-index: -1;
  pointer-events: none;
}

.tstf-drop__art img {
  width: 100%;
  height: 100%;
  object-fit: cover;
  object-position: 50% 28%;
  /* Desaturated into the neutral core, then lifted just enough to read.
     Anime key art is usually highly saturated; left alone it would be the
     only colour on the page and undo the whole palette correction. */
  /* Was brightness(0.52), which on anything but the brightest key art
     left the composition's whole right-hand side invisible — the artwork
     was present in the DOM and absent from the design. */
  filter: grayscale(1) contrast(1.22) brightness(0.86);
  -webkit-mask-image: linear-gradient(to left, #000 34%, rgba(0,0,0,0.55) 62%, transparent 96%);
          mask-image: linear-gradient(to left, #000 34%, rgba(0,0,0,0.55) 62%, transparent 96%);
}

/* A second mask from below, so the rundown's hairlines cross clean ink and
   the artwork never competes with the times. */
.tstf-drop__art::after {
  content: "";
  position: absolute;
  inset: -1px;
  background: linear-gradient(to top, var(--f-ink) 0%, transparent 62%);
}

.tstf-drop__art--none {
  display: grid;
  place-items: center;
  width: clamp(240px, 34vw, 520px);
}

.tstf-drop__art--none svg {
  width: 52%;
  fill: color-mix(in oklab, var(--f-graphite) 90%, var(--f-stone));
  transform: rotate(-12deg);
}

.tstf-drop__inner {
  position: relative;
  z-index: 1;
  width: 100%;
  max-width: var(--f-shell-wide);
  margin-inline: auto;
  padding-inline: clamp(18px, 4vw, 56px);
  display: grid;
  gap: clamp(22px, 3vw, 40px);
}

.tstf-drop__day {
  margin: 0;
  font-family: var(--f-head);
  /* The day is a masthead. At the Hub's 34px it was a section heading; at
     this scale it is the thing you read from across the room. */
  font-size: clamp(46px, 8.2vw, 132px);
  line-height: 0.86;
  letter-spacing: -0.022em;
  text-transform: uppercase;
  color: var(--f-bone);
}

.tstf-drop__date {
  margin: 14px 0 0;
  font-family: var(--f-mono);
  font-size: clamp(10px, 1.1vw, 12px);
  font-weight: 700;
  letter-spacing: 0.26em;
  color: var(--f-silver);
}

/* The lead. The clock is the largest element in the section and it
   straddles the artwork's left edge on purpose — type crossing the image
   is what makes it one composition instead of two columns. */
.tstf-drop__lead {
  display: grid;
  gap: 6px;
  max-width: 22ch;
}

.tstf-drop__label {
  margin: 0;
  font-family: var(--f-mono);
  font-size: 10px;
  font-weight: 700;
  letter-spacing: 0.3em;
  color: var(--f-faint);
}

.tstf-drop__clock {
  margin: 0;
  font-family: var(--f-head);
  font-size: clamp(52px, 10.4vw, 168px);
  line-height: 0.82;
  letter-spacing: -0.03em;
  font-variant-numeric: tabular-nums;
  color: var(--f-bone);
  /* The one thing on this screen allowed to overflow its column. */
  white-space: nowrap;
}

.tstf-drop__clock--tba {
  font-size: clamp(30px, 4.6vw, 64px);
  color: var(--f-stone);
  letter-spacing: 0.02em;
}

.tstf-drop__title {
  margin: 10px 0 0;
  font-family: var(--f-head);
  font-size: clamp(19px, 2.3vw, 32px);
  line-height: 1.04;
  letter-spacing: -0.012em;
  text-transform: uppercase;
  color: var(--f-cream);
}

.tstf-drop__meta {
  margin: 0;
  font-family: var(--f-mono);
  font-size: 10.5px;
  font-weight: 700;
  letter-spacing: 0.2em;
  color: var(--f-silver);
}

/* ------------------------------------------------- THE RUNNING ORDER
   A broadcast rundown. Hairlines, tabular times at headline scale, and
   nothing enclosed. */
.tstf-order {
  list-style: none;
  margin: clamp(8px, 2vw, 24px) 0 0;
  padding: 0;
  border-top: 1px solid var(--f-line);
}

.tstf-order__row {
  display: grid;
  grid-template-columns: minmax(84px, auto) minmax(0, 1fr) auto;
  align-items: baseline;
  gap: clamp(14px, 2.4vw, 34px);
  padding: clamp(13px, 1.7vw, 20px) 0;
  border-bottom: 1px solid var(--f-line);
  transition: background-color 260ms var(--f-ease);
}

.tstf-order__row:hover { background: rgba(244, 241, 234, 0.028); }

.tstf-order__time {
  font-family: var(--f-head);
  font-size: clamp(19px, 2.3vw, 32px);
  line-height: 1;
  font-variant-numeric: tabular-nums;
  letter-spacing: -0.01em;
  color: var(--f-bone);
}

.tstf-order__name {
  display: block;
  font-size: clamp(14px, 1.3vw, 17px);
  font-weight: 600;
  line-height: 1.3;
  color: var(--f-cream);
}

.tstf-order__name em {
  display: block;
  margin-top: 3px;
  font-style: normal;
  font-size: 12.5px;
  font-weight: 400;
  color: var(--f-faint);
}

.tstf-order__state {
  font-family: var(--f-mono);
  font-size: 9.5px;
  font-weight: 700;
  letter-spacing: 0.2em;
  color: var(--f-faint);
  white-space: nowrap;
}

.tstf-order__state b {
  font-variant-numeric: tabular-nums;
  color: var(--f-silver);
}

.tstf-order__state b[data-soon] { color: var(--f-gold); }

/* The lead row is marked by WEIGHT, not by a box. */
.tstf-order__row.is-lead .tstf-order__time,
.tstf-order__row.is-lead .tstf-order__name { color: #fff; }
.tstf-order__row.is-lead { box-shadow: inset 2px 0 0 var(--f-red); padding-left: 16px; }

/* AIRED ROWS RECEDE BY COLOUR, NOT BY OPACITY.
   `opacity: 0.44` over text already at --f-dim (cream @0.68) composited to
   roughly a 0.30 alpha — about 2.4:1, nowhere near AA, and three quarters
   of the week is already-aired on a Friday. "Preserve legibility" and
   "opacity as a dimmer" are incompatible; a stated colour is measurable. */
.tstf-order__row.is-aired .tstf-order__time { color: var(--f-stone); }
.tstf-order__row.is-aired .tstf-order__name { color: var(--f-faint); font-weight: 500; }
/* --f-stone is 3.79:1 at this size — fine for a rule, under AA for 12.5px
   text. The aired note recedes by size and weight, not by going grey
   enough to fail. */
.tstf-order__row.is-aired .tstf-order__name em { color: var(--f-faint); }

.tstf-order__time.is-notime { color: var(--f-stone); letter-spacing: 0; }

/* ------------------------------------------------------------ OUR WEEK */

.tstf-week {
  position: relative;
  padding-block: clamp(48px, 7vh, 96px) clamp(32px, 4.5vh, 64px);
}

.tstf-week__head {
  width: 100%;
  max-width: var(--f-shell-wide);
  margin: 0 auto clamp(26px, 4vw, 52px);
  padding-inline: clamp(18px, 4vw, 56px);
  display: grid;
  gap: 28px;
  align-items: end;
}

@media (min-width: 860px) {
  .tstf-week__head { grid-template-columns: minmax(0, 1fr) auto; }
}

/* The standing count. Hairline-separated figures, no card. */
.tstf-week__stats {
  display: flex;
  margin: 0;
  gap: clamp(18px, 2.6vw, 40px);
}

.tstf-week__stats div { padding-left: clamp(14px, 2vw, 28px); border-left: 1px solid var(--f-line); }
.tstf-week__stats div:first-child { padding-left: 0; border-left: 0; }

.tstf-week__stats dt {
  font-family: var(--f-mono);
  font-size: 9px;
  font-weight: 700;
  letter-spacing: 0.24em;
  text-transform: uppercase;
  color: var(--f-faint);
}

.tstf-week__stats dd {
  margin: 4px 0 0;
  font-family: var(--f-head);
  font-size: clamp(24px, 2.8vw, 40px);
  line-height: 1;
  font-variant-numeric: tabular-nums;
  color: var(--f-bone);
}

.tstf-week__stats dd span {
  display: block;
  margin-top: 5px;
  font-family: var(--f-mono);
  font-size: 9px;
  font-weight: 700;
  letter-spacing: 0.16em;
  text-transform: uppercase;
  color: var(--f-silver);
}

.tstf-week__title {
  margin: 8px 0 0;
  font-family: var(--f-head);
  font-size: clamp(34px, 5.4vw, 86px);
  line-height: 0.9;
  letter-spacing: -0.02em;
  text-transform: uppercase;
  color: var(--f-bone);
}

.tstf-week__sub {
  margin: 12px 0 0;
  max-width: 46ch;
  font-size: clamp(14px, 1.2vw, 16px);
  color: var(--f-dim);
}

/* THE BAND. Seven columns, no tabs, no cards — hairlines and weight.
   Today is WIDER, which is the whole point: the layout tells you what day
   it is before you read a word.

   The columns end ragged, because the days are not the same length. One
   hairline underneath closes the band without enclosing anything. */
.tstf-week__band {
  position: relative;   /* offsetParent for the columns — see renderWeek */
  display: flex;
  border-bottom: 1px solid var(--f-line);
  width: 100%;
  max-width: var(--f-shell-wide);
  margin-inline: auto;
  padding-inline: clamp(18px, 4vw, 56px);
  align-items: stretch;
}

.tstf-wday {
  position: relative;
  isolation: isolate;
  flex: 1 1 0;
  min-width: 0;
  padding: 108px clamp(10px, 1.1vw, 18px) 20px;
  border-top: 1px solid var(--f-line);
  /* The only vertical rules in the section, and they are hairlines between
     days rather than an outline around each one. */
  box-shadow: inset -1px 0 0 var(--f-line);
  overflow: clip;
}

.tstf-wday:last-child { box-shadow: none; }

.tstf-wday.is-today {
  /* Asymmetry, structural: today takes twice the room. */
  flex: 2.15 1 0;
  border-top: 2px solid var(--f-red);
  background: linear-gradient(to bottom,
    color-mix(in oklab, var(--f-graphite) 88%, transparent) 0%,
    transparent 72%);
}

/* The week's artwork, bleeding out of the top of each column. Greyscale
   and low, so the band reads as a filmstrip of the week rather than seven
   pictures — and today's is stronger and taller, which is the hierarchy. */
.tstf-wday__art {
  position: absolute;
  z-index: -1;
  inset: 0 0 auto 0;
  height: 128px;
  background-size: cover;
  background-position: 50% 24%;
  filter: grayscale(1) contrast(1.1) brightness(0.46);
  opacity: 0.5;
  -webkit-mask-image: linear-gradient(to bottom, #000 0%, transparent 92%);
          mask-image: linear-gradient(to bottom, #000 0%, transparent 92%);
  transition: opacity 420ms var(--f-ease), filter 420ms var(--f-ease);
}

.tstf-wday.is-today .tstf-wday__art {
  height: 168px;
  opacity: 0.92;
  filter: grayscale(0.82) contrast(1.06) brightness(0.62);
}

.tstf-wday:hover .tstf-wday__art { opacity: 0.95; filter: grayscale(0.6) brightness(0.6); }

.tstf-wday__art--none { background: none; }

/* The oversized ghost day code. This is what replaced the trapezoid tabs:
   the same information, doing the job with type instead of chrome. */
.tstf-wday__ghost {
  position: absolute;
  left: clamp(4px, 0.6vw, 12px);
  top: 70px;
  font-family: var(--f-head);
  /* One initial, so this can be big without ever reaching the next column.
     `isolation` on the column below is what actually keeps a negative
     z-index child inside the clip. */
  font-size: clamp(44px, 5.6vw, 104px);
  line-height: 1;
  letter-spacing: -0.07em;
  color: color-mix(in oklab, var(--f-smoke) 86%, transparent);
  pointer-events: none;
  user-select: none;
  z-index: 0;
}

.tstf-wday.is-today .tstf-wday__ghost {
  color: color-mix(in oklab, var(--f-slate) 82%, transparent);
  font-size: clamp(64px, 8.4vw, 152px);
}

.tstf-wday__code {
  position: relative;
  z-index: 2;
  margin: 0 0 14px;
  display: flex;
  align-items: baseline;
  gap: 8px;
  font-family: var(--f-mono);
  font-size: 10px;
  font-weight: 700;
  letter-spacing: 0.24em;
  color: var(--f-silver);
}

.tstf-wday__code b { font-size: 13px; color: var(--f-bone); letter-spacing: 0.04em; }

.tstf-wday__code i {
  font-style: normal;
  font-size: 9px;
  letter-spacing: 0.22em;
  color: var(--f-red);
}

.tstf-wday__shows {
  position: relative;
  z-index: 2;
  list-style: none; margin: 0; padding: 0; display: grid; gap: 11px;
}

.tstf-wday__show {
  position: relative;
  display: grid;
  gap: 1px;
}

.tstf-wday__t {
  font-family: var(--f-mono);
  font-size: 12px;
  font-weight: 700;
  font-variant-numeric: tabular-nums;
  letter-spacing: 0.06em;
  color: var(--f-bone);
}

.tstf-wday.is-today .tstf-wday__t {
  font-family: var(--f-head);
  font-size: clamp(15px, 1.5vw, 20px);
  letter-spacing: -0.01em;
}

.tstf-wday__n {
  font-size: 12.5px;
  line-height: 1.28;
  color: var(--f-dim);
}

.tstf-wday.is-today .tstf-wday__n { font-size: 14px; color: var(--f-cream); font-weight: 600; }

/* Same rule in the week band, same reason. */
.tstf-wday__show.is-aired .tstf-wday__t { color: var(--f-silver); }
.tstf-wday__show.is-aired .tstf-wday__n { color: var(--f-faint); }
.tstf-wday__t.is-notime { color: var(--f-faint); }   /* --f-stone fails AA at 12px */

.tstf-wday__empty {
  margin: 0;
  font-family: var(--f-mono);
  font-size: 9.5px;
  letter-spacing: 0.18em;
  text-transform: uppercase;
  color: var(--f-stone);
}

/* ------------------------------------------------------------ mobile
   The week becomes a swipe, not seven squeezed columns. Each day is a
   near-full-width panel with its artwork at dramatic scale — a broadcast
   you page through. JS opens it on today. */
@media (max-width: 749px) {
  .tstf-drop { min-height: auto; padding-block: clamp(40px, 7vh, 72px) 32px; }

  .tstf-drop__art {
    /* Behind the type rather than beside it, because there is no beside on
       a phone. Still full-bleed, still off the edge — the drama is the
       point and shrinking it to a thumbnail would be the original sin. */
    inset: 0;
    width: 100%;
  }

  .tstf-drop__art { inset: 0; }

  .tstf-drop__art img {
    object-position: 50% 18%;
    filter: grayscale(1) contrast(1.24) brightness(0.52);
    /* Gone well before the rundown starts. On the first mobile render the
       artwork ran the full height at half strength and the placeholder's
       own lettering was legible straight through the times — the same
       fault that was fixed on desktop, unfixed here because the mobile
       block overrides the mask. */
    -webkit-mask-image: linear-gradient(to bottom, #000 0%, rgba(0,0,0,0.42) 30%, transparent 52%);
            mask-image: linear-gradient(to bottom, #000 0%, rgba(0,0,0,0.42) 30%, transparent 52%);
  }

  .tstf-drop__lead { max-width: none; }

  .tstf-order__row {
    grid-template-columns: minmax(62px, auto) minmax(0, 1fr);
    row-gap: 4px;
  }
  .tstf-order__state { grid-column: 2; }

  .tstf-week__band {
    display: flex;
    overflow-x: auto;
    scroll-snap-type: x mandatory;
    -webkit-overflow-scrolling: touch;
    scrollbar-width: none;
    gap: 0;
    padding-inline: 16px;
  }
  .tstf-week__band::-webkit-scrollbar { display: none; }

  .tstf-wday {
    flex: 0 0 74vw;
    scroll-snap-align: start;
    padding: 132px 18px 24px;
  }
  .tstf-wday.is-today { flex: 0 0 86vw; }
  .tstf-wday__art { height: 156px; }
  .tstf-wday.is-today .tstf-wday__art { height: 196px; }
  .tstf-wday__ghost { top: 96px; font-size: 62px; }
  .tstf-wday.is-today .tstf-wday__ghost { font-size: 82px; }
}

/* =========================================================================
   16. THE AIRING BOARD, DISSOLVED INTO THE HERO

   The board was a panel: 1px border, 18px radius, translucent fill, a
   backdrop blur, a header bar with its own bottom rule, and poster chips
   in every row. Every one of those is a device for separating a widget
   from its page — which is precisely what it should not be doing. Sitting
   beside a full-bleed wordmark it read as a SaaS card that had been dropped
   onto a film.

   The functionality is untouched: same live feed, same lead show, same
   ticking clock, same queue, same JP-broadcast/UK-release labelling, same
   honesty note. What changes is that it is now typography standing on the
   hero's own ground.

     · no border, no radius, no fill, no blur — one hairline down its
       leading edge, which ties it to the hero's asymmetry
     · the header is an eyebrow, in the hero's own eyebrow language
     · the queue becomes a rundown on hairlines, in the SAME language as
       THE DROP two screens below. That repetition is deliberate: the page
       changes register as it descends, but the rundown is the one form
       carried through, so it reads as one publication
     · the poster chips are gone. The hero's imagery is the sky and the
       world; the artwork gets its full-bleed moment in THE DROP. Two
       competing picture systems in one viewport is how a composition
       becomes a dashboard — and those chips were rendering empty anyway
       whenever a hand-verified UK time won the merge
   ========================================================================= */

.tstf-board {
  border: 0;
  border-radius: 0;
  background: none;
  backdrop-filter: none;
  overflow: visible;
  /* The one mark of structure. On wide screens it is a rule down the
     leading edge; there is nothing else holding this together but
     alignment, which is the point. */
  padding-left: clamp(0px, 2vw, 30px);
}

@media (min-width: 1000px) {
  .tstf-board { box-shadow: inset 1px 0 0 var(--f-line); }
}

.tstf-board__head {
  padding: 0 0 12px;
  border-bottom: 1px solid var(--f-line);
  letter-spacing: 0.26em;
  color: var(--f-silver);
}

.tstf-board__lead {
  padding: 18px 0 20px;
  gap: 0;
  /* One column now that the poster chip is gone. */
  grid-template-columns: minmax(0, 1fr);
  border-bottom: 1px solid var(--f-line);
}

.tstf-board__poster { display: none; }

.tstf-board__show {
  font-size: clamp(17px, 2vw, 26px);
  letter-spacing: -0.012em;
  color: var(--f-bone);
}

/* The clock is the hero's second-largest piece of type, after the
   wordmark. It was sized as a widget's readout.

   SCOPED TO THE BOARD, and without `!important`. The first version of this
   rule was `.tstf-clock { ... !important }` — and the countdown in THE
   DROP carries both `tstf-clock` and `tstf-drop__clock`, so an
   unqualified !important here would have silently shrunk the 168px
   masthead clock two screens below. A descendant selector is specific
   enough and cannot reach out of the hero. */
.tstf-board .tstf-clock {
  /* 76px was too big for the column: "05:48:48" in Archivo Black ran past
     the board's right edge and off the frame. */
  font-size: clamp(34px, 4.3vw, 62px);
  /* 0.88 left ~80px of air between the digits and the rundown's first
     hairline, because a display face's line box is much taller than its
     numerals. Set below 1 deliberately and trimmed. */
  line-height: 0.74;
  letter-spacing: -0.03em;
  margin-top: 10px;
}

/* The lead block had a widget's internal padding, which left a dead
   70px gap between the clock and the rundown. */
.tstf-board__lead { padding-bottom: 14px; }
.tstf-board__queue { padding: 0; margin-top: 0; }

.tstf-queue-row {
  grid-template-columns: minmax(46px, auto) minmax(0, 1fr) auto;
  gap: 14px;
  padding: 12px 0;
  border-bottom: 1px solid var(--f-line);
  border-radius: 0;
  background: none;
}

.tstf-queue-row:hover { background: rgba(244, 241, 234, 0.03); }

/* The empty poster chip goes. The time takes its slot, tabular, at the
   same scale the rundown uses below — one language, two registers. */
.tstf-queue-row__art { display: none; }

.tstf-board__note {
  padding: 12px 0 0;
  border-top: 0;
}

.tstf-queue-row__at {
  font-family: var(--f-head);
  font-size: 15px;
  line-height: 1.1;
  font-variant-numeric: tabular-nums;
  letter-spacing: -0.01em;
  color: var(--f-bone);
}

/* =========================================================================
   17. THE TWO TRANSITIONS
   See assets/tst-transition.js for why there are two and not four.

   A. SLASH — two ink halves, cut along one diagonal, closing then parting.
      The caller swaps state in the covered frame between the two.
   B. FLASH — flash-step: the live page desaturated and nudged for ~110ms
      with speed lines across it, then the destination snapping in.

   Everything here is transform, opacity and clip-path, so both run on the
   compositor. No layout property is animated in either.
   ========================================================================= */

.tstt {
  position: fixed;
  inset: 0;
  z-index: 9990;      /* under the portal (9999), over everything else */
  pointer-events: none;
  display: none;
  overflow: hidden;
  --tstt-ang: -28deg;
}

.tstt.is-slash,
.tstt.is-flash { display: block; }

/* ------------------------------------------------------------- A. SLASH
   Each half is an oversized ink plane, clipped to one side of the cut and
   parked off-screen perpendicular to it. The rotation is on the wrapper so
   the clip stays a simple straight edge — rotating the clip-path instead
   would need trigonometry for every angle. */
.tstt__half {
  position: absolute;
  left: -60%;
  top: -60%;
  width: 220%;
  height: 220%;
  background: var(--f-ink);
  transform-origin: 50% 50%;
  will-change: transform;
}

.tstt__half--a {
  clip-path: polygon(-10% -10%, 110% -10%, 110% 50%, -10% 50%);
  transform: rotate(var(--tstt-ang)) translateY(-104%);
}

.tstt__half--b {
  clip-path: polygon(-10% 50%, 110% 50%, 110% 110%, -10% 110%);
  transform: rotate(var(--tstt-ang)) translateY(104%);
}

/* Closing. Fast and linear-ish on the way in: a cut is not eased. */
.tstt.is-cutting .tstt__half--a,
.tstt.is-cutting .tstt__half--b {
  transition: transform 210ms cubic-bezier(0.55, 0, 0.55, 1);
  transform: rotate(var(--tstt-ang)) translateY(0);
}

/* Opening. Slower, and eased out, so the destination arrives rather than
   being flung at you. */
.tstt.is-opening .tstt__half--a {
  transition: transform 420ms cubic-bezier(0.16, 1, 0.3, 1);
  transform: rotate(var(--tstt-ang)) translateY(-108%);
}

.tstt.is-opening .tstt__half--b {
  transition: transform 420ms cubic-bezier(0.16, 1, 0.3, 1);
  transform: rotate(var(--tstt-ang)) translateY(108%);
}

/* The cut itself: a hairline of bone along the seam, brightest at the
   moment the halves meet. This is the blade's edge, not a glow. */
.tstt__edge {
  position: absolute;
  left: -60%;
  top: 50%;
  width: 220%;
  height: 2px;
  margin-top: -1px;
  background: linear-gradient(90deg, transparent, var(--f-bone) 18%, #fff 50%, var(--f-bone) 82%, transparent);
  transform: rotate(var(--tstt-ang)) scaleX(0);
  transform-origin: 0% 50%;
  opacity: 0;
}

.tstt.is-cutting .tstt__edge {
  animation: tstt-edge 300ms cubic-bezier(0.3, 0, 0.2, 1) forwards;
}

/* The kunai that makes the cut. It crosses once, ahead of the seam, and it
   is the only figurative element in either transition — the mark doing the
   thing the mark is for. */
.tstt__blade {
  position: absolute;
  left: 50%;
  top: 50%;
  width: clamp(54px, 7vw, 104px);
  margin: -26vh 0 0 -26vw;
  color: var(--f-bone);
  opacity: 0;
}

.tstt__blade svg { width: 100%; height: auto; display: block; fill: currentColor; }

.tstt.is-cutting .tstt__blade {
  animation: tstt-blade 300ms cubic-bezier(0.4, 0, 0.5, 1) forwards;
}

/* Once covered, the blade and the seam have done their job and must not
   sit on top of the new state as it opens. */
.tstt.is-covered .tstt__blade,
.tstt.is-covered .tstt__edge { opacity: 0; animation: none; }

/* ------------------------------------------------------------- B. FLASH */

.tstt__lines {
  position: absolute;
  inset: 0;
  display: grid;
  align-content: center;
  gap: 14vh;
  opacity: 0;
  color: var(--f-bone);
}

.tstt__lines svg { width: 130%; height: auto; margin-left: -15%; fill: none; stroke: currentColor; }

.tstt.is-streaking .tstt__lines {
  animation: tstt-streak 200ms cubic-bezier(0.3, 0, 0.3, 1) forwards;
}

/* The after-image. The live document, drained and nudged — see the note in
   tst-transition.js about why this is not a screenshot. `filter` on body
   promotes it to its own layer for the duration and nothing else, which is
   why this is 110ms and not 400ms. */
/* NO TRANSITION HERE, AND THAT IS THE POINT.
   The first version eased the filter in over 110ms — the same 110ms the
   ghost lasts — so the page was still on its way to being desaturated
   when the class was removed. Measured: grayscale(0) at 46ms, grayscale(0)
   at 80ms. The after-image never actually appeared.

   A freeze-frame is a hard cut. The drain is instant; the ARRIVAL is what
   animates, in `tstt-arrive` below. */
.tstt-ghost {
  filter: grayscale(1) contrast(1.5) brightness(1.15);
  transform: translate3d(-10px, 0, 0);
}

.tstt-arrive {
  animation: tstt-arrive 260ms cubic-bezier(0.16, 1, 0.3, 1);
}

/* Top level, always. A keyframe set declared inside @supports or @media
   does not exist when that condition is false, and an element left mid-
   animation waiting for a name that resolves to nothing never comes back.
   That bug cost a whole hero earlier in this project. */
@keyframes tstt-edge {
  0%   { opacity: 0; transform: rotate(var(--tstt-ang)) scaleX(0); }
  36%  { opacity: 1; }
  100% { opacity: 1; transform: rotate(var(--tstt-ang)) scaleX(1); }
}

@keyframes tstt-blade {
  0%   { opacity: 0; transform: translate3d(-58vw, 46vh, 0) rotate(calc(var(--tstt-ang) + 118deg)) scale(0.8); }
  22%  { opacity: 1; }
  100% { opacity: 0; transform: translate3d(58vw, -46vh, 0) rotate(calc(var(--tstt-ang) + 118deg)) scale(1.05); }
}

@keyframes tstt-streak {
  0%   { opacity: 0; transform: translate3d(38vw, 0, 0) skewX(-14deg); }
  40%  { opacity: 0.85; }
  100% { opacity: 0; transform: translate3d(-40vw, 0, 0) skewX(-14deg); }
}

@keyframes tstt-arrive {
  from { opacity: 0.35; transform: translate3d(0, 18px, 0); filter: grayscale(0.8); }
}

/* Reduced motion never reaches any of this — run() returns after calling
   the swap, so no class is ever added. Belt and braces in case a
   preference changes mid-transition. */
@media (prefers-reduced-motion: reduce) {
  .tstt { display: none !important; }
  .tstt-ghost, .tstt-arrive {
    filter: none !important;
    transform: none !important;
    animation: none !important;
    transition: none !important;
  }
}

/* =========================================================================
   18. THE CHROME AND THE FULL-SCREEN MENU
   See assets/tst-menu.js for the reasoning. The Hub's chip rail stays in
   the DOM, unstyled and hidden, as the fallback — app.js owns its markup
   and anime.js re-runs a scroll spy against it, so emptying it would break
   two things that are fine as they are.
   ========================================================================= */

.tstf-owns-nav .tst-hub-nav { display: none !important; }

/* ------------------------------------------------------------------ the bar
   Fixed, and absent until you have left the hero. The hero is a full
   screen with its own wordmark; a navigation bar across it would be the
   one piece of conventional chrome in the composition. */
.tstf-bar {
  position: fixed;
  inset: 0 0 auto 0;
  z-index: 60;
  pointer-events: none;
  opacity: 0;
  transform: translate3d(0, -100%, 0);
  transition: opacity 420ms var(--f-ease), transform 520ms var(--f-out);
}

.tstf-bar.is-up { opacity: 1; transform: none; }
.tstf-bar.is-up .tstf-bar__inner { pointer-events: auto; }

.tstf-bar__inner {
  display: flex;
  align-items: center;
  gap: clamp(10px, 2vw, 22px);
  width: 100%;
  max-width: var(--f-shell-wide);
  margin-inline: auto;
  padding: clamp(10px, 1.4vw, 16px) clamp(14px, 4vw, 56px);
  /* A hairline and a graded veil, not a panel with a blur. The page has to
     read through it. */
  background: linear-gradient(to bottom,
    color-mix(in oklab, var(--f-ink) 94%, transparent) 0%,
    color-mix(in oklab, var(--f-ink) 74%, transparent) 62%,
    transparent 100%);
}

.tstf-bar__mark {
  font-family: var(--f-head);
  font-size: clamp(13px, 1.3vw, 16px);
  letter-spacing: 0.06em;
  color: var(--f-bone);
  text-decoration: none;
  padding: 12px 4px;
  min-height: 44px;
  display: inline-flex;
  align-items: center;
}

.tstf-bar__live {
  display: inline-flex;
  align-items: center;
  gap: 8px;
  margin-left: auto;
  min-height: 44px;
  padding: 10px 14px;
  border: 0;
  background: none;
  color: var(--f-silver);
  font-family: var(--f-mono);
  font-size: 10px;
  font-weight: 700;
  letter-spacing: 0.22em;
  text-transform: uppercase;
  cursor: pointer;
  transition: color 240ms var(--f-ease);
}

.tstf-bar__live:hover { color: var(--f-bone); }
.tstf-bar__live b { font-family: var(--f-head); font-size: 14px; letter-spacing: 0; color: var(--f-bone); }
.tstf-bar__live[hidden] { display: none; }

/* MENU is a label and a two-line rule, in the same register as HOLD TO
   ENTER in the portal — the site's one verb-plus-rule idiom. */
.tstf-bar__menu {
  display: inline-grid;
  gap: 7px;
  justify-items: end;
  min-height: 44px;
  padding: 11px 2px 9px 14px;
  border: 0;
  background: none;
  color: var(--f-bone);
  font-family: var(--f-mono);
  font-size: clamp(10px, 1.1vw, 12px);
  font-weight: 700;
  letter-spacing: 0.3em;
  text-transform: uppercase;
  cursor: pointer;
}

.tstf-bar__menu-rule { display: grid; gap: 4px; width: clamp(26px, 3vw, 38px); }
.tstf-bar__menu-rule i {
  display: block; height: 1px; background: var(--f-silver);
  transition: transform 340ms var(--f-out), background-color 240ms var(--f-ease);
  transform-origin: right center;
}
.tstf-bar__menu:hover .tstf-bar__menu-rule i { background: var(--f-bone); }
.tstf-bar__menu:hover .tstf-bar__menu-rule i:first-child { transform: scaleX(0.62); }
.tstf-bar__menu[aria-expanded="true"] .tstf-bar__menu-rule i:last-child { transform: scaleX(0.4); }

/* --------------------------------------------------------------- the menu */

.tstf-menu-open { overflow: hidden; overscroll-behavior: none; }
.tstf-menu-open body { overflow: hidden; }

.tstf-menu {
  position: fixed;
  inset: 0;
  z-index: 9995;   /* over the bar and the slash, under the portal */
  overflow: hidden;
  background: var(--f-ink);
  opacity: 0;
  transition: opacity 340ms var(--f-ease);
}

.tstf-menu[hidden] { display: none; }
.tstf-menu.is-open { opacity: 1; }

/* Swipe-down to dismiss follows the finger, then springs back or closes. */
.tstf-menu.is-dragging {
  transform: translate3d(0, var(--tstf-drag, 0px), 0);
  transition: none;
}

/* The preview. Two layers crossfading, graded hard into the neutral core —
   the artwork is atmosphere here, not the subject; the type is. */
.tstf-menu__bg { position: absolute; inset: 0; z-index: 0; }

.tstf-menu__img {
  position: absolute;
  inset: 0 0 0 auto;
  width: clamp(300px, 52vw, 900px);
  background-size: cover;
  background-position: 50% 30%;
  filter: grayscale(1) contrast(1.18) brightness(0.82);
  opacity: 0;
  transition: opacity 620ms var(--f-ease);
  -webkit-mask-image: linear-gradient(to left, #000 26%, rgba(0,0,0,0.4) 66%, transparent 98%);
          mask-image: linear-gradient(to left, #000 26%, rgba(0,0,0,0.4) 66%, transparent 98%);
}

.tstf-menu__img.is-on { opacity: 0.66; }

/* A vignette over the previews so the list never has to fight an image. */
.tstf-menu__bg::after {
  content: "";
  position: absolute;
  inset: 0;
  background:
    radial-gradient(70rem 50rem at 6% 96%, color-mix(in oklab, var(--f-slate) 52%, transparent), transparent 66%),
    linear-gradient(to right, var(--f-ink) 4%, color-mix(in oklab, var(--f-ink) 55%, transparent) 52%, transparent 88%);
}

.tstf-menu__inner {
  position: relative;
  z-index: 1;
  height: 100%;
  width: 100%;
  max-width: var(--f-shell-wide);
  margin-inline: auto;
  padding: clamp(14px, 2vw, 26px) clamp(18px, 4vw, 56px) clamp(22px, 4vh, 48px);
  display: grid;
  grid-template-rows: auto 1fr auto;
  gap: clamp(14px, 2.4vh, 30px);
}

.tstf-menu__head { display: flex; align-items: center; justify-content: space-between; gap: 16px; }

.tstf-menu__mark {
  margin: 0;
  font-family: var(--f-mono);
  font-size: clamp(9px, 1vw, 11px);
  font-weight: 700;
  letter-spacing: 0.42em;
  text-transform: uppercase;
  color: var(--f-silver);
}

.tstf-menu__mark span { color: var(--f-bone); }

.tstf-menu__close {
  min-height: 44px;
  padding: 12px 16px;
  border: 1px solid color-mix(in oklab, var(--f-silver) 26%, transparent);
  border-radius: 999px;
  background: none;
  color: var(--f-silver);
  font-family: var(--f-mono);
  font-size: 10px;
  font-weight: 700;
  letter-spacing: 0.22em;
  cursor: pointer;
  transition: color 240ms var(--f-ease), border-color 240ms var(--f-ease);
}

.tstf-menu__close:hover { color: var(--f-bone); border-color: var(--f-silver); }

/* The list has to FIT. Eight rows at a purely width-based size overflowed
   a 900px viewport: the first destination collided with the wordmark and
   the last was jammed against the bottom edge. `min(vw, vh)` makes the
   type answer to the shorter axis, which is what a full-screen menu has to
   do — and `overflow: hidden` is the guard, not the mechanism. */
.tstf-menu__nav { align-self: center; min-height: 0; overflow: hidden; }

.tstf-menu__list { list-style: none; margin: 0; padding: 0; }

.tstf-menu__item a {
  display: grid;
  grid-template-columns: auto minmax(0, 1fr);
  align-items: baseline;
  column-gap: clamp(12px, 2vw, 28px);
  padding: clamp(3px, 0.6vh, 9px) 0;
  text-decoration: none;
  color: var(--f-bone);
}

.tstf-menu__n {
  font-family: var(--f-mono);
  font-size: clamp(9px, 0.9vw, 11px);
  font-weight: 700;
  letter-spacing: 0.16em;
  color: var(--f-stone);
  transition: color 300ms var(--f-ease);
}

.tstf-menu__label {
  font-family: var(--f-head);
  font-size: clamp(26px, min(6.4vw, 6.7vh), 104px);   /* 7.2vh fitted 900px by one pixel */
  line-height: 0.96;
  letter-spacing: -0.026em;
  text-transform: uppercase;
  color: var(--f-bone);
  transition: color 300ms var(--f-ease), transform 420ms var(--f-out), opacity 300ms var(--f-ease);
}

.tstf-menu__note {
  grid-column: 2;
  font-size: clamp(11px, 1.05vw, 13px);
  color: var(--f-faint);
  max-height: 0;
  opacity: 0;
  overflow: hidden;
  transition: max-height 380ms var(--f-out), opacity 300ms var(--f-ease);
}

/* HOVER RESTRUCTURES THE LIST, it does not just recolour a row. The others
   step back and dim; the live one steps forward and reveals its own line.
   That is what makes a menu feel like a place rather than a list of links. */
/* Dimmed by COLOUR only. The first version added `opacity: 0.55` on top of
   --f-stone, compositing the unhovered destinations to about 1.8:1 — they
   are still links, and a menu whose other options are nearly invisible is
   not a focus effect, it is a fault. --f-stone alone measures 4.05:1,
   which clears AA for text at this size. */
.tstf-menu__list.is-focused .tstf-menu__label { color: var(--f-stone); }
.tstf-menu__item.is-on .tstf-menu__label { color: #fff; opacity: 1; transform: translate3d(clamp(6px, 1vw, 18px), 0, 0); }
.tstf-menu__item.is-on .tstf-menu__n { color: var(--f-red); }
.tstf-menu__item.is-on .tstf-menu__note { max-height: 3em; opacity: 1; }

.tstf-menu__item a:focus-visible {
  outline: 2px solid var(--f-bone);
  outline-offset: 6px;
  border-radius: 2px;
}

.tstf-bar__menu:focus-visible,
.tstf-bar__live:focus-visible,
.tstf-bar__mark:focus-visible,
.tstf-menu__close:focus-visible { outline: 2px solid var(--f-bone); outline-offset: 3px; }

/* The live line for the focused destination. Bottom-right, quiet, and it
   is the reason the preview is information rather than wallpaper. */
.tstf-menu__aside { justify-self: end; text-align: right; max-width: 34ch; }

.tstf-menu__pline {
  margin: 0;
  font-size: clamp(12px, 1.2vw, 15px);
  line-height: 1.35;
  color: var(--f-silver);
}

.tstf-menu__pval {
  margin: 6px 0 0;
  font-family: var(--f-head);
  font-size: clamp(18px, 2.4vw, 34px);
  line-height: 1;
  font-variant-numeric: tabular-nums;
  letter-spacing: -0.02em;
  color: var(--f-bone);
}

/* Staggered entrance. Top level, as always. */
.tstf-menu.is-open .tstf-menu__item { animation: tstf-menu-row 620ms var(--f-out) backwards; }
.tstf-menu.is-open .tstf-menu__item:nth-child(1) { animation-delay: 60ms; }
.tstf-menu.is-open .tstf-menu__item:nth-child(2) { animation-delay: 100ms; }
.tstf-menu.is-open .tstf-menu__item:nth-child(3) { animation-delay: 140ms; }
.tstf-menu.is-open .tstf-menu__item:nth-child(4) { animation-delay: 180ms; }
.tstf-menu.is-open .tstf-menu__item:nth-child(5) { animation-delay: 220ms; }
.tstf-menu.is-open .tstf-menu__item:nth-child(6) { animation-delay: 260ms; }
.tstf-menu.is-open .tstf-menu__item:nth-child(7) { animation-delay: 300ms; }
.tstf-menu.is-open .tstf-menu__item:nth-child(8) { animation-delay: 340ms; }

@keyframes tstf-menu-row {
  from { opacity: 0; transform: translate3d(0, 26px, 0); }
}

/* ------------------------------------------------------------------ mobile
   Destinations sit in the lower two thirds where a thumb reaches, rows are
   deliberately tall, and the preview goes behind the type rather than
   beside it. Swipe down anywhere to dismiss. */
@media (max-width: 749px) {
  .tstf-menu__inner {
    grid-template-rows: auto 1fr auto;
    gap: 10px;
    padding-bottom: clamp(26px, 5vh, 54px);
  }

  .tstf-menu__nav { align-self: end; }

  .tstf-menu__item a { padding: 9px 0; min-height: 52px; align-content: center; }
  .tstf-menu__label { font-size: clamp(28px, min(9.4vw, 5.4vh), 46px); }
  /* On a phone there is no hover, so every note is shown: the information
     is the point and there is nothing to reveal it with. */
  .tstf-menu__note { max-height: 3em; opacity: 1; }
  .tstf-menu__list.is-focused .tstf-menu__label { color: var(--f-bone); opacity: 1; }

  .tstf-menu__img { inset: 0; width: 100%;
    -webkit-mask-image: linear-gradient(to bottom, #000 0%, rgba(0,0,0,0.35) 46%, transparent 76%);
            mask-image: linear-gradient(to bottom, #000 0%, rgba(0,0,0,0.35) 46%, transparent 76%);
  }
  .tstf-menu__img.is-on { opacity: 0.36; }
  .tstf-menu__bg::after {
    background: linear-gradient(to bottom, color-mix(in oklab, var(--f-ink) 30%, transparent) 0%, var(--f-ink) 72%);
  }

  .tstf-menu__aside { justify-self: start; text-align: left; }
  .tstf-bar__live-label { display: none; }
}

@media (prefers-reduced-motion: reduce) {
  .tstf-bar,
  .tstf-menu,
  .tstf-menu__img,
  .tstf-menu__label,
  .tstf-menu__note,
  .tstf-bar__menu-rule i,
  .tstf-menu__n { transition-duration: 1ms !important; }
  .tstf-menu.is-open .tstf-menu__item { animation: none !important; }
}

/* =========================================================================
   19. THE SPLIT — Manuel vs Guffz
   See renderTable() in flagship.js for the concept and what it replaced.

   One editorial spread cut by a single diagonal seam at the slash
   transition's own angle. Manuel upper-left, Guffz lower-right, each a
   masthead with their number one's artwork bleeding off their outer edge
   and their picks as a hairline rundown. The hinge on the seam carries the
   week's agreement figure at rest and the comparison when a show is live.

   NOT ONE BOX. Host identity is typographic and material — display face
   and warm light for Manuel, the mono voice and cool light for Guffz —
   never two accent colours. Red appears once in the section, on the
   agreement figure, because that is the one derived fact.
   ========================================================================= */

.tstf-owns-table #tstd-table { display: none !important; }

.tstf-split {
  position: relative;
  overflow: clip;
  isolation: isolate;
  padding-block: clamp(52px, 8vh, 104px) clamp(48px, 7vh, 96px);
}

/* The seam. One hairline, the slash's angle, brightening toward the hinge
   so the eye is taken to the middle of the composition. */
.tstf-split__seam {
  position: absolute;
  left: -14%;
  top: 50%;
  width: 128%;
  height: 1px;
  transform: rotate(-11deg);
  background: linear-gradient(90deg,
    transparent 0%,
    color-mix(in oklab, var(--f-silver) 16%, transparent) 26%,
    color-mix(in oklab, var(--f-silver) 42%, transparent) 50%,
    color-mix(in oklab, var(--f-silver) 16%, transparent) 74%,
    transparent 100%);
  z-index: -1;
  pointer-events: none;
}

.tstf-split__inner {
  width: 100%;
  max-width: var(--f-shell-wide);
  margin-inline: auto;
  padding-inline: clamp(18px, 4vw, 56px);
  display: grid;
  gap: clamp(20px, 3vw, 44px);
}

.tstf-split__head { max-width: 52ch; }

.tstf-split__title {
  margin: 8px 0 0;
  font-family: var(--f-head);
  font-size: clamp(34px, 5.4vw, 86px);
  line-height: 0.9;
  letter-spacing: -0.02em;
  text-transform: uppercase;
  color: var(--f-bone);
}

.tstf-split__sub {
  margin: 12px 0 0;
  font-size: clamp(14px, 1.2vw, 16px);
  color: var(--f-dim);
}

/* --------------------------------------------------------------- the sides
   Diagonal by construction: Manuel's block starts at the top of its
   column, Guffz's is pushed down, so the pair reads across the seam
   instead of as two parallel columns. */
@media (min-width: 900px) {
  .tstf-split__inner {
    grid-template-columns: minmax(0, 1fr) auto minmax(0, 1fr);
    grid-template-areas:
      'head head head'
      'm    hinge g';
    align-items: start;
    column-gap: clamp(24px, 3.4vw, 64px);
  }
  .tstf-split__head { grid-area: head; }
  .tstf-side--m { grid-area: m; }
  .tstf-side--g { grid-area: g; padding-top: clamp(28px, 5vw, 84px); }
  .tstf-hinge   { grid-area: hinge; align-self: center; }
}

.tstf-side { position: relative; min-width: 0; }

/* Their number one, bleeding off their own outer edge. Warm light on
   Manuel, cool on Guffz — the identity, with no second colour introduced. */
/* THE ARTWORK IS BEHIND THE MASTHEAD, NOT BEHIND THE RUNDOWN.
   The first render showed why that distinction matters: at
   brightness(0.82) and spanning the side's full height, the placeholder's
   own lettering was legible straight through the picks and the masked
   block still had a hard clipped edge where the section's `overflow: clip`
   cut it. A cover behind a name is atmosphere; a cover behind a list of
   titles is noise.

   So it is bounded to the masthead band, much darker, and masked on two
   axes so it has no edge at all. */
.tstf-side__art {
  position: absolute;
  top: clamp(-24px, -1.4vw, -8px);
  width: clamp(112px, 14vw, 210px);
  aspect-ratio: 2 / 3;
  max-height: clamp(150px, 19vw, 280px);
  z-index: -1;
  pointer-events: none;
  opacity: 0.72;
}

/* A scrim over the artwork itself, not a filter on it. `brightness()`
   scales the source, so a cover with white lettering or a bright sky stays
   bright relative to everything around it — the placeholders' own words
   were still legible at brightness(0.36). An ink wash on top is
   source-agnostic: whatever the image contains, it sits at the same low
   level, and the outer edge fades out so the clipped bleed has no line. */
.tstf-side__art::after {
  content: "";
  position: absolute;
  inset: 0;
  background: linear-gradient(to bottom,
    color-mix(in oklab, var(--f-ink) 56%, transparent) 0%,
    color-mix(in oklab, var(--f-ink) 78%, transparent) 62%,
    var(--f-ink) 100%);
}

.tstf-side--m .tstf-side__art::after {
  background-image:
    linear-gradient(to left, transparent 30%, color-mix(in oklab, var(--f-ink) 88%, transparent) 88%),
    linear-gradient(to bottom, color-mix(in oklab, var(--f-ink) 54%, transparent) 0%, var(--f-ink) 100%);
}

.tstf-side--g .tstf-side__art::after {
  background-image:
    linear-gradient(to right, transparent 30%, color-mix(in oklab, var(--f-ink) 88%, transparent) 88%),
    linear-gradient(to bottom, color-mix(in oklab, var(--f-ink) 54%, transparent) 0%, var(--f-ink) 100%);
}

.tstf-side__art img {
  width: 100%; height: 100%; object-fit: cover;
  -webkit-mask-image:
    radial-gradient(112% 92% at 50% 38%, #000 30%, transparent 82%),
    linear-gradient(to bottom, #000 42%, transparent 92%);
          mask-image:
    radial-gradient(112% 92% at 50% 38%, #000 30%, transparent 82%),
    linear-gradient(to bottom, #000 42%, transparent 92%);
  -webkit-mask-composite: source-in;
          mask-composite: intersect;
}

/* Warm light on Manuel, cool on Guffz — the identity, and no second
   colour introduced. Both far darker than the first attempt. */
.tstf-side--m .tstf-side__art { right: calc(100% - clamp(96px, 11vw, 176px)); }
.tstf-side--m .tstf-side__art img { filter: grayscale(1) sepia(0.2) contrast(1.3) brightness(0.36); }

.tstf-side--g .tstf-side__art { left: calc(100% - clamp(96px, 11vw, 176px)); }
.tstf-side--g .tstf-side__art img { filter: grayscale(1) contrast(1.36) brightness(0.3); }

.tstf-side__art--none { display: grid; place-items: center; aspect-ratio: 1; }
.tstf-side__art--none svg {
  width: 62%;
  stroke: color-mix(in oklab, var(--f-smoke) 92%, var(--f-stone));
  fill: none;
}

.tstf-side__body { position: relative; }

.tstf-side__role {
  margin: 0;
  font-family: var(--f-mono);
  font-size: 9px;
  font-weight: 700;
  letter-spacing: 0.3em;
  text-transform: uppercase;
  color: var(--f-faint);
}

.tstf-side__name {
  margin: 4px 0 0;
  font-family: var(--f-head);
  font-size: clamp(38px, 6vw, 96px);
  line-height: 0.88;
  letter-spacing: -0.03em;
  text-transform: uppercase;
  color: var(--f-bone);
}

/* Guffz answers in the mono voice, flush right. Same palette, opposite
   treatment — how a magazine separates two bylines without colour-coding
   them like a scoreboard. */
.tstf-side--g .tstf-side__body { text-align: right; }
.tstf-side--g .tstf-side__name {
  font-family: var(--f-mono);
  font-weight: 700;
  font-size: clamp(30px, 4.6vw, 72px);
  letter-spacing: -0.01em;
}

.tstf-side__lead {
  margin: 10px 0 0;
  font-family: var(--f-mono);
  font-size: 10px;
  font-weight: 700;
  letter-spacing: 0.2em;
  text-transform: uppercase;
  color: var(--f-silver);
}
.tstf-side__lead b { color: var(--f-bone); }

.tstf-picks {
  list-style: none;
  margin: clamp(16px, 2.4vw, 30px) 0 0;
  padding: 0;
  border-top: 1px solid var(--f-line);
}

.tstf-pick__btn {
  display: grid;
  grid-template-columns: auto minmax(0, 1fr) auto;
  align-items: baseline;
  gap: clamp(10px, 1.6vw, 22px);
  width: 100%;
  min-height: 48px;
  padding: clamp(11px, 1.5vw, 17px) 0;
  border: 0;
  border-bottom: 1px solid var(--f-line);
  background: none;
  text-align: left;
  cursor: pointer;
  /* Colour and transform only. Nothing here can move the layout, so the
     comparison never shifts the page under the pointer. */
  transition: color 260ms var(--f-ease), transform 380ms var(--f-out), opacity 260ms var(--f-ease);
}

/* Mirrored by ALIGNMENT, not by `direction: rtl`.
   The first version set the row rtl, which put the number on the right and
   the BOTH marker in a far-left column — the marker ended up a hundred
   pixels from the row it belonged to, because the title column was `1fr`
   and stretched between them. Shrink-to-fit columns pushed flush right
   keep the trio together and still read as the mirror of Manuel's side. */
.tstf-side--g .tstf-pick__btn {
  grid-template-columns: auto auto auto;
  justify-content: end;
  text-align: right;
}
.tstf-side--g .tstf-pick__note { grid-column: 1 / -1; text-align: right; }

.tstf-pick__n {
  font-family: var(--f-head);
  font-size: clamp(19px, 2.4vw, 36px);
  line-height: 0.9;
  font-variant-numeric: tabular-nums;
  letter-spacing: -0.02em;
  color: var(--f-stone);
  transition: color 260ms var(--f-ease);
}

.tstf-side--g .tstf-pick__n { font-family: var(--f-mono); font-size: clamp(16px, 2vw, 28px); }

.tstf-pick__t {
  font-size: clamp(15px, 1.5vw, 20px);
  font-weight: 600;
  line-height: 1.22;
  color: var(--f-cream);
  transition: color 260ms var(--f-ease);
}

.tstf-pick__note {
  grid-column: 2;
  font-size: 12px;
  color: var(--f-faint);
}

/* The shared marker. Quiet at rest — the hinge is where the agreement is
   stated; this only says which rows carry it. */
.tstf-pick__mark {
  font-family: var(--f-mono);
  font-size: 8.5px;
  font-weight: 700;
  letter-spacing: 0.2em;
  /* --f-stone measured 4.05:1 here — fine for a rule, under AA for 9px
     bold text. Same trap as the aired rows and the menu labels. */
  color: var(--f-faint);
  border: 1px solid var(--f-line);
  padding: 4px 6px 3px;
  transition: color 260ms var(--f-ease), border-color 260ms var(--f-ease);
}

.tstf-side__short {
  margin: 14px 0 0;
  font-family: var(--f-mono);
  font-size: 9.5px;
  font-weight: 700;
  letter-spacing: 0.2em;
  text-transform: uppercase;
  color: var(--f-faint);
}

/* ------------------------------------------------------- the comparison
   One show live in both rundowns at once, everything else receding. This
   is the section's single interaction and the reason to come back weekly. */
.tstf-split.is-comparing .tstf-pick__btn { color: inherit; opacity: 0.4; }
.tstf-split.is-comparing .tstf-pick.is-on .tstf-pick__btn { opacity: 1; }

.tstf-pick.is-on .tstf-pick__t { color: #fff; }
.tstf-pick.is-on .tstf-pick__n { color: var(--f-bone); }
.tstf-pick.is-on .tstf-pick__mark { color: var(--f-bone); border-color: var(--f-silver); }
.tstf-side--m .tstf-pick.is-on .tstf-pick__btn { transform: translate3d(clamp(4px, 0.8vw, 14px), 0, 0); }
.tstf-side--g .tstf-pick.is-on .tstf-pick__btn { transform: translate3d(clamp(-14px, -0.8vw, -4px), 0, 0); }

.tstf-pick__btn:focus-visible { outline: 2px solid var(--f-bone); outline-offset: 3px; }

/* ----------------------------------------------------------- the hinge */

.tstf-hinge {
  position: relative;
  text-align: center;
  min-width: clamp(132px, 15vw, 210px);
  padding: clamp(14px, 2vw, 24px) clamp(10px, 1.4vw, 18px);
}

.tstf-hinge__k {
  margin: 0;
  font-family: var(--f-mono);
  font-size: 9px;
  font-weight: 700;
  letter-spacing: 0.26em;
  text-transform: uppercase;
  color: var(--f-silver);
  min-height: 1.2em;
}

.tstf-hinge__v {
  margin: 8px 0 0;
  display: flex;
  align-items: baseline;
  justify-content: center;
  gap: 6px;
  font-family: var(--f-head);
  line-height: 0.9;
}

.tstf-hinge__v b {
  font-size: clamp(38px, 5.4vw, 78px);
  font-variant-numeric: tabular-nums;
  letter-spacing: -0.03em;
  /* The section's ONE red moment, on the one derived fact in it — the
     week's agreement figure, at rest. */
  color: var(--f-red);
}

/* While comparing, the two positions are NOT red. The first render put two
   large red numerals side by side, which is both a second accent and a
   third: red is the section's resting signature, and the comparison is a
   reading state, not an alarm. */
.tstf-split.is-comparing .tstf-hinge__v b { color: var(--f-bone); }
.tstf-split.is-comparing .tstf-hinge__v i { color: var(--f-stone); }
.tstf-split.is-comparing .tstf-hinge__k { color: var(--f-bone); }

.tstf-hinge__v i {
  font-style: normal;
  font-family: var(--f-mono);
  font-size: clamp(10px, 1.1vw, 13px);
  font-weight: 700;
  letter-spacing: 0.16em;
  text-transform: uppercase;
  color: var(--f-silver);
}

.tstf-hinge__l {
  margin: 10px 0 0;
  font-family: var(--f-mono);
  font-size: 10px;
  font-weight: 700;
  letter-spacing: 0.14em;
  text-transform: uppercase;
  line-height: 1.5;
  color: var(--f-faint);
  min-height: 3em;
}

/* --------------------------------------------------------------- mobile
   Not two stacked cards. The seam turns horizontal and becomes the hinge
   itself: Manuel above with his artwork bleeding off the top-right, the
   agreement figure on the divide, Guffz below with his off the
   bottom-left. Both rundowns stay on one screen for short lists, so the
   comparison still works by tapping. */
@media (max-width: 899px) {
  .tstf-split { padding-block: clamp(40px, 6vh, 72px) clamp(36px, 6vh, 72px); }
  .tstf-split__seam { transform: rotate(-4deg); top: 52%; }

  .tstf-side__art {
    top: auto;
    width: clamp(104px, 34vw, 168px);
    max-height: clamp(140px, 44vw, 220px);
    opacity: 0.6;
  }
  .tstf-side--m .tstf-side__art { right: -10%; left: auto; bottom: auto; top: -6px; }
  .tstf-side--g .tstf-side__art { left: -10%; right: auto; top: -6px; bottom: auto; }

  .tstf-side--m .tstf-side__body { padding-right: 34%; }
  .tstf-side--g .tstf-side__body { padding-left: 34%; }

  .tstf-side__name { font-size: clamp(32px, 11vw, 52px); }
  .tstf-side--g .tstf-side__name { font-size: clamp(26px, 9vw, 42px); }

  /* Tap targets: the whole row, comfortably over 44px. */
  .tstf-pick__btn { min-height: 56px; padding: 13px 0; }
  .tstf-pick__n { font-size: 22px; }
  .tstf-side--g .tstf-pick__n { font-size: 19px; }
  .tstf-pick__t { font-size: 15.5px; }

  .tstf-hinge {
    text-align: left;
    padding: clamp(18px, 4vw, 30px) 0;
    border-top: 1px solid var(--f-line);
    border-bottom: 1px solid var(--f-line);
    display: grid;
    grid-template-columns: auto minmax(0, 1fr);
    column-gap: 18px;
    align-items: center;
  }
  .tstf-hinge__k { grid-column: 2; text-align: left; }
  .tstf-hinge__v { grid-row: 1 / span 2; grid-column: 1; justify-content: flex-start; }
  .tstf-hinge__v b { font-size: clamp(40px, 13vw, 62px); }
  .tstf-hinge__l { grid-column: 2; margin-top: 4px; min-height: 0; }
}

@media (prefers-reduced-motion: reduce) {
  .tstf-pick__btn,
  .tstf-pick__n,
  .tstf-pick__t,
  .tstf-pick__mark,
  .tstf-hinge__k,
  .tstf-hinge__v b,
  .tstf-hinge__v i,
  .tstf-side__art { transition-duration: 1ms !important; }
  .tstf-side--m .tstf-pick.is-on .tstf-pick__btn,
  .tstf-side--g .tstf-pick.is-on .tstf-pick__btn { transform: none !important; }
}

/* =========================================================================
   20. THE UNIFORM — the store

   The section's own visual mode, and the only place on the site where
   light is the ground rather than the accent: one oversized paper plate
   carrying the garment, on a warm near-black that is deliberately a
   different black from the page's cool graphite.

   THE PAPER IS THE POINT. Every product photograph in this project is
   shot on pure white with the garment occupying exactly the centre half
   of a square frame. Dropped onto a dark page that is a white rectangle,
   and no amount of border styling fixes a white rectangle. So the plate
   is paper (#FAF8F4) and the photograph is composited onto it with
   `mix-blend-mode: multiply`: white multiplied by the paper IS the paper,
   so the image has no edge at all — no seam, no frame, no card. The
   garment sits on the page the way it sits on a press sheet.

   Multiply is also why the white colourway survives. It preserves the
   photograph's own ratios rather than flattening them: the white tee's
   body is 244 against a 255 ground in the source file, and 240 against
   251 on the paper — the same eleven levels of separation the
   photographer shot. A `filter: brightness()` or an overlay scrim would
   have crushed exactly that one colourway into invisibility, which is
   the trap §19 had to be rescued from in the other direction.

   `isolation: isolate` on the plate keeps the blend inside it. Without
   that, multiply would find the near-black page behind the plate and the
   garment would vanish completely.

   NOT A CARD, ANYWHERE. No border, no radius, no shadow on any element
   in this section. Structure is three things: one hairline under the
   rail, inset 1px hairlines between the register's rows and the price
   rows, and the paper plate itself. The register's selection marker is a
   2px bar and a ring on the chip — no fill, no pill, no red.
   ========================================================================= */

.tstf-store {
  --uni-paper: #FAF8F4;
  --uni-ink: #15140F;
  --uni-quiet: #67625A;
  --uni-pad: clamp(18px, 4vw, 56px);

  padding-block: clamp(58px, 8vw, 116px);
  /* A warm black, and a faint spill of light where the plate sits, so the
     paper reads as lit rather than pasted on. */
  background:
    radial-gradient(108% 72% at 56% 48%, rgba(250, 248, 244, 0.055), transparent 66%),
    linear-gradient(180deg, #100F0D 0%, #0B0B09 60%, var(--f-ink) 100%);
}

/* Anchor offset for the menu's STORE destination, same as the other
   flagship-owned sections. `#tstf-table` belongs on this list too and was
   missed in step 8. */
#tstf-shop,
#tstf-table { scroll-margin-top: clamp(56px, 8vh, 96px); }

.tstf-uni {
  width: 100%;
  max-width: var(--f-shell-wide);
  margin-inline: auto;
  padding-inline: var(--uni-pad);
  display: grid;
  gap: clamp(16px, 2.2vw, 30px) clamp(22px, 3.2vw, 54px);
  grid-template-areas: "rail" "mast" "stage" "ctrl" "note";
}

/* --------------------------------------------------------------- the rail */

.tstf-uni__rail {
  grid-area: rail;
  display: flex;
  justify-content: space-between;
  align-items: baseline;
  gap: 16px;
  margin: 0;
  padding-bottom: 12px;
  box-shadow: inset 0 -1px 0 var(--f-line);
  font-family: var(--f-mono);
  font-size: 10.5px;
  font-weight: 700;
  letter-spacing: 0.18em;
  text-transform: uppercase;
  color: var(--f-faint);
}

/* ----------------------------------------------------------- the masthead */

.tstf-uni__mast { grid-area: mast; align-self: end; }

.tstf-uni__title { margin: 0 0 clamp(14px, 1.8vw, 22px); }

.tstf-uni__the {
  display: block;
  margin-bottom: clamp(6px, 0.8vw, 12px);
  font-family: var(--f-mono);
  font-size: 11.5px;
  font-weight: 700;
  letter-spacing: 0.34em;
  text-transform: uppercase;
  color: var(--f-faint);
}

/* Sized on the smaller axis, never on vw alone — the lesson from the
   full-screen menu, where a 900px-tall viewport overflowed. `nowrap`
   because a display word that wraps mid-letter is not a display word. */
.tstf-uni__big {
  display: block;
  font-family: var(--f-head);
  font-size: clamp(42px, min(5.6vw, 10vh), 88px);
  line-height: 0.86;
  letter-spacing: -0.034em;
  text-transform: uppercase;
  color: var(--f-bone);
  white-space: nowrap;
}

.tstf-uni__sub {
  margin: 0 0 14px;
  max-width: 28ch;
  font-size: clamp(14px, 1.1vw, 16.5px);
  line-height: 1.5;
  color: var(--f-silver);
}

.tstf-uni__coll {
  margin: 0;
  font-family: var(--f-mono);
  font-size: 10.5px;
  font-weight: 700;
  letter-spacing: 0.16em;
  text-transform: uppercase;
  color: var(--f-faint);
}

/* ---------------------------------------------------------- the plate */

.tstf-uni__stage { grid-area: stage; position: relative; }

.tstf-uni__plate {
  position: relative;
  display: block;
  width: 100%;
  aspect-ratio: 0.56;
  margin: 0;
  padding: 0;
  border: 0;
  border-radius: 0;
  background: var(--uni-paper);
  color: var(--uni-ink);
  isolation: isolate;
  overflow: hidden;
  -webkit-appearance: none;
  appearance: none;
  text-align: left;
  font: inherit;
}
button.tstf-uni__plate { cursor: pointer; }

.tstf-uni__layers { position: absolute; inset: 0; display: block; }

/* Cover with the container narrower than the square source crops the
   photograph's outer white margins away, so the garment fills the plate
   instead of floating in a field of unused stock. */
.tstf-uni__shot {
  position: absolute;
  inset: 0;
  width: 100%;
  height: 100%;
  object-fit: cover;
  object-position: 50% 50%;
  mix-blend-mode: multiply;
  opacity: 0;
}
.tstf-uni__shot.is-on { opacity: 1; }

/* WHY THE PLATE IS THIS NARROW.
   Every mockup in the set carries a faint grey backdrop band behind the
   garment — the photographer's own soft shadow — about eight levels
   darker than the pure white outside it. Eight levels at 95% luminance
   is invisible as a tone and very visible as an EDGE, and it was drawing
   two vertical lines down a plate that is supposed to have none.

   The band sits at exactly 25%–75% of the square source, because that is
   where the garment is. So the plate is given an aspect ratio of 0.56,
   which makes `cover` show 56% of the source width: the band then runs
   almost the full width of the plate and its edges land ~5% in, where
   the falloff below absorbs them. The garment gets bigger in the bargain
   — 415px of garment on a 350px plate rather than 294px on a 460px one.
   Measured, not guessed: the edge was at 14.1% of the plate at the first
   aspect ratio and is at 5.4% now.

   Cropping vertically as well would have been the tidier fix, but the
   BACK shots are framed flush to the top and bottom of the frame — a
   vertical crop takes the collar and the hem off. */
.tstf-uni__plate::after {
  content: "";
  position: absolute;
  inset: 0;
  z-index: 1;
  pointer-events: none;
  background:
    linear-gradient(90deg,
      rgba(24, 22, 18, 0.036) 0%,
      rgba(24, 22, 18, 0.036) 5.2%,
      rgba(24, 22, 18, 0.012) 9%,
      rgba(24, 22, 18, 0) 15%,
      rgba(24, 22, 18, 0) 85%,
      rgba(24, 22, 18, 0.012) 91%,
      rgba(24, 22, 18, 0.036) 94.8%,
      rgba(24, 22, 18, 0.036) 100%);
}

.tstf-uni__nophoto {
  position: absolute;
  z-index: 2;
  left: clamp(14px, 1.6vw, 22px);
  right: clamp(14px, 1.6vw, 22px);
  top: 50%;
  transform: translateY(-50%);
  display: none;
  font-family: var(--f-mono);
  font-size: 11px;
  font-weight: 700;
  letter-spacing: 0.16em;
  text-transform: uppercase;
  line-height: 1.6;
  color: var(--uni-quiet);
}
.tstf-store.is-nophoto .tstf-uni__nophoto { display: block; }

.tstf-uni__side {
  position: absolute;
  left: clamp(14px, 1.6vw, 22px);
  bottom: clamp(12px, 1.4vw, 18px);
  z-index: 2;
  display: flex;
  align-items: baseline;
  gap: 9px;
  font-family: var(--f-mono);
  font-size: 10px;
  font-weight: 700;
  letter-spacing: 0.2em;
  text-transform: uppercase;
}
.tstf-uni__side-v { color: var(--uni-quiet); }
.tstf-uni__side-sep { color: var(--uni-quiet); }
.tstf-uni__side-v.is-on { color: var(--uni-ink); }
/* The side that is NOT showing is underlined, so the plate reads as a
   switch on touch, where there is no hover to discover it with. */
.tstf-uni__side-v:not(.is-on) {
  text-decoration: underline;
  text-decoration-thickness: 1px;
  text-underline-offset: 3px;
}

.tstf-uni__credit {
  grid-area: note;
  align-self: end;
  margin: 12px 0 0;
  font-family: var(--f-mono);
  font-size: 10px;
  font-weight: 700;
  letter-spacing: 0.16em;
  text-transform: uppercase;
  color: var(--f-faint);
}

/* ------------------------------------------------------ the control column */

.tstf-uni__ctrl {
  grid-area: ctrl;
  display: flex;
  flex-direction: column;
  gap: clamp(16px, 2vw, 24px);
}
.tstf-uni__pick,
.tstf-uni__buy { display: block; }
.tstf-uni__buy .tstf-uni__note { margin-top: 14px; }

.tstf-uni__state {
  display: flex;
  flex-direction: column;
  gap: 5px;
  margin: 0;
  /* Reserved so a longer colour name can never move the register. */
  min-height: 46px;
}
.tstf-uni__garment {
  font-family: var(--f-mono);
  font-size: 10.5px;
  font-weight: 700;
  letter-spacing: 0.18em;
  text-transform: uppercase;
  color: var(--f-faint);
}
.tstf-uni__way {
  font-family: var(--f-head);
  font-size: clamp(20px, 1.7vw, 26px);
  line-height: 1;
  letter-spacing: -0.012em;
  text-transform: uppercase;
  color: var(--f-bone);
}

/* -------------------------------------------------------- the register
   THE ACCESSIBILITY FIX. The Hub's swatches were 33px tall — under the
   44px floor, flagged in every QA run since step 2. The answer is not a
   bigger dot: a 44px circle of flat colour is a clumsy object. The row
   is the target (48px tall, the full width of the column) and the swatch
   stays an 18px chip inside it, which is both a larger hit area than a
   44px button and a more restrained mark than the original. */

.tstf-uni__reg { display: grid; grid-template-columns: repeat(auto-fit, minmax(134px, 1fr)); }

.tstf-sw {
  position: relative;
  display: grid;
  grid-template-columns: 18px minmax(0, 1fr) auto;
  align-items: center;
  gap: 12px;
  min-height: 48px;
  margin: 0;
  padding: 8px 4px 8px 14px;
  border: 0;
  border-radius: 0;
  background: none;
  box-shadow: inset 0 1px 0 var(--f-line);
  font: inherit;
  text-align: left;
  cursor: pointer;
}
.tstf-sw::before {
  content: "";
  position: absolute;
  left: 0;
  top: 10px;
  bottom: 10px;
  width: 2px;
  background: transparent;
}
.tstf-sw.is-on::before { background: var(--f-bone); }

.tstf-sw__chip {
  width: 18px;
  height: 18px;
  background: var(--c, var(--f-slate));
  box-shadow: 0 0 0 1px rgba(244, 241, 234, 0.24);
}
.tstf-sw.is-on .tstf-sw__chip {
  box-shadow: 0 0 0 1px var(--f-bone), 0 0 0 4px rgba(239, 234, 225, 0.09);
}

/* Dimmed with a stated colour, never opacity — the rule this project
   learned four times over. */
.tstf-sw__name {
  font-family: var(--f-mono);
  font-size: 11.5px;
  font-weight: 700;
  letter-spacing: 0.14em;
  text-transform: uppercase;
  color: var(--f-faint);
}
.tstf-sw.is-on .tstf-sw__name { color: var(--f-bone); }
.tstf-sw:hover .tstf-sw__name { color: var(--f-silver); }

.tstf-sw__n {
  font-family: var(--f-mono);
  font-size: 9.5px;
  letter-spacing: 0.1em;
  color: var(--f-faint);
}

/* ------------------------------------------------------------- the terms */

.tstf-uni__tiers { list-style: none; margin: 0; padding: 0; }

.tstf-uni__tier {
  display: grid;
  grid-template-columns: minmax(0, 1fr) auto auto;
  grid-template-rows: auto auto;
  align-items: center;
  column-gap: 14px;
  min-height: 62px;
  padding: 11px 2px;
  box-shadow: inset 0 1px 0 var(--f-line);
  color: var(--f-cream);
  text-decoration: none;
}
.tstf-uni__qty {
  grid-area: 1 / 1 / 2 / 2;
  align-self: end;
  font-family: var(--f-mono);
  font-size: 11px;
  font-weight: 700;
  letter-spacing: 0.16em;
  text-transform: uppercase;
  color: var(--f-silver);
}
/* Always rendered, empty or not, so both rows measure the same and the
   prices stay on one optical line. */
.tstf-uni__save {
  grid-area: 2 / 1 / 3 / 2;
  align-self: start;
  min-height: 14px;
  font-family: var(--f-mono);
  font-size: 10px;
  font-weight: 700;
  letter-spacing: 0.14em;
  text-transform: uppercase;
  color: var(--f-faint);
}
.tstf-uni__price {
  grid-area: 1 / 2 / 3 / 3;
  align-self: center;
  font-family: var(--f-head);
  font-size: clamp(26px, 2.1vw, 38px);
  line-height: 1;
  letter-spacing: -0.022em;
  color: var(--f-bone);
}
.tstf-uni__go {
  grid-area: 1 / 3 / 3 / 4;
  align-self: center;
  font-family: var(--f-mono);
  font-size: 15px;
  color: var(--f-faint);
}
.tstf-uni__tier:hover { background: rgba(239, 234, 225, 0.035); }
.tstf-uni__tier:hover .tstf-uni__go,
.tstf-uni__tier:focus-visible .tstf-uni__go { color: var(--f-red); }

.tstf-uni__soon {
  margin: 0;
  padding-top: 14px;
  box-shadow: inset 0 1px 0 var(--f-line);
  font-family: var(--f-mono);
  font-size: 11px;
  font-weight: 700;
  letter-spacing: 0.18em;
  text-transform: uppercase;
  color: var(--f-silver);
}

.tstf-uni__note {
  margin: 0;
  max-width: 34ch;
  font-size: 12.5px;
  line-height: 1.5;
  color: var(--f-faint);
}
.tstf-uni__note--test { color: var(--f-gold); }
.tstf-uni__note + .tstf-uni__note { margin-top: 6px; }

/* ------------------------------------------------------------ focus rings
   Stated here rather than inherited: the §9 floor targets `.tstf-section`,
   and this section is a `.tstd-section` in the Hub's slot. */

.tstf-store .tstf-uni__plate:focus-visible,
.tstf-store .tstf-sw:focus-visible,
.tstf-store .tstf-uni__tier:focus-visible {
  outline: 2px solid var(--f-bone);
  outline-offset: 3px;
}

/* ----------------------------------------------------------------- motion */

@media (prefers-reduced-motion: no-preference) {
  .tstf-uni__shot { transition: opacity 200ms linear; }
  .tstf-uni__plate { transition: transform 190ms var(--f-out); }
  /* A squeeze, not a 3D flip. These are flat photographs of a garment,
     not a rotatable object, and rotateY on a photograph is a lie the eye
     catches immediately. */
  .tstf-uni__plate.is-turning { transform: scaleX(0.962); }
  .tstf-sw__name,
  .tstf-uni__go,
  .tstf-uni__tier { transition: color 180ms linear, background-color 180ms linear, transform 190ms var(--f-out); }
  .tstf-uni__tier:hover .tstf-uni__go,
  .tstf-uni__tier:focus-visible .tstf-uni__go { transform: translateX(4px); }
}

@media (prefers-reduced-motion: reduce) {
  .tstf-uni__shot,
  .tstf-uni__plate,
  .tstf-uni__go { transition: none; }
  .tstf-uni__plate.is-turning { transform: none; }
}

/* ---------------------------------------------------------------- layout
   Three compositions, not one that stretches. The middle one exists
   because at 900–1040px the three-column spread squeezed the masthead
   column to 254px and the display word — correctly set `nowrap` — ran
   straight out of it. Measured, not guessed: `scrollWidth > clientWidth`
   on `.tstf-uni__big` at exactly 900px. */

/* TABLET: the masthead goes full width above a two-column body. The plate
   stops bleeding here, because a 1,000px-wide photograph of a t-shirt is
   not a composition, it is a wall. */
@media (min-width: 700px) and (max-width: 1039.98px) {
  .tstf-uni {
    grid-template-columns: minmax(240px, 0.86fr) minmax(240px, 1fr);
    grid-template-areas:
      "rail  rail"
      "mast  mast"
      "stage ctrl"
      "note  note";
  }
  .tstf-uni__plate { max-width: 400px; }
  .tstf-uni__mast { align-self: start; }
  .tstf-uni__sub { max-width: 46ch; }
  /* The plate is taller than the controls here, so they spread to the
     plate's own bottom edge rather than leaving one void under them. */
  .tstf-uni__ctrl { justify-content: space-between; gap: clamp(20px, 2.6vw, 34px); }
}

/* DESKTOP: a three-column spread on three rows, so both outer columns are
   anchored top AND bottom — masthead above the delivery terms on the
   left, the colour register above the prices on the right — while the
   plate spans the full height between them. Type at the top left, price
   at the bottom right: the composition reads on the diagonal rather than
   as three columns of equal weight, and neither side leaves the large
   empty rectangle the first cut of this section had. */
@media (min-width: 1040px) {
  .tstf-uni {
    grid-template-columns: minmax(0, 1.04fr) minmax(258px, 0.66fr) minmax(220px, 0.6fr);
    grid-template-rows: auto 1fr auto;
    grid-template-areas:
      "rail  rail  rail"
      "mast  stage ctrl"
      "note  stage ctrl";
    align-items: stretch;
  }
  .tstf-uni__rail { margin-bottom: clamp(18px, 2.4vw, 34px); }
  .tstf-uni__mast { align-self: start; }
  .tstf-uni__ctrl { justify-content: space-between; gap: 24px; }
  /* One column, full width of the register: a colour index, not a table of
     swatches. Two columns put a selection bar in the middle of the list
     where it read as a cell divider. */
  .tstf-uni__reg { grid-template-columns: 1fr; }
  .tstf-sw { min-height: 56px; }
  .tstf-uni__credit { margin: 0; }
}

@media (min-width: 1200px) {
  /* The display word runs into the gutter beside the plate rather than
     stopping politely inside its column. */
  .tstf-uni__mast { margin-right: clamp(-46px, -2.4vw, -12px); }
  .tstf-uni__stage { z-index: 1; }
}

/* -------------------------------------------------------------- mobile
   Its own composition, not the desktop stack: the masthead, then the
   garment full-bleed and tall — the plate is deliberately narrower than
   its own aspect so the crop zooms past the mockup's backdrop band and
   the garment fills the width — then a thumb-height list of controls. */

@media (max-width: 699.98px) {
  .tstf-uni__stage { margin-inline: calc(-1 * var(--uni-pad)); }
  .tstf-uni__mast { align-self: start; }
  .tstf-uni__sub { max-width: 34ch; }
  .tstf-sw { min-height: 52px; }
  .tstf-uni__tier { min-height: 66px; }
  .tstf-uni__state { min-height: 0; }
  .tstf-uni__credit { margin-top: 4px; }
}

@media (max-width: 520px) {
  .tstf-uni__big { font-size: clamp(40px, 12.4vw, 60px); }
}

/* The Hub's own store section is now the flagship's. Hidden with a class
   on <html>, never the `hidden` attribute — `mount()` clears that on
   every render. The class is added at parse time, before the Hub renders
   into the slot, so the Hub's five product photographs never enter a
   viewport and never load; see the note in flagship.js. */
.tstf-owns-shop #shop { display: none !important; }

/* =========================================================================
   21. THE STUDIO SECTIONS — #featured, #latest, #follow
   ...and the news panel that has no photograph.

   These were the last three Hub blocks on the page, and they were the same
   object three times: a rounded bordered card with a thumbnail in it,
   instanced once, four times and three times. Everything here is built
   from the tokens and the rules already in this file — no new palette, no
   new family, no new motion idea. What changes is that hierarchy comes
   from TREATMENT rather than from repeating one card at different sizes:
   a bleeding 16:9 still, a numbered filmstrip, a handle at display scale.

   WHY EVERY VIDEO FRAME IS 16:9. YouTube guarantees exactly one thumbnail
   size, `hqdefault.jpg` — 480x360, 4:3, with the 16:9 frame letterboxed
   inside it. A 16:9 frame with `object-fit: cover` crops those bars
   precisely (480/360 scaled to width shows source rows 45-315, which is
   the content band to the pixel) and never invents pixels vertically. The
   Hub put that 4:3 source inside a 9:16 card, which threw away most of the
   frame and zoomed what was left.

   AND WHY THE STILL IS NOT FULL-BLEED. 480px cannot become 1440px. The
   featured still is displayed at roughly 700-760px — under 1.6x — and
   bleeds off the shell's right edge rather than stretching the width of
   the page. `featuredVideo.thumbnail` in config.js overrides it with a
   real asset whenever one exists, and then it can go wider.
   ========================================================================= */

#tstf-featured,
#tstf-latest,
#tstf-follow { scroll-margin-top: clamp(56px, 8vh, 96px); }

/* ---------------------------------------------- 21a. #featured — NOW SHOWING */

.tstf-showcase {
  padding-block: clamp(54px, 7vw, 104px);
  /* A lift in the middle of the section so the still sits in light rather
     than on a flat field. Same ink family, no new colour. */
  background:
    radial-gradient(120% 86% at 72% 50%, rgba(239, 234, 225, 0.045), transparent 66%),
    linear-gradient(180deg, var(--f-ink) 0%, #0D0D0B 52%, var(--f-ink) 100%);
  overflow: clip;
}

.tstf-show {
  width: 100%;
  max-width: var(--f-shell-wide);
  margin-inline: auto;
  padding-inline: var(--f-pad);
  display: grid;
  gap: clamp(20px, 2.6vw, 40px);
  grid-template-areas: "mast" "stage" "act";
}

.tstf-show__mast { grid-area: mast; }
.tstf-show__stage { grid-area: stage; }
.tstf-show__act { grid-area: act; }

.tstf-show__label {
  margin: 0 0 clamp(10px, 1.4vw, 16px);
  font-family: var(--f-mono);
  font-size: 10.5px;
  font-weight: 700;
  letter-spacing: 0.18em;
  text-transform: uppercase;
  color: var(--f-faint);
}

.tstf-show__title {
  margin: 0;
  font-family: var(--f-head);
  font-size: clamp(32px, min(4.4vw, 7.6vh), 70px);
  line-height: 0.92;
  letter-spacing: -0.03em;
  text-transform: uppercase;
  color: var(--f-bone);
}
/* Treatment, not size: the lead-in is quiet and the noun is loud, in one
   face at one size. */
.tstf-show__lead { color: var(--f-silver); }
.tstf-show__tail { color: var(--f-bone); }

.tstf-show__name {
  margin: 0 0 8px;
  font-family: var(--f-head);
  font-size: clamp(17px, 1.5vw, 22px);
  text-transform: uppercase;
  letter-spacing: -0.01em;
  color: var(--f-bone);
}
.tstf-show__dek {
  margin: 0 0 18px;
  max-width: 40ch;
  font-size: clamp(14px, 1.1vw, 16.5px);
  line-height: 1.55;
  color: var(--f-silver);
}

.tstf-show__act .tstf-show__name { margin-top: 0; }
.tstf-show__cta {
  display: inline-flex;
  align-items: center;
  min-height: 44px;
  font-family: var(--f-mono);
  font-size: 11.5px;
  font-weight: 700;
  letter-spacing: 0.16em;
  text-transform: uppercase;
  color: var(--f-bone);
  text-decoration: none;
  box-shadow: inset 0 -1px 0 var(--f-slate);
}
.tstf-show__cta:hover,
.tstf-show__cta:focus-visible { box-shadow: inset 0 -1px 0 var(--f-red); }

/* ----- the still */

.tstf-show__stage { position: relative; }

.tstf-show__frame {
  position: relative;
  display: block;
  aspect-ratio: 16 / 9;
  background: var(--f-graphite);
  isolation: isolate;
  overflow: hidden;
  text-decoration: none;
  color: var(--f-bone);
}

.tstf-show__art { position: absolute; inset: 0; display: block; }
.tstf-show__art img {
  width: 100%; height: 100%;
  object-fit: cover;
  object-position: 50% 50%;
  /* The one motion moment in this section. Transform only. */
  transition: scale 0.9s var(--f-out);
}
.tstf-show__art iframe {
  position: absolute; inset: 0;
  width: 100%; height: 100%;
  border: 0;
}
.tstf-show__frame:hover .tstf-show__art img { scale: 1.035; }

/* Wash for the cue, stated as a gradient over the image rather than a
   filter on it — the source-agnostic rule from §19. */
.tstf-show__frame::after {
  content: "";
  position: absolute; inset: 0; z-index: 1;
  pointer-events: none;
  background: linear-gradient(0deg,
    rgba(10, 10, 9, 0.74) 0%, rgba(10, 10, 9, 0.24) 34%, transparent 58%);
}

.tstf-show__cue {
  position: absolute;
  left: clamp(14px, 1.8vw, 26px);
  bottom: clamp(12px, 1.6vw, 22px);
  z-index: 2;
  display: flex;
  align-items: center;
  gap: 12px;
}
/* The site's own kunai, turned to point into the frame. Not a red YouTube
   disc — that was the fourth and fifth red thing on this page. */
.tstf-show__kunai {
  /* The symbol's viewBox is 100x400. Forcing it into a square flattened
     the blade into a needle; sized on its own proportions and turned, it
     reads as a blade pointing into the frame. */
  width: 12px; height: 40px;
  fill: var(--f-bone);
  transform: rotate(90deg);
  transition: transform 0.28s var(--f-out);
}
.tstf-show__cue i {
  font-family: var(--f-mono);
  font-size: 11px;
  font-weight: 700;
  font-style: normal;
  letter-spacing: 0.2em;
  text-transform: uppercase;
  color: var(--f-bone);
}
.tstf-show__frame:hover .tstf-show__kunai { transform: rotate(90deg) translateY(-5px); }

/* Once the embed is in, the poster furniture has no business being there. */
.tstf-show__frame.is-playing::after,
.tstf-show__frame.is-playing .tstf-show__cue { display: none; }

/* MOBILE: the still bleeds to both edges. A 16:9 frame inside the side
   gutters is a thumbnail; edge to edge it is a screen. */
@media (max-width: 979.98px) {
  .tstf-show__stage { margin-inline: calc(-1 * var(--f-pad)); }
}

@media (min-width: 980px) {
  .tstf-show {
    grid-template-columns: minmax(270px, 0.86fr) minmax(0, 1.14fr);
    grid-template-rows: auto auto;
    grid-template-areas:
      "mast stage"
      "act  stage";
    align-content: center;
    row-gap: clamp(18px, 2.2vw, 30px);
  }
  .tstf-show__mast { align-self: end; }
  .tstf-show__act { align-self: start; }
  /* Off the shell's edge. At the shell width that is the viewport edge. */
  .tstf-show__stage { margin-right: calc(-1 * var(--f-pad)); align-self: center; }
}

/* ------------------------------------------------- 21b. #latest — THE REEL */

.tstf-reel {
  padding-block: clamp(50px, 6.6vw, 96px);
  background: linear-gradient(180deg, var(--f-ink) 0%, #0C0C0B 58%, var(--f-ink) 100%);
  overflow: clip;
}

.tstf-reel__in {
  width: 100%;
  max-width: var(--f-shell-wide);
  margin-inline: auto;
  padding-inline: var(--f-pad);
}

/* The platform is named ONCE, here. Four "YOUTUBE" chips over four
   thumbnails was four duplicated labels and four more red marks. */
.tstf-reel__rail {
  display: flex;
  justify-content: space-between;
  align-items: baseline;
  gap: 16px;
  margin: 0;
  padding-bottom: 12px;
  box-shadow: inset 0 -1px 0 var(--f-line);
  font-family: var(--f-mono);
  font-size: 10.5px;
  font-weight: 700;
  letter-spacing: 0.18em;
  text-transform: uppercase;
  color: var(--f-faint);
}

.tstf-reel__mast {
  display: grid;
  gap: 14px;
  margin: clamp(18px, 2.4vw, 32px) 0 clamp(20px, 2.6vw, 34px);
}
.tstf-reel__title {
  margin: 0 0 8px;
  font-family: var(--f-head);
  font-size: clamp(27px, min(3.9vw, 6.8vh), 60px);
  line-height: 0.94;
  letter-spacing: -0.028em;
  text-transform: uppercase;
  color: var(--f-bone);
}
.tstf-reel__sub {
  margin: 0;
  max-width: 44ch;
  font-size: clamp(13.5px, 1.05vw, 16px);
  line-height: 1.55;
  color: var(--f-silver);
}

.tstf-reel__nav { display: flex; gap: 8px; }
.tstf-reel__arrow {
  min-width: 44px; min-height: 44px;
  display: inline-flex; align-items: center; justify-content: center;
  margin: 0; padding: 0;
  border: 0; border-radius: 0;
  background: none;
  font-family: var(--f-mono);
  font-size: 17px;
  color: var(--f-silver);
  cursor: pointer;
  box-shadow: inset 0 0 0 1px var(--f-slate);
}
.tstf-reel__arrow:hover:not(:disabled) { color: var(--f-bone); box-shadow: inset 0 0 0 1px var(--f-silver); }
/* WCAG exempts inactive controls from the contrast floor, but the first
   measured state here was #3A3D43 at 1.82:1 — near invisible, which on a
   phone (where the nav IS shown and the leading arrow starts disabled)
   reads as a rendering fault rather than as a disabled button. Both
   states are now above the floor with no exemption needed, and they are
   told apart by BRIGHTNESS and by the ring rather than by one of them
   being unreadable: silver 8.69:1 resting, faint 6.02:1 disabled. */
.tstf-reel__arrow:disabled {
  color: var(--f-faint);
  box-shadow: inset 0 0 0 1px #2A2D33;
  cursor: default;
}

/* ----- the filmstrip */

.tstf-reel__strip {
  list-style: none;
  margin: 0;
  /* Bleeds off the shell on the right, and keeps a gutter of padding so
     the last clip can still scroll fully into view. */
  margin-right: calc(-1 * var(--f-pad));
  padding: 0 var(--f-pad) 6px 0;
  display: flex;
  /* Bottom-aligned. The stills share one baseline while the index
     numerals stagger above them at four different heights, because the
     clips are different widths and every frame is 16:9. Top-aligning them
     left the lead hanging 90px below the other three, which reads as a
     layout fault rather than as a decision. */
  align-items: flex-end;
  gap: 18px;
  overflow-x: auto;
  overscroll-behavior-x: contain;
  scroll-snap-type: x mandatory;
  scrollbar-width: none;
}
.tstf-reel__strip::-webkit-scrollbar { display: none; }

.tstf-clip {
  scroll-snap-align: start;
  flex: 0 0 min(74vw, 300px);
  /* Two lines of .tstf-clip__t, reserved. Stated here rather than inlined
     below so the clamp and the track that holds it can never drift apart. */
  --f-clip-t: calc(13.5px * 1.45 * 2);
}
.tstf-clip__a {
  display: grid;
  /* The title track is a FIXED two lines in every clip, not `auto`.
     `align-items: flex-end` on the strip aligns clip BOTTOMS, so the moment
     the rail carried real titles — one wrapping to three lines, another to
     one — the stills stopped sharing a baseline and the strip read as a
     layout fault. A stated track keeps the variation inside the text block
     where it belongs and leaves the frames level. It is also reserved when
     a clip has no title at all, so one untitled item cannot drop its own
     still 39px below the rest. */
  grid-template-rows: auto auto var(--f-clip-t);
  gap: 9px;
  text-decoration: none;
  color: inherit;
}
.tstf-clip__n {
  font-family: var(--f-mono);
  font-size: 10px;
  font-weight: 700;
  letter-spacing: 0.16em;
  color: var(--f-faint);
}
.tstf-clip__art {
  position: relative;
  display: block;
  aspect-ratio: 16 / 9;
  background: var(--f-graphite);
  isolation: isolate;
  overflow: hidden;
}
.tstf-clip__art img {
  width: 100%; height: 100%;
  object-fit: cover;
  transition: scale 0.8s var(--f-out);
}
.tstf-clip__art::after {
  content: "";
  position: absolute; inset: 0;
  background: linear-gradient(0deg, rgba(10, 10, 9, 0.46) 0%, transparent 46%);
}
.tstf-clip__t {
  font-size: 13.5px;
  line-height: 1.45;
  color: var(--f-silver);
  /* Two lines maximum. The DOM keeps the whole title, so the link's
     accessible name is still the full one; only the painted text is cut.
     `height` rather than `min-height` because several of these titles
     carry emoji, whose line box can measure taller than 1.45em — a floor
     would let one clip grow and take the baseline with it. */
  display: -webkit-box;
  -webkit-box-orient: vertical;
  -webkit-line-clamp: 2;
  height: var(--f-clip-t);
  overflow: hidden;
}
.tstf-clip__a:hover .tstf-clip__art img { scale: 1.045; }
.tstf-clip__a:hover .tstf-clip__n { color: var(--f-bone); }

@media (min-width: 900px) {
  .tstf-reel__mast { grid-template-columns: minmax(0, 1fr) auto; align-items: end; }
  /* Four equal stills in a row is a grid with the borders taken off. The
     first clip is the lead and takes half again the width of the others —
     rhythm from treatment, the same rule as everything else here. */
  .tstf-reel__strip > .tstf-clip { flex: 1 1 0; min-width: 0; }
  .tstf-reel__strip > .tstf-clip:first-child { flex-grow: 1.6; }
}

/* ----------------------------------------------- 21c. #follow — ONE HANDLE */

.tstf-join {
  position: relative;
  padding-block: clamp(56px, 7.6vw, 116px);
  background: linear-gradient(180deg, var(--f-ink) 0%, #0E0E0C 44%, #090908 100%);
  overflow: clip;
}

.tstf-join__in {
  position: relative;
  width: 100%;
  max-width: var(--f-shell-wide);
  margin-inline: auto;
  padding-inline: var(--f-pad);
}

.tstf-join__label {
  margin: 0 0 clamp(12px, 1.6vw, 20px);
  font-family: var(--f-mono);
  font-size: 10.5px;
  font-weight: 700;
  letter-spacing: 0.18em;
  text-transform: uppercase;
  color: var(--f-faint);
}

/* THE SECTION IS THE HANDLE. All three profiles are @theshonentable; the
   Hub printed it three times in 12px grey, once under each platform card,
   as though they were three different addresses. Said once, at this size,
   it is the thing worth remembering — and it is the page's sign-off.
   Sized on the smaller axis and NOT set `nowrap`: fifteen characters
   cannot be made to fit 320px at any legible size, so it is allowed to
   break rather than to clip. */
.tstf-join__handle {
  margin: 0;
  font-family: var(--f-head);
  font-size: clamp(30px, min(7.4vw, 11vh), 104px);
  line-height: 0.9;
  letter-spacing: -0.042em;
  color: var(--f-bone);
  overflow-wrap: anywhere;
}
.tstf-join__handle--multi { text-transform: uppercase; }

.tstf-join__line {
  margin: clamp(12px, 1.6vw, 20px) 0 0;
  font-family: var(--f-mono);
  font-size: 11px;
  font-weight: 700;
  letter-spacing: 0.18em;
  text-transform: uppercase;
  color: var(--f-silver);
}
.tstf-join__sub {
  margin: 12px 0 0;
  max-width: 40ch;
  font-size: clamp(14px, 1.1vw, 16.5px);
  line-height: 1.55;
  color: var(--f-silver);
}

.tstf-join__row {
  list-style: none;
  margin: clamp(28px, 3.6vw, 54px) 0 0;
  padding: 0;
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(190px, 1fr));
  /* The cells need real air between them: with no gap, each row's trailing
     mark sat immediately in front of the NEXT row's index numeral and read
     as belonging to it. */
  column-gap: clamp(22px, 3vw, 46px);
  max-width: 820px;
}
/* `color` is stated on the anchor on purpose. An anchor with no colour
   set hands `currentColor` to the UA's default link blue, and anything
   inside it that inherits — an SVG stroke, a glyph — renders bright blue
   on a near-black page. That has now happened twice in this project. */
.tstf-join__a {
  display: grid;
  grid-template-columns: auto minmax(0, 1fr) auto;
  align-items: center;
  gap: 14px;
  min-height: 72px;
  padding: 18px 6px 18px 0;
  color: var(--f-silver);
  text-decoration: none;
  box-shadow: inset 0 1px 0 var(--f-line);
}
.tstf-join__n {
  font-family: var(--f-mono);
  font-size: 9.5px;
  letter-spacing: 0.1em;
  color: var(--f-faint);
}
.tstf-join__k {
  font-family: var(--f-head);
  font-size: clamp(16px, 1.4vw, 21px);
  letter-spacing: -0.01em;
  text-transform: uppercase;
  color: var(--f-bone);
}
.tstf-join__h {
  font-family: var(--f-mono);
  font-size: 10.5px;
  letter-spacing: 0.12em;
  color: var(--f-faint);
}
.tstf-join__go {
  font-family: var(--f-mono);
  font-size: 14px;
  color: var(--f-faint);
  transition: transform 0.24s var(--f-out), color 0.2s linear;
}
.tstf-join__a:hover .tstf-join__go,
.tstf-join__a:focus-visible .tstf-join__go {
  color: var(--f-bone);
  transform: translate(3px, -3px);
}
.tstf-join__a:hover { box-shadow: inset 0 1px 0 var(--f-silver); }

/* A large `i-table` mark was tried here as a sign-off and removed: the
   symbol carries its own light fills, so overriding `fill` recoloured
   part of it and left two bright diagonals floating in the corner. A
   decoration that cannot be controlled is not worth the corner. */

/* --------------------------------------------------------- 21d. THE SLATE
   One designed state for "there is no picture", used by both video
   sections. A frame with a fixed aspect ratio is structural — it cannot
   simply be dropped the way a news panel's can — so it becomes a title
   card and the type becomes the image. */

.tstf-slate {
  position: absolute;
  inset: 0;
  display: flex;
  flex-direction: column;
  align-items: flex-start;
  justify-content: flex-end;
  gap: 6px;
  padding: clamp(12px, 1.6vw, 22px);
  background: linear-gradient(152deg, #1B1B17 0%, #131311 56%, #0E0E0C 100%);
  overflow: hidden;
}
.tstf-slate__word {
  font-family: var(--f-head);
  font-size: clamp(20px, 5vw, 60px);
  line-height: 0.86;
  letter-spacing: -0.035em;
  text-transform: uppercase;
  color: #423F38;          /* ~1.9:1 — a ghost wordmark, decorative, aria-hidden */
  overflow-wrap: anywhere;
}
.tstf-slate__note {
  /* Bottom-RIGHT, because the frame's own WATCH cue is bottom-left and
     the two were printing over each other. */
  align-self: flex-end;
  font-family: var(--f-mono);
  font-size: 9.5px;
  font-weight: 700;
  letter-spacing: 0.18em;
  text-transform: uppercase;
  color: var(--f-faint);
}
/* The river's thumb is 56px; a word in it would be illegible, so there it
   is the plate alone.

   `position: relative` on the host is LOAD-BEARING, not tidiness. The
   slate is `position: absolute; inset: 0`, and `.tstf-river__art` had no
   positioning of its own, so all three river slates resolved against the
   nearest positioned ancestor — the section itself — and each one painted
   a full-section black rectangle over the entire wire. The DOM and every
   computed style looked perfect; the section was simply covered. Found
   with `elementFromPoint`, not by reading the CSS. */
.tstf-river__art { position: relative; }
.tstf-river__art .tstf-slate { padding: 0; }
.tstf-river__art .tstf-slate__word { display: none; }

/* ------------------------------ 21e. THE NEWS PANEL WITH NO PHOTOGRAPH
   The weakest thing on the page before this phase: a hatched diagonal
   texture with a 7%-opacity shuriken in it, filling 50-70% of every panel
   that had no image — which, with this wire, is most of them.

   A publication does not put a placeholder where the picture would have
   been; it sets the story bigger. So the frame is gone, the ground goes
   flat, one hairline carries the edge, and the headline takes the space
   the empty box was holding. The standfirst — previously shown on the lead
   only — appears on every panel that has no picture, because there is room
   for it and because it is the only other thing the story has. */

.tstf-panel--noart {
  background: linear-gradient(168deg, #161613 0%, #111110 62%, #0D0D0C 100%);
  border-color: var(--f-line);
}
.tstf-panel--noart::before { display: none; }   /* no image, so no ink wash */
.tstf-panel--noart .tstf-panel__img { display: none; }

.tstf-panel--noart .tstf-panel__title {
  /* Set larger than the illustrated panels, in the same face. The picture
     is missing, so the type is the picture. */
  font-size: clamp(20px, 2.3vw, 34px);
  line-height: 1.02;
}
.tstf-panels > .tstf-panel--noart:first-child .tstf-panel__title {
  font-size: clamp(26px, 3.6vw, 54px);
}
.tstf-panel--noart .tstf-panel__dek {
  display: block;
  margin-top: 10px;
  max-width: 48ch;
  color: var(--f-silver);
}
/* A printer's ornament, not a watermark over a void: one small mark on the
   hairline, in a stated colour. */
/* With no image there is nothing to justify a void at the top of the
   panel, so the type starts at the top and the attribution is pushed to
   the bottom hairline — the panel reads as a column of text rather than
   as a caption hanging under an absent picture. */
.tstf-panel--noart { justify-content: flex-start; }
.tstf-panel--noart .tstf-panel__foot {
  margin-top: auto;
  padding-top: 10px;
  box-shadow: inset 0 1px 0 var(--f-line);
}

/* ------------------------------------------------------------ focus rings
   Stated locally: the §9 floor targets `.tstf-section`, and all three of
   these are `.tstd-section`s sitting in the Hub's slots. */

.tstf-showcase :focus-visible,
.tstf-reel :focus-visible,
.tstf-join :focus-visible {
  outline: 2px solid var(--f-bone);
  outline-offset: 3px;
}

/* ----------------------------------------------------------------- motion */

@media (prefers-reduced-motion: reduce) {
  .tstf-show__art img,
  .tstf-clip__art img,
  .tstf-show__kunai { transition: none; }
  .tstf-show__frame:hover .tstf-show__art img,
  .tstf-clip__a:hover .tstf-clip__art img { scale: 1; }
  .tstf-show__frame:hover .tstf-show__kunai { transform: rotate(90deg); }
  .tstf-join__go { transition: none; }
  .tstf-join__a:hover .tstf-join__go { transform: none; }
  .tstf-reel__strip { scroll-snap-type: none; }
}

/* ------------------------------------------------------------- the Hub's
   The three sections are the flagship's now. Hidden with a class on
   <html>, never the `hidden` attribute, and claimed at parse time so the
   Hub's ten thumbnails never enter a viewport — see flagship.js. */

.tstf-owns-featured #featured,
.tstf-owns-latest #latest,
.tstf-owns-follow #follow { display: none !important; }

/* =========================================================================
   22. PHASE 11 — THE TWO HUB CTA CARDS, AND A QUIETER FOOTER

   `#cta-watch` and `#cta-site` were the last two rounded bordered cards on
   the page, sitting directly between flagship sections. They are gone, not
   reskinned:

   - `#cta-watch` carried exactly one thing this page did not already have:
     the CHANNEL address, which is a different URL from the featured video.
     Its headline ("WATCH THE SHONEN TABLE") and standfirst ("Debates,
     rankings, interviews and hot takes") restated NOW SHOWING two lines
     below it, and its red fill was the largest saturated area on a page
     whose standing brief is to keep reducing red. The destination is now a
     second, quieter action inside NOW SHOWING, using that section's own
     copy for the label.
   - `#cta-site` pointed back UP the page at `#latest`, which the menu
     already lists, and it sat AFTER the @theshonentable sign-off — so the
     page ended by sending people backwards. Its slot now holds JOIN THE
     TABLE, which is what an ending should ask for.

   Neither had an anchor pointing at it, so nothing went dead; `hideHub()`
   retargets any that appear later anyway.
   ========================================================================= */

.tstf-hides-cta-watch #cta-watch { display: none !important; }
.tstf-owns-cta-site #cta-site,
.tstf-owns-trending #tstd-trending { display: none !important; }

#tstf-era,
#tstf-join-table { scroll-margin-top: clamp(56px, 8vh, 96px); }

/* The second action in NOW SHOWING. Quieter than the episode link by
   weight and rule, not by size — it is a destination, not a headline. */
.tstf-show__alt {
  display: inline-flex;
  align-items: center;
  min-height: 44px;
  margin-left: clamp(16px, 2vw, 28px);
  font-family: var(--f-mono);
  font-size: 11px;
  font-weight: 700;
  letter-spacing: 0.16em;
  text-transform: uppercase;
  color: var(--f-faint);
  text-decoration: none;
}
.tstf-show__alt:hover,
.tstf-show__alt:focus-visible { color: var(--f-bone); }

/* ------------------------------------------------------- 22b. the footer
   ONE HANDLE is the social sign-off now, so the footer's own icon row was
   the same three platforms twice within 400px. The footer goes back to
   being utility: a hairline, the wordmark at reading size, the year. No
   display type, no tagline in red, no second social row, no decoration.
   Done as overrides here because app.js is untouched. */

.tst-hub-footer__inner { padding-block: clamp(26px, 3.4vw, 40px) !important; }
.tst-hub-footer__eyebrow { display: none !important; }
.tst-hub-footer__name {
  font-family: var(--f-mono) !important;
  font-size: 11px !important;
  font-weight: 700 !important;
  letter-spacing: 0.2em !important;
  color: var(--f-silver) !important;
  margin: 0 0 6px !important;
}
.tst-hub-footer__tagline {
  font-family: var(--f-mono) !important;
  font-size: 10px !important;
  letter-spacing: 0.16em !important;
  color: var(--f-faint) !important;
  margin: 0 !important;
}
/* The duplicate platform row. */
.tst-hub-footer__inner .tst-hub-social-row { display: none !important; }
.tst-hub-footer__legal {
  margin-top: 14px !important;
  font-family: var(--f-mono) !important;
  font-size: 10px !important;
  letter-spacing: 0.1em !important;
  color: var(--f-faint) !important;
}
/* Quiet means quiet: no red wash bleeding up from the top edge, no
   floating kunai (fx.js drops one into `#site-footer`), and aligned left
   with every other line on the page instead of centred on its own. */
#site-footer {
  background: var(--f-ink) !important;
  border-top: 0 !important;
  box-shadow: inset 0 1px 0 var(--f-line);
  text-align: left !important;
  padding-block: 0 !important;
}
#site-footer .tst-kunai-fx { display: none !important; }
.tst-hub-footer__inner {
  max-width: var(--f-shell-wide);
  margin-inline: auto;
  padding-inline: var(--f-pad);
}
.tst-hub-footer__eyebrow,
.tst-hub-footer__legal { justify-content: flex-start !important; }

/* =========================================================================
   23. THE ERA THAT MADE US — the Big Three  (NEXT-STEPS step 7)

   The one full PAPER section on the site, and the mode change the spec
   asks for: the sections either side of it (THE SPLIT and THE WIRE) are
   both dark, so the page is interrupted by a printed insert. The store's
   plate is paper inside a dark section; this is the opposite arrangement
   and a different, older stock — warmer, with a halftone screen and heavy
   black rules instead of hairlines.

   Contrast is stated for paper rather than inherited: none of the `--f-*`
   quiet tiers are legal on a light ground, so this section carries its own
   two ink tones and nothing else borrows them.
   ========================================================================= */

.tstf-era {
  --era-paper: #E6DFD2;
  --era-ink: #141310;
  --era-quiet: #57513F;
  position: relative;
  padding-block: clamp(52px, 7vw, 104px);
  background: var(--era-paper);
  color: var(--era-ink);
  overflow: clip;
}
/* The halftone. Generated, not an asset, and multiplied so it darkens the
   paper and the printed covers together — one screen over the whole
   insert, the way a press would put it there. */
.tstf-era::after {
  content: "";
  position: absolute;
  inset: 0;
  /* Above the content, not behind it: a press screen falls on the whole
     forme, the covers included. Behind the content it was decorating the
     paper and doing nothing to the reproductions. */
  z-index: 2;
  pointer-events: none;
  background-image: radial-gradient(rgba(20, 19, 16, 0.24) 0.9px, transparent 1px);
  background-size: 4px 4px;
  mix-blend-mode: multiply;
  opacity: 0.5;
}

.tstf-era__in {
  position: relative;
  z-index: 1;
  width: 100%;
  max-width: var(--f-shell-wide);
  margin-inline: auto;
  padding-inline: var(--f-pad);
}

.tstf-era__rail {
  display: flex;
  justify-content: space-between;
  align-items: baseline;
  gap: 16px;
  margin: 0;
  padding-bottom: 10px;
  /* Heavier than the dark sections' hairlines: this is print. */
  box-shadow: inset 0 -2px 0 var(--era-ink);
  font-family: var(--f-mono);
  font-size: 10.5px;
  font-weight: 700;
  letter-spacing: 0.2em;
  text-transform: uppercase;
  color: var(--era-ink);
}

.tstf-era__mast { margin: clamp(20px, 2.6vw, 36px) 0 clamp(22px, 3vw, 40px); }
.tstf-era__title {
  margin: 0;
  font-family: var(--f-head);
  font-size: clamp(34px, min(6vw, 10vh), 104px);
  line-height: 0.86;
  letter-spacing: -0.04em;
  text-transform: uppercase;
  color: var(--era-ink);
}
.tstf-era__line {
  margin: clamp(14px, 1.8vw, 22px) 0 0;
  max-width: 34ch;
  font-size: clamp(15px, 1.3vw, 19px);
  line-height: 1.45;
  color: var(--era-quiet);
}

.tstf-era__list { list-style: none; margin: 0; padding: 0; }

.tstf-era__row {
  display: grid;
  grid-template-columns: auto minmax(0, 1fr) auto;
  grid-template-areas:
    "n name art"
    "n tag  art"
    "n now  art";
  align-items: start;
  column-gap: clamp(14px, 2vw, 30px);
  padding: clamp(16px, 2vw, 26px) 0;
  box-shadow: inset 0 2px 0 var(--era-ink);
}
.tstf-era__n {
  grid-area: n;
  align-self: start;
  padding-top: 6px;
  font-family: var(--f-mono);
  font-size: 11px;
  font-weight: 700;
  letter-spacing: 0.1em;
  color: var(--era-quiet);
}
.tstf-era__name {
  grid-area: name;
  margin: 0;
  font-family: var(--f-head);
  font-size: clamp(26px, min(4.6vw, 8vh), 76px);
  line-height: 0.92;
  letter-spacing: -0.035em;
  text-transform: uppercase;
  color: var(--era-ink);
}
.tstf-era__tag {
  grid-area: tag;
  margin-top: 4px;
  font-family: var(--f-mono);
  font-size: 10.5px;
  font-weight: 700;
  letter-spacing: 0.16em;
  text-transform: uppercase;
  color: var(--era-quiet);
}

/* THEN is the name; NOW is this. The one thing in the section that is
   live, so it is the one thing set in the display face at reading size. */
.tstf-era__now {
  grid-area: now;
  display: flex;
  flex-wrap: wrap;
  align-items: baseline;
  gap: 4px 10px;
  margin-top: clamp(10px, 1.4vw, 16px);
}
.tstf-era__now b {
  font-family: var(--f-head);
  font-size: clamp(13px, 1.15vw, 16px);
  letter-spacing: 0.02em;
  text-transform: uppercase;
  color: var(--era-ink);
}
.tstf-era__now i {
  font-family: var(--f-mono);
  font-size: 10.5px;
  font-style: normal;
  letter-spacing: 0.12em;
  text-transform: uppercase;
  color: var(--era-quiet);
}
/* A printed tick beside the two that are still running. Drawn, not an
   asset, and it is the section's only accent. */
.tstf-era__row.is-live .tstf-era__now b::before {
  content: "";
  display: inline-block;
  width: 7px; height: 7px;
  margin-right: 8px;
  vertical-align: 1px;
  background: var(--f-red);
}

/* The cover as a printed reproduction: squared off, inked, screened by the
   section's own halftone rather than by a filter of its own. */
.tstf-era__art {
  grid-area: art;
  position: relative;
  display: block;
  width: clamp(72px, 9vw, 132px);
  aspect-ratio: 3 / 4;
  background: #CFC7B7;
  overflow: hidden;
  box-shadow: inset 0 0 0 1px rgba(20, 19, 16, 0.32);
}
/* Grayscale and lifted into a printable mid-range, with NO multiply. A
   cover is often already mid-dark, and multiplying that onto paper turns
   it into a black rectangle; the section's halftone above it is what
   makes it read as printed. */
.tstf-era__art img {
  width: 100%; height: 100%;
  object-fit: cover;
  filter: grayscale(1) contrast(0.94) brightness(1.08);
}
/* The slate inside a paper section has to be paper too. */
.tstf-era__art .tstf-slate {
  background: linear-gradient(160deg, #D8D0BF 0%, #CFC7B7 100%);
  justify-content: center;
  align-items: center;
  padding: 8px;
}
/* `overflow-wrap: anywhere` is right for a 300px video slate and wrong
   for a 64px one: it printed "NARUT / O". Words stay whole here and the
   size follows the plate. */
.tstf-era__art .tstf-slate__word {
  font-size: clamp(11px, 1.5vw, 17px);
  line-height: 1.05;
  text-align: center;
  overflow-wrap: normal;
  word-break: keep-all;
  hyphens: none;
  /* 2.27:1 as a ghost was too faint for a printed plate, and the sweep
     flags it even though the word is decorative here (the title is set at
     66px immediately beside it). Inked to 4.98:1: it reads as a stamped
     title, which is the right idiom for an archival page anyway. */
  color: #4E4739;
}
.tstf-era__art .tstf-slate__note { display: none; }

.tstf-era__credit {
  margin: clamp(18px, 2.4vw, 30px) 0 0;
  max-width: 62ch;
  font-family: var(--f-mono);
  font-size: 10px;
  line-height: 1.6;
  letter-spacing: 0.06em;
  text-transform: uppercase;
  color: var(--era-quiet);
}

@media (min-width: 900px) {
  .tstf-era__row {
    grid-template-columns: auto minmax(0, 1fr) minmax(190px, auto) auto;
    grid-template-areas:
      "n name now art"
      "n tag  now art";
    align-items: center;
  }
  .tstf-era__now { justify-content: flex-end; text-align: right; margin-top: 0; }
  .tstf-era__n { align-self: center; padding-top: 0; }
}

@media (max-width: 480px) {
  .tstf-era__row {
    grid-template-columns: auto minmax(0, 1fr);
    grid-template-areas:
      "n   name"
      "art tag"
      "art now";
    column-gap: 14px;
  }
  .tstf-era__art { width: 82px; margin-top: 10px; }
  .tstf-era__art .tstf-slate__word { font-size: 13px; }
}

/* =========================================================================
   24. JOIN THE TABLE  (NEXT-STEPS step 11)

   Back to the studio for the ending: paper for the era and the store,
   dark for the invitation. The form is set as a form on paper would be —
   ruled lines, no boxes, no rounded fields — and the whole thing is
   optional and never blocks anything.

   NOTHING HERE EVER CLAIMS A SIGNUP IT CANNOT PROVE. The endpoint
   (`netlify/functions/join.js`) exists and validates, so the form is live
   — but it answers HTTP 200 with `{ ok:false, configured:false }` until an
   email provider is connected. So there are two separate off states, and
   both are styled: `closed`, when config carries no endpoint at all, and
   `unavailable`, when the endpoint replies that no provider is connected.
   Neither is coloured like success. The alternative — a form that accepts
   an address and says "thanks" because the HTTP status was 200 — would be
   a lie told to everyone who used it.
   ========================================================================= */

.tstf-jt {
  padding-block: clamp(54px, 7.4vw, 112px);
  background: linear-gradient(180deg, #090908 0%, var(--f-ink) 40%, #0C0C0B 100%);
  overflow: clip;
}
.tstf-jt__in {
  width: 100%;
  max-width: var(--f-shell-wide);
  margin-inline: auto;
  padding-inline: var(--f-pad);
  display: grid;
  gap: clamp(26px, 3.4vw, 56px);
}

.tstf-jt__label {
  margin: 0 0 clamp(10px, 1.4vw, 16px);
  font-family: var(--f-mono);
  font-size: 10.5px;
  font-weight: 700;
  letter-spacing: 0.18em;
  text-transform: uppercase;
  color: var(--f-faint);
}
.tstf-jt__title {
  margin: 0;
  font-family: var(--f-head);
  font-size: clamp(32px, min(5vw, 9vh), 86px);
  line-height: 0.9;
  letter-spacing: -0.035em;
  text-transform: uppercase;
  color: var(--f-bone);
}
.tstf-jt__promise {
  margin: clamp(14px, 1.8vw, 22px) 0 0;
  max-width: 26ch;
  font-family: var(--f-head);
  font-size: clamp(15px, 1.35vw, 20px);
  line-height: 1.35;
  letter-spacing: -0.01em;
  color: var(--f-silver);
}

/* ----- the form */

.tstf-jt__form { display: grid; gap: 18px; max-width: 46ch; }
.tstf-jt__field { display: grid; gap: 8px; }
.tstf-jt__lab {
  font-family: var(--f-mono);
  font-size: 10.5px;
  font-weight: 700;
  letter-spacing: 0.16em;
  text-transform: uppercase;
  color: var(--f-faint);
}
/* A ruled line, not a box. */
.tstf-jt__input {
  width: 100%;
  min-height: 48px;
  margin: 0;
  padding: 10px 2px;
  border: 0;
  border-radius: 0;
  background: none;
  box-shadow: inset 0 -1px 0 var(--f-slate);
  color: var(--f-bone);
  font-family: inherit;
  font-size: 16px;   /* 16px: anything smaller makes iOS zoom the page */
  letter-spacing: 0.01em;
}
.tstf-jt__input::placeholder { color: var(--f-stone); }
.tstf-jt__input:focus { outline: none; box-shadow: inset 0 -2px 0 var(--f-bone); }
.tstf-jt__input:focus-visible { outline: 2px solid var(--f-bone); outline-offset: 4px; }
.tstf-jt__input:disabled { color: var(--f-faint); box-shadow: inset 0 -1px 0 #2A2D33; }

/* The whole row is the target, so the 16px control sits inside a 44px+
   label rather than being a 16px tap target of its own. */
.tstf-jt__consent {
  display: grid;
  grid-template-columns: auto minmax(0, 1fr);
  gap: 12px;
  align-items: start;
  min-height: 44px;
  padding: 6px 0;
  cursor: pointer;
  font-size: 13.5px;
  line-height: 1.5;
  color: var(--f-silver);
}
.tstf-jt__consent input {
  width: 18px; height: 18px;
  margin: 2px 0 0;
  accent-color: var(--f-bone);
  cursor: pointer;
}
.tstf-jt__consent input:disabled { cursor: default; }

.tstf-jt__go {
  justify-self: start;
  min-height: 48px;
  margin: 0;
  padding: 14px 26px;
  border: 0;
  border-radius: 0;
  background: var(--f-bone);
  color: var(--f-ink);
  font-family: var(--f-mono);
  font-size: 11.5px;
  font-weight: 700;
  letter-spacing: 0.18em;
  text-transform: uppercase;
  cursor: pointer;
}
.tstf-jt__go:hover:not(:disabled) { background: #FFFFFF; }
.tstf-jt__go:disabled {
  background: none;
  box-shadow: inset 0 0 0 1px var(--f-slate);
  color: var(--f-faint);
  cursor: default;
}

.tstf-jt__note,
.tstf-jt__closed {
  margin: 0;
  max-width: 44ch;
  font-size: 12.5px;
  line-height: 1.55;
  color: var(--f-faint);
}
.tstf-jt__closed {
  padding-top: 14px;
  box-shadow: inset 0 1px 0 var(--f-line);
  color: var(--f-silver);
}
.tstf-jt__closed b { color: var(--f-bone); }

/* One live region for every outcome; the colour states what kind it is
   and the words carry the meaning, never the colour alone. */
.tstf-jt__msg { margin: 0; font-size: 13.5px; line-height: 1.5; color: var(--f-silver); }
.tstf-jt__msg:empty { display: none; }
[data-join-state="invalid"] .tstf-jt__msg,
[data-join-state="noconsent"] .tstf-jt__msg,
[data-join-state="error"] .tstf-jt__msg { color: var(--f-gold); }
[data-join-state="ok"] .tstf-jt__msg { color: var(--f-bone); }
/* Endpoint reachable, provider not connected — the honest HTTP 200 that
   join.js answers with { ok:false, configured:false }. Deliberately NOT
   the success colour and NOT the attention colour: this is a state of the
   site, not the visitor's mistake, and not a claim that anything was
   stored. It takes the same hairline rule as the closed notice, because it
   says the same thing at a different moment. */
[data-join-state="unavailable"] .tstf-jt__msg {
  padding-top: 14px;
  box-shadow: inset 0 1px 0 var(--f-line);
  color: var(--f-silver);
}
/* Dims the label while the request is in flight. Was `--f-stone`, which
   measures 4.08:1 on the bone button — under the floor. That never showed
   up in the contrast sweep because with no endpoint configured the form
   was disabled and `sending` was unreachable; enabling the endpoint made
   it reachable, so it now has to pass. `--f-slate` reads as clearly dimmer
   than the resting ink (10.10:1 against 16.53:1) with a stated colour
   rather than an opacity, and stays above AA. */
[data-join-state="sending"] .tstf-jt__go { color: var(--f-slate); }
[data-join-state="ok"] .tstf-jt__form .tstf-jt__field,
[data-join-state="ok"] .tstf-jt__consent,
[data-join-state="ok"] .tstf-jt__go { display: none; }

.tstf-jt__alt {
  list-style: none;
  margin: clamp(20px, 2.6vw, 32px) 0 0;
  padding: 0;
  display: flex;
  flex-wrap: wrap;
  gap: 8px 26px;
}
.tstf-jt__alt a {
  display: inline-flex;
  align-items: center;
  min-height: 44px;
  font-family: var(--f-mono);
  font-size: 11px;
  font-weight: 700;
  letter-spacing: 0.14em;
  text-transform: uppercase;
  color: var(--f-silver);
  text-decoration: none;
  box-shadow: inset 0 -1px 0 var(--f-slate);
}
.tstf-jt__alt a:hover,
.tstf-jt__alt a:focus-visible { color: var(--f-bone); box-shadow: inset 0 -1px 0 var(--f-bone); }

.tstf-jt :focus-visible { outline: 2px solid var(--f-bone); outline-offset: 3px; }

@media (min-width: 900px) {
  .tstf-jt__in {
    grid-template-columns: minmax(0, 1fr) minmax(360px, 0.92fr);
    align-items: start;
  }
  /* Centred against the form rather than hung from the top, which left
     ~400px of dead column under the promise. */
  .tstf-jt__in { align-items: center; }
  .tstf-jt__mast { padding-top: 0; }
}

/* =========================================================================
   25. THE MOBILE HERO, RECOMPOSED  (NEXT-STEPS step 11b)

   MEASURED BEFORE: 1075px at 320x568 (1.89 viewports), 998px at 375x667
   (1.50), 966px at 390x844 (1.14). Nothing on a phone was one clean
   screen, and the breakdown says exactly why — at 390x844 the airing
   board was 474px of a 966px hero, half the opening screen, and 320px of
   that was a four-row queue and a three-line footnote that THE DROP
   restates in full immediately below it.

   So this is not the desktop hero shrunk. On a phone the hero keeps the
   identity — cloud sky, monogram, three-line wordmark, tagline, the two
   actions — and the board is cut down to the ONE thing a first screen
   needs from it: what is next, and how long. The queue and the footnote
   are not lost; they are 600px further down in THE DROP, where there is
   room for them and where the section exists to hold them.

   Everything here is inside a mobile query. The desktop hero is a
   protected, approved composition and is untouched.
   ========================================================================= */

@media (max-width: 699.98px) {
  /* Tighter vertical rhythm throughout, and the block padding stops
     fighting the fixed chrome. */
  .tstf-hero { min-height: 100svh; padding-block: clamp(14px, 3vh, 26px) 0; }
  .tstf-hero__inner { gap: clamp(10px, 1.6vh, 18px); }

  .tstf-hero__blurb {
    margin-top: 6px;
    max-width: 34ch;
    font-size: 14.5px;
    line-height: 1.42;
  }

  /* The secondary action stops being a second heavy pill and becomes the
     underlined text action used everywhere else in this layer — one
     filled shape per screen, which is the rule the rest of the site
     already follows. Saves ~34px and reads as a hierarchy. */
  .tstf-hero__actions { gap: 6px 18px; align-items: center; }
  .tstf-hero__actions > a:nth-child(2) {
    min-height: 44px;
    padding: 0;
    border: 0;
    border-radius: 0;
    background: none;
    box-shadow: inset 0 -1px 0 var(--f-slate);
    font-family: var(--f-mono);
    font-size: 11px;
    font-weight: 700;
    letter-spacing: 0.14em;
    text-transform: uppercase;
    color: var(--f-silver);
  }
  .tstf-hero__actions > a:nth-child(2):hover,
  .tstf-hero__actions > a:nth-child(2):focus-visible {
    color: var(--f-bone);
    box-shadow: inset 0 -1px 0 var(--f-bone);
  }

  /* THE BOARD, CUT TO THE NEXT DROP.
     Kept: the live label, the show, the episode/broadcast line, the
     countdown — the four things that make a first screen feel live.
     Dropped: the four-row queue and the times footnote, both of which are
     stated in full in THE DROP below. */
  .tstf-board__queue,
  .tstf-board__note { display: none; }
  .tstf-board { padding-block: 14px 16px; }
  .tstf-board__lead { padding-bottom: 0; }
  .tstf-board .tstf-clock { font-size: clamp(38px, 12.4vw, 54px); line-height: 0.92; }
  .tstf-board__show { font-size: clamp(17px, 4.6vw, 21px); }

  /* A stated pointer to where the rest of it went, so removing the queue
     reads as an edit rather than as missing data. */
  .tstf-board::after {
    content: "Full schedule below";
    display: block;
    margin-top: 12px;
    padding-top: 10px;
    box-shadow: inset 0 1px 0 var(--f-line);
    font-family: var(--f-mono);
    font-size: 9.5px;
    font-weight: 700;
    letter-spacing: 0.18em;
    text-transform: uppercase;
    color: var(--f-faint);
  }
}

/* SHORT VIEWPORTS. A 568px or 600px screen cannot hold a three-line
   wordmark, a tagline, a blurb, two actions and a live countdown and
   still be premium, so on the shortest ones the blurb — the only element
   whose content appears again further down — steps out, and the wordmark
   gives back a few points of size. The tagline stays: it is the
   positioning line and it is one line long. */
@media (max-width: 699.98px) and (max-height: 700px) {
  .tstf-hero__blurb { display: none; }
  .tstf-hero__inner { gap: clamp(8px, 1.2vh, 14px); }
  .tstf-board .tstf-clock { font-size: clamp(34px, 11vw, 44px); }
  .tstf-board::after { margin-top: 10px; }
}

/* The smallest screen still in real use (320x568) is the one size that
   cannot reach a single viewport with a live countdown on it: measured
   626px, 1.10 viewports, with the monogram and blurb already stepped out.
   The board gives back the rest rather than the wordmark, because the
   wordmark is the identity and the board is a summary of something 600px
   below. */
@media (max-width: 699.98px) and (max-height: 600px) {
  .tstf-hero__mark { display: none; }
  .tstf-board { padding-block: 10px 12px; }
  .tstf-board .tstf-clock { font-size: clamp(30px, 10vw, 38px); }
  .tstf-board__ep { font-size: 10px; }
  .tstf-board::after { display: none; }
}

/* =========================================================================
   FINAL COMPLETION PASS — 2026-09-20
   High-contrast amber system, tonal section rhythm, balanced Split, portal
   cloud orbit and the dismissible Join prompt. Kept at the end so it safely
   refines both Hub and flagship layers without unpicking their interactions.
   ========================================================================= */
:root {
  --hub-ink: #101722;
  --hub-ink-2: #151E2B;
  --hub-ink-3: #1B2635;
  --hub-cream: #FFFFFF;
  --hub-cream-dim: #E9EBEF;
  --hub-cream-faint: #D2D7DF;
  --hub-red: #F5A524;
  --hub-burgundy: #C97908;
  --hub-gold: #F5A524;
  --hub-line: rgba(255,255,255,.18);
  --f-ink: #101722;
  --f-cream: #FFFFFF;
  --f-dim: #E9EBEF;
  --f-faint: #D2D7DF;
  --f-red: #F5A524;
  --f-burg: #C97908;
  --f-gold: #F5A524;
  --f-graphite: #151E2B;
  --f-smoke: #1B2635;
  --f-slate: #2B394A;
  --f-stone: #B8C0CC;
  --f-silver: #E0E5EC;
  --f-bone: #FFFFFF;
  --f-line: rgba(255,255,255,.18);
}

html, body { background: #101722; color: #FFFFFF; }
body { background-image: linear-gradient(180deg, #101722 0%, #151E2B 48%, #101722 100%); }
.tst-hub-hero { background-color: #101722; }
.tstf-btn--primary, .tst-hub-button--primary,
.tstf-jt__submit, .tstf-store__buy {
  background: #F5A524 !important;
  border-color: #F5A524 !important;
  color: #101722 !important;
}
a:focus-visible, button:focus-visible, input:focus-visible { outline-color: #F5A524 !important; }

/* Clear tonal cadence: dark slate, porcelain editorial, charcoal retail,
   porcelain news. Light sections locally invert their type tokens. */
#tstf-featured, #tstf-table, #tstf-latest, #tstf-follow, #tstf-join-table {
  background-color: #151E2B;
}
#tstf-shop { background-color: #1B2635; }
#tstf-week, #tstf-news {
  --f-ink: #E9EBEF;
  --f-cream: #101722;
  --f-dim: #334155;
  --f-faint: #526171;
  --f-bone: #101722;
  --f-silver: #334155;
  --f-stone: #667384;
  --f-smoke: #D9DEE6;
  --f-slate: #C7CED8;
  --f-line: rgba(16,23,34,.20);
  color: #101722;
  background-color: #E9EBEF;
}
#tstf-week *, #tstf-news * { border-color: var(--f-line); }
#tstf-week .tstf-kicker, #tstf-news .tstf-kicker,
#tstf-week .tstf-week__title, #tstf-news .tstf-news__title { color: #101722; }
#tstf-news .tstf-panel { background-color: #DCE1E8; }
#tstf-news .tstf-panel__headline, #tstf-news .tstf-panel__meta,
#tstf-news .tstf-panel__sum { color: #101722; }
#tstf-news .tstf-panel[data-panel-art] .tstf-panel__headline,
#tstf-news .tstf-panel[data-panel-art] .tstf-panel__meta,
#tstf-news .tstf-panel[data-panel-art] .tstf-panel__sum { color: #FFFFFF; }

/* Equal host hierarchy: identical vertical position, font family, sizing,
   artwork strength, and row geometry. Alignment alone mirrors the debate. */
@media (min-width: 900px) {
  .tstf-side--g { padding-top: 0; }
}
.tstf-side--g .tstf-side__name {
  font-family: var(--f-head);
  font-weight: 400;
  font-size: clamp(38px, 6vw, 96px);
  letter-spacing: -0.03em;
}
.tstf-side--g .tstf-pick__n { font-family: var(--f-head); font-size: clamp(19px, 2.4vw, 36px); }
.tstf-side--g .tstf-side__art img,
.tstf-side--m .tstf-side__art img { filter: grayscale(.55) contrast(1.2) brightness(.48); }
.tstf-side--m, .tstf-side--g { min-height: 100%; }

/* Local art fallback is intentional brand artwork, never a broken-image box. */
.tstf-panel--fallback-art .tstf-panel__img img { object-fit: cover; object-position: center; }

/* Atmospheric cloud orbit around the existing world. */
.tstp__clouds { position: absolute; inset: 0; overflow: hidden; pointer-events: none; z-index: 1; }
.tstp__cloud {
  position: absolute;
  display: block;
  width: clamp(170px, 24vw, 420px);
  aspect-ratio: 300 / 190;
  background: url("cloud.png") center / contain no-repeat;
  opacity: .13;
  filter: grayscale(1) brightness(2.1) blur(.2px);
  animation: tstp-cloud-drift 16s ease-in-out infinite alternate;
}
.tstp__cloud--a { left: 5%; top: 22%; transform: rotate(-8deg); }
.tstp__cloud--b { right: 3%; top: 18%; transform: scaleX(-1) rotate(-5deg); animation-delay: -5s; }
.tstp__cloud--c { left: 16%; bottom: 9%; width: clamp(130px, 18vw, 310px); animation-delay: -9s; }
.tstp__cloud--d { right: 14%; bottom: 10%; width: clamp(140px, 20vw, 340px); transform: scaleX(-1); animation-delay: -12s; }
@keyframes tstp-cloud-drift {
  from { margin-inline: -14px; margin-top: 8px; opacity: .08; }
  to { margin-inline: 18px; margin-top: -10px; opacity: .18; }
}

/* Dismissible prompt: deliberately compact and non-blocking. */
.tstf-join-prompt {
  position: fixed; right: clamp(14px, 3vw, 36px); bottom: clamp(14px, 3vw, 32px);
  z-index: 980; width: min(360px, calc(100vw - 28px));
  padding: 20px 54px 20px 20px; color: #FFFFFF;
  background: rgba(21,30,43,.97); border: 1px solid rgba(245,165,36,.6);
  box-shadow: 0 18px 60px rgba(0,0,0,.38);
  transform: translate3d(0, calc(100% + 56px), 0); opacity: 0;
  transition: transform 420ms var(--f-out), opacity 320ms ease;
}
.tstf-join-prompt.is-visible { transform: translate3d(0,0,0); opacity: 1; }
.tstf-join-prompt__close { position:absolute; right:10px; top:8px; width:44px; height:44px; border:0; background:none; color:#E9EBEF; font-size:25px; cursor:pointer; }
.tstf-join-prompt__eyebrow { margin:0 0 6px; color:#F5A524; font:700 9px/1.2 var(--f-mono); letter-spacing:.24em; }
.tstf-join-prompt__title { margin:0; font:400 clamp(25px,4vw,34px)/.95 var(--f-head); }
.tstf-join-prompt__copy { margin:10px 0 15px; color:#E9EBEF; font-size:13px; line-height:1.45; }
.tstf-join-prompt__action { min-height:44px; padding:0 16px; border:1px solid #F5A524; background:#F5A524; color:#101722; font:700 10px/1 var(--f-mono); letter-spacing:.13em; cursor:pointer; }

/* The removed section must stay absent even if a legacy renderer races. */
#tstf-era, #tstd-trending { display: none !important; }

@media (prefers-reduced-motion: reduce) {
  .tstp__cloud { animation: none; }
  .tstf-join-prompt { transition-duration: 1ms; }
}

/* =========================================================================
   READABILITY AND WEEK INTERACTION FIX — 2026-09-20
   The earlier porcelain-token inversion sat underneath legacy dark gradients,
   producing dark-on-dark copy. These two editorial sections now use brighter
   slate surfaces with unequivocal white text. The week is a real day picker.
   ========================================================================= */
#tstf-week,
#tstf-news {
  --f-ink: #263548;
  --f-cream: #FFFFFF;
  --f-dim: #FFFFFF;
  --f-faint: #EDF2F7;
  --f-bone: #FFFFFF;
  --f-silver: #F1F5F9;
  --f-stone: #E2E8F0;
  --f-smoke: #334155;
  --f-slate: #475569;
  --f-line: rgba(255,255,255,.26);
  color: #FFFFFF !important;
  background: linear-gradient(135deg, #3D4E63 0%, #2F4055 52%, #263548 100%) !important;
}

/* Every readable label in the two problem sections is solid white. Amber is
   reserved for the current/selected signal, never body copy. */
#tstf-week :is(h2,h3,p,li,dt,dd,span,b,i,time),
#tstf-news :is(h2,h3,p,li,span,b,time,a) {
  color: #FFFFFF !important;
  text-shadow: 0 1px 2px rgba(0,0,0,.34);
}
#tstf-week .tstf-eyebrow,
#tstf-week .tstf-wday__code i,
#tstf-news .tstf-eyebrow,
#tstf-news .tstf-tag--gold { color: #F5A524 !important; }

/* News stays image-led but gets a stronger wash and brighter slate behind
   image-free stories. Correct class names override the previous ineffective
   headline/sum selectors. */
#tstf-news .tstf-panel {
  background: #34465C !important;
  border-color: rgba(255,255,255,.74) !important;
}
#tstf-news .tstf-panel::before {
  background: linear-gradient(0deg, rgba(12,20,31,.98) 4%, rgba(19,31,47,.84) 52%, rgba(19,31,47,.26) 100%) !important;
}
#tstf-news .tstf-panel__title,
#tstf-news .tstf-panel__dek,
#tstf-news .tstf-panel__foot,
#tstf-news .tstf-river__title,
#tstf-news .tstf-river__meta,
#tstf-news .tstf-sub { color: #FFFFFF !important; }
#tstf-news .tstf-tag:not(.tstf-tag--gold) {
  background: rgba(255,255,255,.94) !important;
  color: #172233 !important;
  text-shadow: none;
}
#tstf-news .tstf-tag--gold {
  background: #F5A524 !important;
  color: #172233 !important;
  text-shadow: none;
}

/* The large ghost initials were sitting across the programme names in the
   screenshot. Day identity now lives in the clear label and interaction. */
#tstf-week .tstf-wday__ghost { display: none !important; }
#tstf-week .tstf-wday {
  padding-top: 132px;
  cursor: pointer;
  background-color: rgba(20,31,46,.28);
  transition: flex-grow 360ms var(--f-out), background-color 260ms ease, transform 260ms ease;
}
#tstf-week .tstf-wday:hover,
#tstf-week .tstf-wday:focus-visible {
  background-color: rgba(71,85,105,.72);
  transform: translateY(-3px);
}
#tstf-week .tstf-wday:focus-visible {
  outline: 3px solid #F5A524 !important;
  outline-offset: -3px;
}
#tstf-week .tstf-wday.is-today { flex: 1 1 0; }
#tstf-week .tstf-wday.is-selected {
  flex: 2.15 1 0;
  background: linear-gradient(180deg, rgba(71,85,105,.95), rgba(38,53,72,.82));
  border-top: 3px solid #F5A524;
}
#tstf-week .tstf-wday.is-selected .tstf-wday__art {
  height: 178px;
  opacity: .95;
  filter: grayscale(.15) contrast(1.04) brightness(.74);
}
#tstf-week .tstf-wday__code {
  padding: 6px 8px;
  width: max-content;
  background: rgba(12,20,31,.72);
  border-left: 3px solid transparent;
}
#tstf-week .tstf-wday.is-selected .tstf-wday__code { border-left-color: #F5A524; }
#tstf-week .tstf-wday__n,
#tstf-week .tstf-wday__t { color: #FFFFFF !important; opacity: 1 !important; }

@media (max-width: 749px) {
  #tstf-week .tstf-wday { flex: 0 0 78vw; padding-top: 158px; }
  #tstf-week .tstf-wday.is-today { flex: 0 0 78vw; }
  #tstf-week .tstf-wday.is-selected { flex: 0 0 88vw; }
}

/* =========================================================================
   OUR WEEK ART AND CLOUD RESTORATION — 2026-09-20
   Each day now carries one deliberate lead anime image from the existing
   AniList snapshot. Local clouds return as section-specific atmosphere.
   ========================================================================= */
#tstf-week { isolation: isolate; overflow: hidden; }
#tstf-week::before,
#tstf-week::after {
  content: "";
  position: absolute;
  z-index: 0;
  pointer-events: none;
  width: clamp(220px, 34vw, 560px);
  aspect-ratio: 300 / 190;
  background: url("cloud.png") center / contain no-repeat;
  filter: grayscale(1) brightness(2.4);
  opacity: .13;
}
#tstf-week::before { left: -8%; top: 2%; transform: rotate(-8deg); }
#tstf-week::after { right: -10%; bottom: -5%; transform: scaleX(-1) rotate(-4deg); }
#tstf-week .tstf-week__head,
#tstf-week .tstf-week__band { position: relative; z-index: 1; }

/* The artwork is content now, not a negative-z decorative layer. */
#tstf-week .tstf-wday { isolation: isolate; padding-top: 168px; }
#tstf-week .tstf-wday__art {
  z-index: 0;
  height: 154px;
  opacity: .82;
  filter: saturate(.82) contrast(1.08) brightness(.78);
  -webkit-mask-image: linear-gradient(to bottom, #000 0%, rgba(0,0,0,.96) 66%, transparent 100%);
          mask-image: linear-gradient(to bottom, #000 0%, rgba(0,0,0,.96) 66%, transparent 100%);
}
#tstf-week .tstf-wday__art::after {
  content: "";
  position: absolute;
  inset: 0;
  background: linear-gradient(180deg, rgba(10,18,28,.06), rgba(10,18,28,.30) 68%, rgba(38,53,72,.94));
}
#tstf-week .tstf-wday:hover .tstf-wday__art,
#tstf-week .tstf-wday:focus-visible .tstf-wday__art,
#tstf-week .tstf-wday.is-selected .tstf-wday__art {
  opacity: 1;
  filter: saturate(1) contrast(1.06) brightness(.88);
}

#tstf-week .tstf-wday__feature {
  position: absolute;
  z-index: 2;
  top: 92px;
  left: clamp(10px, 1.1vw, 18px);
  right: clamp(10px, 1.1vw, 18px);
  display: block;
  color: #FFFFFF !important;
  font-family: var(--f-head);
  font-size: clamp(13px, 1.15vw, 17px);
  line-height: 1.04;
  text-transform: uppercase;
  text-shadow: 0 2px 10px rgba(0,0,0,.9);
}
#tstf-week .tstf-wday__feature small {
  display: block;
  margin-bottom: 5px;
  color: #F5A524 !important;
  font-family: var(--f-mono);
  font-size: 8px;
  letter-spacing: .18em;
  text-shadow: 0 1px 4px rgba(0,0,0,.9);
}

/* Selected artwork gets room to breathe without hiding the schedule. */
#tstf-week .tstf-wday.is-selected { padding-top: 190px; }
#tstf-week .tstf-wday.is-selected .tstf-wday__art { height: 178px; }
#tstf-week .tstf-wday.is-selected .tstf-wday__feature { top: 110px; }

@media (max-width: 749px) {
  #tstf-week .tstf-wday,
  #tstf-week .tstf-wday.is-selected { padding-top: 218px; }
  #tstf-week .tstf-wday__art,
  #tstf-week .tstf-wday.is-selected .tstf-wday__art { height: 206px; }
  #tstf-week .tstf-wday__feature,
  #tstf-week .tstf-wday.is-selected .tstf-wday__feature { top: 138px; }
  #tstf-week::before { left: -34%; top: 4%; }
  #tstf-week::after { right: -38%; bottom: 1%; }
}

/* =========================================================================
   TRUE HUB DARK + CORNER TINT SYSTEM — 2026-09-20
   The canvas follows hub.theshonentable.co.uk: near-black editorial ground,
   porcelain type, and colour used as atmospheric light at opposing corners.
   ========================================================================= */

:root {
  --tst-ink: #0A0A09;
  --tst-panel: #111110;
  --tst-panel-2: #171615;
  --tst-white: #FFFFFF;
  --tst-porcelain: #F4F1EA;
  --tst-muted: #D4D1CB;
  --tst-red: #C8102E;
  --tst-red-deep: #7E0C1E;
  --tst-orange: #E88728;
  --tst-gold: #E3A93B;
  --tst-blue: #1556C8;
  --tst-blue-bright: #2B74E8;
}

html,
body {
  background: var(--tst-ink) !important;
  color: var(--tst-white) !important;
}
body {
  background-image:
    radial-gradient(42rem 28rem at 100% 18%, rgba(200,16,46,.055), transparent 72%),
    radial-gradient(38rem 26rem at 0 72%, rgba(21,86,200,.045), transparent 72%) !important;
}

/* White is the default reading colour; secondary copy stays bright enough. */
#tstf-today,
#tstf-table,
#tstf-week,
#tstf-shop,
#tstf-featured,
#tstf-news,
#tstf-latest,
#tstf-follow,
#tstf-join-table {
  --f-cream: #FFFFFF;
  --f-bone: #FFFFFF;
  --f-dim: #E8E5DF;
  --f-silver: #D9D6D0;
  --f-faint: #C5C2BC;
  --f-stone: #A8A6A1;
  --f-line: rgba(255,255,255,.14);
  color: #FFFFFF !important;
  position: relative;
  isolation: isolate;
  overflow: clip;
}

/* --------------------------------------------------------- opening / red */
.tst-hub-hero.tstf-hero {
  color: #FFFFFF !important;
  background:
    radial-gradient(64rem 44rem at 100% -2%, rgba(200,16,46,.34), transparent 68%),
    radial-gradient(58rem 40rem at -5% 105%, rgba(126,12,30,.34), transparent 70%),
    linear-gradient(145deg, #080808 0%, #0B0909 55%, #0A0A09 100%) !important;
}
.tst-hub-hero.tstf-hero::before {
  opacity: .09 !important;
  filter: grayscale(1) brightness(2.6) contrast(.8) !important;
}
.tstf-hero::after {
  background:
    radial-gradient(48rem 30rem at 101% 4%, rgba(255,67,96,.13), transparent 72%),
    radial-gradient(46rem 30rem at -2% 99%, rgba(200,16,46,.14), transparent 72%),
    linear-gradient(90deg, transparent, rgba(255,255,255,.015), transparent) !important;
}
.tst-hub-hero.tstf-hero :is(.tstf-wordmark,.tstf-hero__tagline,.tstf-hero__blurb) {
  color: #FFFFFF !important;
  text-shadow: 0 2px 24px rgba(0,0,0,.58) !important;
}
.tstf-hero__tagline { color: #FF4967 !important; }
.tstf-hero__logo {
  box-shadow: 0 0 0 1px rgba(255,255,255,.24), 0 18px 50px rgba(200,16,46,.18) !important;
}
.tstf-hero__actions > a:first-child {
  background: var(--tst-red) !important;
  border-color: var(--tst-red) !important;
  color: #FFFFFF !important;
}
.tstf-hero__actions > a:nth-child(2) {
  background: rgba(255,255,255,.035) !important;
  border-color: rgba(255,255,255,.34) !important;
  color: #FFFFFF !important;
}

/* Airing Next is a dark hub card with clear white type and one red edge. */
.tstf-board {
  background: rgba(17,17,16,.94) !important;
  border: 1px solid rgba(200,16,46,.52) !important;
  box-shadow: 0 24px 64px rgba(0,0,0,.44), inset 2px 0 0 rgba(200,16,46,.72) !important;
  backdrop-filter: blur(14px) !important;
}
.tstf-board__head,
.tstf-board__lead,
.tstf-queue-row { border-color: rgba(255,255,255,.13) !important; }
.tstf-board__lead::after {
  background: linear-gradient(135deg, rgba(126,12,30,.20), rgba(23,22,21,.96) 58%) !important;
  border: 1px solid rgba(255,255,255,.07) !important;
}
.tstf-board :is(.tstf-board__show,.tstf-board__ep,.tstf-clock,.tstf-queue-row__name,.tstf-queue-row__t,.tstf-board__note,.tstf-board__note b) {
  color: #FFFFFF !important;
  text-shadow: none !important;
}
.tstf-board :is(.tstf-board__head,.tstf-board__kind) { color: var(--tst-muted) !important; }
.tstf-board__live { background: #FF3154 !important; }

.tstf-strip {
  background: #0D0D0C !important;
  border-color: rgba(255,255,255,.12) !important;
  box-shadow: inset 0 -2px 0 rgba(200,16,46,.72) !important;
}
.tstf-strip__tag {
  background: rgba(200,16,46,.13) !important;
  border-right-color: rgba(200,16,46,.46) !important;
  color: #FF4967 !important;
}
.tstf-strip__item,
.tstf-strip__item b { color: #FFFFFF !important; }
.tstf-strip__item span { color: var(--tst-muted) !important; }

/* Shared dark editorial panels. */
.tstf-panel,
.tstf-picks,
.tstf-hinge,
.tstf-join__a,
.tstf-jt__panel,
.tstf-uni__plate {
  background-color: rgba(17,17,16,.88) !important;
  border-color: rgba(255,255,255,.14) !important;
}

/* ----------------------------------------------------------- red section */
#tstf-today {
  background:
    radial-gradient(46rem 30rem at 100% 0, rgba(200,16,46,.22), transparent 70%),
    radial-gradient(42rem 28rem at 0 100%, rgba(126,12,30,.19), transparent 72%),
    #0A0A09 !important;
  box-shadow: inset 0 1px 0 rgba(255,255,255,.08) !important;
}

/* -------------------------------------------------------- orange section */
#tstf-featured,
#tstf-table {
  background:
    radial-gradient(48rem 30rem at 100% 5%, rgba(232,135,40,.17), transparent 72%),
    radial-gradient(42rem 28rem at -3% 98%, rgba(227,169,59,.11), transparent 72%),
    #0B0B0A !important;
  box-shadow: inset 0 1px 0 rgba(232,135,40,.18) !important;
}
#tstf-table .tstf-hinge__v b,
#tstf-featured .tstf-show__label { color: #FFB348 !important; }

/* Image/video surfaces keep porcelain copy over their overlays. */
#tstf-featured .tstf-show__frame,
#tstf-featured .tstf-show__frame *,
.tstf-slate,
.tstf-slate * { color: #FFFFFF !important; }

/* ---------------------------------------------------------- blue section */
#tstf-week {
  background:
    radial-gradient(50rem 32rem at 100% 0, rgba(43,116,232,.20), transparent 72%),
    radial-gradient(46rem 30rem at 0 100%, rgba(21,86,200,.18), transparent 72%),
    #0A0B0E !important;
  box-shadow: inset 0 1px 0 rgba(43,116,232,.24) !important;
}
#tstf-week::before,
#tstf-week::after { opacity: .12 !important; filter: grayscale(1) brightness(1.45) !important; }
#tstf-week .tstf-week__head :is(h2,p,dt,dd,span) { color: #FFFFFF !important; }
#tstf-week .tstf-eyebrow { color: #72A7FF !important; }
#tstf-week .tstf-wday {
  background-color: #10141C !important;
  border-color: rgba(109,160,244,.30) !important;
  box-shadow: inset -1px 0 0 rgba(255,255,255,.11), 0 14px 30px rgba(0,0,0,.22) !important;
}
#tstf-week .tstf-wday:hover,
#tstf-week .tstf-wday:focus-visible,
#tstf-week .tstf-wday.is-selected { background-color: #151C29 !important; }
#tstf-week .tstf-wday.is-selected { border-top-color: #4B8FFF !important; }
#tstf-week .tstf-wday :is(p,li,span,b,i) { color: #FFFFFF !important; }
#tstf-week .tstf-wday__feature small,
#tstf-week .tstf-wday__code i { color: #82B1FF !important; }

/* --------------------------------------------------------- white section */
#tstf-shop {
  --f-cream: #FFFFFF;
  --f-bone: #FFFFFF;
  --f-silver: #E5E3DE;
  --f-faint: #C8C5BF;
  background:
    radial-gradient(48rem 30rem at 100% 0, rgba(255,255,255,.13), transparent 70%),
    radial-gradient(44rem 28rem at 0 100%, rgba(180,186,197,.095), transparent 72%),
    #0C0C0C !important;
  box-shadow: inset 0 1px 0 rgba(255,255,255,.20) !important;
}
#tstf-shop :is(.tstf-uni__rail,.tstf-uni__the,.tstf-uni__big,.tstf-uni__sub,.tstf-uni__coll) {
  color: #FFFFFF !important;
}
#tstf-shop .tstf-uni__plate {
  background: linear-gradient(145deg, #1A1A19, #101010) !important;
  border: 1px solid rgba(255,255,255,.24) !important;
  box-shadow: 0 22px 54px rgba(0,0,0,.35), inset 2px 0 0 rgba(255,255,255,.26) !important;
}

/* ----------------------------------------------------- red + blue news */
#tstf-news {
  background:
    radial-gradient(48rem 30rem at 100% 0, rgba(200,16,46,.16), transparent 72%),
    radial-gradient(44rem 28rem at 0 100%, rgba(21,86,200,.14), transparent 72%),
    #0A0A09 !important;
  box-shadow: inset 0 1px 0 rgba(255,255,255,.10) !important;
}
#tstf-news .tstf-head :is(.tstf-eyebrow,.tstf-title,.tstf-sub,.tstf-sub span) { color: #FFFFFF !important; }
#tstf-news .tstf-panel {
  background: #121211 !important;
  border: 1px solid rgba(255,255,255,.16) !important;
  box-shadow: 0 14px 34px rgba(0,0,0,.28) !important;
}
#tstf-news .tstf-panel:first-child { border-color: rgba(200,16,46,.62) !important; }
#tstf-news .tstf-panel:nth-child(2) { border-color: rgba(43,116,232,.46) !important; }
#tstf-news .tstf-panel :is(.tstf-panel__title,.tstf-panel__dek,.tstf-panel__foot,.tstf-panel__foot span) { color: #FFFFFF !important; }
#tstf-news .tstf-panel__dek,
#tstf-news .tstf-panel__foot { color: var(--tst-muted) !important; }
#tstf-news .tstf-panel[data-panel-art]::before {
  display: block !important;
  background: linear-gradient(0deg, rgba(6,6,7,.97) 4%, rgba(6,6,7,.73) 48%, rgba(6,6,7,.08) 100%) !important;
}
#tstf-news .tstf-river__item {
  background: rgba(255,255,255,.035) !important;
  border-color: rgba(255,255,255,.13) !important;
}
#tstf-news .tstf-river__item :is(.tstf-river__title,.tstf-river__meta) { color: #FFFFFF !important; }

/* -------------------------------------------------- supporting tint flow */
#tstf-latest {
  background:
    radial-gradient(46rem 30rem at 100% 0, rgba(232,135,40,.15), transparent 72%),
    radial-gradient(42rem 27rem at 0 100%, rgba(255,255,255,.055), transparent 72%),
    #0B0B0A !important;
}
#tstf-follow {
  background:
    radial-gradient(48rem 30rem at 100% 0, rgba(255,255,255,.075), transparent 72%),
    radial-gradient(44rem 28rem at 0 100%, rgba(21,86,200,.17), transparent 72%),
    #0A0B0E !important;
}
#tstf-join-table {
  background:
    radial-gradient(48rem 30rem at 100% 0, rgba(200,16,46,.15), transparent 72%),
    radial-gradient(42rem 27rem at 0 100%, rgba(255,255,255,.065), transparent 72%),
    #0A0A09 !important;
}
#tstf-join-table .tstf-jt__panel {
  background: rgba(17,17,16,.90) !important;
  border: 1px solid rgba(255,255,255,.15) !important;
  box-shadow: inset 2px 0 0 var(--tst-red), 0 18px 48px rgba(0,0,0,.30) !important;
}
#tstf-join-table .tstf-jt__input { color: #FFFFFF !important; box-shadow: inset 0 -2px 0 rgba(255,255,255,.42) !important; }
#tstf-join-table .tstf-jt__go { background: var(--tst-red) !important; color: #FFFFFF !important; }

/* Explicitly protect every non-image text group from old dark declarations. */
#tstf-today :is(.tstf-drop__day,.tstf-drop__date,.tstf-drop__label,.tstf-drop__clock,.tstf-drop__title,.tstf-drop__meta,.tstf-order__time,.tstf-order__name,.tstf-order__state),
#tstf-table :is(.tstf-eyebrow,.tstf-split__title,.tstf-split__sub,.tstf-side__role,.tstf-side__name,.tstf-side__lead,.tstf-pick__n,.tstf-pick__t,.tstf-pick__note,.tstf-pick__mark,.tstf-hinge__k,.tstf-hinge__v,.tstf-hinge__v i,.tstf-hinge__l),
#tstf-featured :is(.tstf-show__label,.tstf-show__title,.tstf-show__lead,.tstf-show__tail,.tstf-show__name,.tstf-show__dek,.tstf-show__cta,.tstf-show__alt),
#tstf-latest :is(.tstf-reel__rail,.tstf-reel__title,.tstf-reel__sub,.tstf-clip__n,.tstf-clip__t,.tstf-reel__arrow),
#tstf-follow :is(.tstf-join__label,.tstf-join__handle,.tstf-join__line,.tstf-join__sub,.tstf-join__a,.tstf-join__n,.tstf-join__k,.tstf-join__h,.tstf-join__go),
#tstf-join-table :is(.tstf-jt__label,.tstf-jt__title,.tstf-jt__promise,.tstf-jt__lab,.tstf-jt__consent,.tstf-jt__note,.tstf-jt__closed,.tstf-jt__msg,.tstf-jt__alt a) {
  color: #FFFFFF !important;
  text-shadow: none !important;
}

.tstf-join-prompt,
.tstf-menu,
.tstp,
.tstf-bar { color: #FFFFFF !important; }
.tstf-bar { background: rgba(10,10,9,.92) !important; }
#site-footer {
  background: #080808 !important;
  border-top: 1px solid rgba(200,16,46,.45) !important;
}
#site-footer :is(.tst-hub-footer__name,.tst-hub-footer__tagline,.tst-hub-footer__legal) { color: #FFFFFF !important; }
.tst-progress__bar { background: var(--tst-red) !important; }
a:focus-visible,
button:focus-visible,
input:focus-visible { outline-color: #FFB348 !important; }

@media (max-width: 699.98px) {
  .tstf-hero__actions > a:nth-child(2) {
    background: transparent !important;
    box-shadow: inset 0 -2px 0 rgba(255,255,255,.72) !important;
  }
}

/* FINAL COMPONENT POLISH — 2026-09-20 */

/* ------------------------------------------------ image-led Airing Next */
@media (min-width: 1000px) {
  .tstf-hero__inner {
    grid-template-columns: minmax(0, 1.12fr) minmax(410px, .88fr) !important;
    gap: clamp(40px, 5vw, 82px) !important;
  }
}

.tstf-board {
  padding: clamp(14px, 1.7vw, 20px) !important;
  border: 1px solid rgba(255,73,103,.42) !important;
  border-radius: 22px !important;
  background:
    radial-gradient(24rem 18rem at 100% 0, rgba(200,16,46,.18), transparent 72%),
    linear-gradient(145deg, rgba(25,20,22,.97), rgba(13,13,13,.96)) !important;
  box-shadow:
    0 30px 75px rgba(0,0,0,.48),
    inset 1px 0 0 rgba(255,73,103,.48),
    inset 0 1px 0 rgba(255,255,255,.08) !important;
  overflow: hidden !important;
  transform: none !important;
}
.tstf-board::before {
  content: "";
  position: absolute;
  width: 210px;
  height: 210px;
  right: -92px;
  top: -112px;
  border-radius: 50%;
  background: rgba(200,16,46,.16);
  filter: blur(34px);
  pointer-events: none;
}
.tstf-board__head {
  position: relative;
  z-index: 2;
  padding: 0 0 13px !important;
  border-bottom: 1px solid rgba(255,255,255,.13) !important;
  color: #E7E3DD !important;
}
.tstf-board__kind {
  padding: 5px 8px;
  border: 1px solid rgba(255,255,255,.16);
  border-radius: 999px;
  background: rgba(255,255,255,.045);
  color: #FFFFFF !important;
  letter-spacing: .12em !important;
}
.tstf-board__lead {
  grid-template-columns: 88px minmax(0, 1fr) !important;
  gap: 16px !important;
  align-items: stretch !important;
  padding: 17px 0 !important;
  border-bottom: 1px solid rgba(255,255,255,.13) !important;
}
.tstf-board__lead::after {
  background: linear-gradient(100deg, rgba(126,12,30,.13), transparent 64%) !important;
  border: 0 !important;
}
.tstf-board__poster {
  display: block !important;
  width: 88px !important;
  min-height: 126px;
  aspect-ratio: 2 / 3;
  border-radius: 10px !important;
  overflow: hidden;
  border: 1px solid rgba(255,255,255,.22);
  box-shadow: 0 14px 34px rgba(0,0,0,.42);
  background: #191919;
}
.tstf-board__poster img {
  width: 100%;
  height: 100%;
  object-fit: cover;
  filter: saturate(1.08) contrast(1.04);
}
.tstf-board__body { gap: 6px !important; align-content: center !important; }
.tstf-board__show {
  font-size: clamp(20px, 2.25vw, 31px) !important;
  line-height: .98 !important;
  text-wrap: balance;
}
.tstf-board__ep {
  color: #D8D3CC !important;
  line-height: 1.45;
}
.tstf-board .tstf-clock {
  margin-top: 9px !important;
  font-size: clamp(35px, 3.9vw, 55px) !important;
  color: #FFFFFF !important;
}
.tstf-board__queue { margin-top: 2px !important; }
.tstf-queue-row {
  padding: 10px 2px !important;
  border-bottom-color: rgba(255,255,255,.11) !important;
}
.tstf-queue-row__at {
  color: #FF5C75 !important;
  font-size: 13px !important;
}
.tstf-board__note {
  padding: 11px 2px 0 !important;
  color: #C9C5BF !important;
}

@media (max-width: 699.98px) {
  .tstf-board { padding: 14px !important; }
  .tstf-board__lead { grid-template-columns: 70px minmax(0,1fr) !important; gap: 13px !important; }
  .tstf-board__poster { width: 70px !important; min-height: 102px; }
  .tstf-board .tstf-clock { font-size: clamp(30px, 9.6vw, 42px) !important; }
  .tstf-board::after { color: #C9C5BF !important; }
}

/* -------------------------------------------------- true product colours */
/* The source mockups were made for a light press-sheet ground and use
   multiply to remove their white photographic background. The dark plate
   crushed every garment colour; restore the intended paper presentation. */
#tstf-shop .tstf-uni__plate {
  background: linear-gradient(145deg, #FFFDF8, #EEEAE1) !important;
  border: 1px solid rgba(255,255,255,.52) !important;
  box-shadow:
    0 26px 64px rgba(0,0,0,.40),
    0 0 0 1px rgba(0,0,0,.32),
    inset 0 0 48px rgba(99,89,69,.06) !important;
  color: #15140F !important;
}
#tstf-shop .tstf-uni__shot {
  mix-blend-mode: multiply !important;
  filter: saturate(1.14) contrast(1.045) !important;
}
#tstf-shop .tstf-uni__side-v,
#tstf-shop .tstf-uni__side-sep,
#tstf-shop .tstf-uni__nophoto { color: #4D4840 !important; }
#tstf-shop .tstf-uni__side-v.is-on { color: #111111 !important; }
#tstf-shop .tstf-uni__plate::after { opacity: .55; }

/* ------------------------------------------------ clouds through the page */
/* Every major section gets two low-opacity cloud banks at opposite corners.
   Content is lifted above them and the established colour glows remain. */
#tstf-today::before,
#tstf-table::before,
#tstf-featured::before,
#tstf-shop::before,
#tstf-news::after,
#tstf-latest::before,
#tstf-follow::before,
#tstf-join-table::before {
  content: "";
  position: absolute;
  inset: 0;
  z-index: 0;
  pointer-events: none;
  background:
    url("cloud.png") -7% 92% / clamp(250px, 31vw, 500px) auto no-repeat,
    url("cloud.png") 108% 8% / clamp(220px, 28vw, 460px) auto no-repeat;
  opacity: .105;
  filter: grayscale(1) brightness(1.22) contrast(.86);
  mix-blend-mode: screen;
  animation: tstf-section-cloud-drift 18s ease-in-out infinite alternate;
}
#tstf-table::before,
#tstf-news::after,
#tstf-follow::before {
  background-position: -8% 8%, 108% 92%;
  animation-delay: -7s;
}
#tstf-featured::before,
#tstf-latest::before,
#tstf-join-table::before { animation-delay: -12s; }

#tstf-today > *,
#tstf-table > *,
#tstf-featured > *,
#tstf-shop > *,
#tstf-news > *,
#tstf-latest > *,
#tstf-follow > *,
#tstf-join-table > * {
  position: relative;
  z-index: 1;
}

/* Our Week already owns both pseudo-elements for its cover-cloud treatment;
   simply make those established clouds a little more present. */
#tstf-week::before,
#tstf-week::after {
  opacity: .18 !important;
  filter: grayscale(1) brightness(1.65) contrast(.82) !important;
}

@keyframes tstf-section-cloud-drift {
  from { transform: translate3d(-10px, 0, 0) scale(1); }
  to   { transform: translate3d(12px, -7px, 0) scale(1.025); }
}

@media (prefers-reduced-motion: reduce) {
  #tstf-today::before,
  #tstf-table::before,
  #tstf-featured::before,
  #tstf-shop::before,
  #tstf-news::after,
  #tstf-latest::before,
  #tstf-follow::before,
  #tstf-join-table::before { animation: none; }
}

@media (max-width: 699.98px) {
  #tstf-today::before,
  #tstf-table::before,
  #tstf-featured::before,
  #tstf-shop::before,
  #tstf-news::after,
  #tstf-latest::before,
  #tstf-follow::before,
  #tstf-join-table::before {
    background-size: 270px auto, 240px auto;
    opacity: .085;
  }
}

/* FINAL MINOR FIXES — 2026-09-20 */

/* Remove the red inset stripe from the email panel. The neutral outline and
   white input rule provide enough structure without another accent. */
#tstf-join-table .tstf-jt__panel {
  box-shadow: 0 18px 48px rgba(0,0,0,.30) !important;
}
#tstf-join-table.tstf-jt {
  padding-block: clamp(46px, 5.8vw, 82px) !important;
}

/* Size On Air Today to its content instead of forcing a near-full viewport.
   The artwork keeps impact but no longer creates a large empty stage. */
@media (min-width: 750px) {
  #tstf-today.tstf-drop {
    min-height: 0 !important;
    padding-block: clamp(54px, 6vw, 86px) clamp(42px, 5vw, 70px) !important;
    align-content: start !important;
  }
  #tstf-today .tstf-drop__inner {
    gap: clamp(18px, 2.2vw, 30px) !important;
  }
  #tstf-today .tstf-drop__art {
    inset: clamp(34px, 4vw, 62px) 0 auto auto !important;
    width: clamp(320px, 39vw, 620px) !important;
    height: clamp(360px, 54vh, 540px) !important;
  }
  #tstf-today .tstf-order {
    margin-top: 4px !important;
  }
}

/* Restore the anime artwork's authored colour. A restrained grade keeps it
   inside the dark hub palette without reducing it to monochrome. */
#tstf-today .tstf-drop__art img {
  filter: saturate(.96) contrast(1.12) brightness(.80) !important;
}
#tstf-today .tstf-drop__art::after {
  background: linear-gradient(to top, #0A0A09 0%, rgba(10,10,9,.34) 35%, transparent 68%) !important;
}

@media (max-width: 749px) {
  #tstf-today .tstf-drop__art img {
    filter: saturate(.92) contrast(1.12) brightness(.68) !important;
  }
}



/* =========================================================================
   SKY — MATCHED TO THE HUB, ACROSS THE WHOLE PAGE
   The hub has one sky: the CSS cloud field from fx.js, fixed behind every
   section at full strength. This site had that field PLUS a WebGL layer
   (tst-sky.js, no longer loaded), a rule dimming the CSS clouds to 22%, a
   rule fading both away below the hero, a far-layer blur the hub doesn't
   have, and near-opaque section backgrounds sitting on top of whatever was
   left. All of that is undone here so the clouds drift behind the whole
   page exactly as they do on the hub. fx.js and styles.css are
   byte-identical to the hub's; only the covering layers differ.
   ========================================================================= */

.tst-sky__cloud[data-layer] {
  filter: none;
  opacity: calc(var(--c-op, 0.05) * 1.63);   /* the hub's own multiplier */
}

/* Sections are transparent, as on the hub. Text sits directly on the ink
   with the clouds moving behind it; the clouds are a few percent opaque, so
   every contrast ratio is unchanged. */
.tstf-section, .tstd-section, .tst-hub-section,
.tstf-showcase, .tstf-reel, .tstf-join, .tstf-jt {
  background: none;
}

/* The sky must paint above the page's own background and below content. */
.tst-sky { z-index: 0; }
.tstd-section, .tst-hub-section { position: relative; z-index: 2; }

/* =========================================================================
   TAP-TO-WATCH — watchUrl in data/tst-week.js
   The name is the link; its ::after stretches over the whole row/entry so a
   thumb anywhere on the line opens the stream. Looks identical at rest.
   ========================================================================= */

.tstf-order__row, .tstf-wday__show, .tstf-queue-row { position: relative; }

.tstf-watch { color: inherit; text-decoration: none; }
.tstf-watch::after { content: ""; position: absolute; inset: 0; z-index: 3; }
.tstf-watch:hover,
.tstf-watch:focus-visible { text-decoration: underline; text-underline-offset: 4px; text-decoration-thickness: 1px; }
.tstf-watch:focus-visible { outline: 2px solid var(--f-gold); outline-offset: 3px; }

.tstf-drop__watch {
  display: inline-flex;
  align-items: center;
  align-self: start;
  min-height: 44px;
  margin-top: 6px;
  font-family: var(--f-mono);
  font-size: 11px;
  font-weight: 700;
  letter-spacing: 0.16em;
  text-transform: uppercase;
  color: var(--f-bone);
  text-decoration: none;
  box-shadow: inset 0 -1px 0 var(--f-slate);
}
.tstf-drop__watch:hover,
.tstf-drop__watch:focus-visible { box-shadow: inset 0 -1px 0 var(--f-red); }


/* Sections let the sky through. Declared with !important and at the end of
   the file because the colourway rules above set solid section grounds with
   their own !important; the radial colour washes (background-image) stay, so
   each section keeps its tint — only the opaque base colour goes. */
main > section,
#tstf-today, #tstf-featured, #tstf-week, #tstf-table, #tstf-news,
#tstf-latest, #tstf-follow, #tstf-join-table, #tstf-shop {
  background-color: transparent !important;
}
