/* ==========================================================================
   EMBER & SLATE — Built by Fire
   Scroll-driven frame sequence, art direction layer.

   Structure
     01  Tokens
     02  Reset & base
     03  Cursor
     04  Preloader
     05  Stage (canvas)
     06  Chrome (masthead, HUD, scroll hint)
     07  Captions & typography choreography
     08  Scroll geometry & outro
     09  Responsive
     10  Motion & input preferences

   Palette is pulled straight out of the footage: the near-black of the grill
   housing, the cool grey of wet slate, the ember orange under the grate, and
   the warm off-white of the bun. Nothing here is a generic dark theme.
   ========================================================================== */


/* 01 — Tokens ============================================================= */

:root {
  /* Ground */
  --void:        #030303;
  --ash:         #0a0a0b;
  --smoke:       #14151a;

  /* Ink */
  --ink:         #f4f1ec;
  --ink-strong:  #fffdfa;
  --ink-dim:     rgb(244 241 236 / 0.58);
  --ink-faint:   rgb(244 241 236 / 0.30);
  --ink-ghost:   rgb(244 241 236 / 0.12);

  /* Accent, sampled from the coals */
  --ember:       #ff7a2f;
  --ember-deep:  #c8451a;
  --slate:       #5a636e;

  /* Type */
  --font-display: 'Instrument Serif', 'Iowan Old Style', 'Palatino Linotype',
                  'Times New Roman', serif;
  --font-ui:      'Inter', -apple-system, BlinkMacSystemFont, 'Segoe UI',
                  Roboto, sans-serif;
  --font-mono:    'IBM Plex Mono', ui-monospace, 'SF Mono', 'Cascadia Mono',
                  Consolas, monospace;

  /* Fluid scale */
  --fs-display:  clamp(2.9rem, 7.4vw, 7.25rem);
  --fs-display-s:clamp(2.3rem, 5.4vw, 5rem);
  --fs-body:     clamp(0.95rem, 1.08vw, 1.15rem);
  --fs-label:    clamp(0.62rem, 0.74vw, 0.72rem);

  /* Rhythm */
  --gutter:      clamp(1.5rem, 4.2vw, 4.5rem);
  --edge:        clamp(1.25rem, 2.6vw, 2.75rem);
  /* Horizontal room the HUD scrubber needs on the right. Zeroed on small
     screens, where the HUD is not rendered at all. */
  --hud-clear:   5rem;

  /* Easings. `--ease-out-expo` is the house curve — everything that enters
     the frame uses it so the whole piece shares one sense of weight. */
  --ease-out-expo: cubic-bezier(0.16, 1, 0.3, 1);
  --ease-in-out:   cubic-bezier(0.65, 0, 0.35, 1);

  /* Live values written by JS each frame. Declared here so the cascade has
     something sane before the first tick. */
  --reveal: 0;      /* 0 while preloading, 1 once the film is exposed        */
  --outro-t: 0;     /* 0 in the film, 1 when the outro fully owns the screen */
}


/* 02 — Reset & base ======================================================= */

*, *::before, *::after { box-sizing: border-box; }

html {
  background: var(--void);
  /* The sequence is scrubbed from the raw scroll position, so the browser's
     own smooth-scrolling would fight the spring integrator. */
  scroll-behavior: auto;
  -webkit-text-size-adjust: 100%;
}

body {
  margin: 0;
  min-height: 100%;
  background: var(--void);
  color: var(--ink);
  font-family: var(--font-ui);
  font-size: var(--fs-body);
  font-weight: 300;
  line-height: 1.55;
  letter-spacing: 0.005em;
  -webkit-font-smoothing: antialiased;
  -moz-osx-font-smoothing: grayscale;
  overflow-x: clip;
}

/* Scrollbar is part of the art direction, not an afterthought. */
body { scrollbar-width: thin; scrollbar-color: rgb(244 241 236 / 0.16) transparent; }
body::-webkit-scrollbar { width: 6px; }
body::-webkit-scrollbar-track { background: transparent; }
body::-webkit-scrollbar-thumb {
  background: rgb(244 241 236 / 0.14);
  border-radius: 99px;
}
body::-webkit-scrollbar-thumb:hover { background: rgb(244 241 236 / 0.28); }

h1, h2, p { margin: 0; }

::selection { background: var(--ember); color: var(--void); }

.visually-hidden {
  position: absolute;
  width: 1px; height: 1px;
  margin: -1px; padding: 0;
  overflow: hidden;
  clip-path: inset(50%);
  white-space: nowrap;
  border: 0;
}

.noscript {
  position: fixed; inset: 0;
  display: grid; place-items: center;
  padding: 2rem;
  background: var(--void);
  color: var(--ink-dim);
  text-align: center;
  z-index: 999;
}


/* 03 — Cursor ============================================================= */

.cursor {
  position: fixed;
  inset: 0;
  pointer-events: none;
  z-index: 200;
  /* `difference` lets one cursor read against both the black letterbox and the
     bright cheese without ever needing a state change. */
  mix-blend-mode: difference;
  opacity: 0;
  transition: opacity 0.5s var(--ease-out-expo);
}
.cursor.is-active { opacity: 1; }

.cursor__dot,
.cursor__ring {
  position: absolute;
  top: 0; left: 0;
  border-radius: 50%;
  will-change: transform;
}

.cursor__dot {
  width: 6px; height: 6px;
  margin: -3px 0 0 -3px;
  background: var(--ink);
}

.cursor__ring {
  width: 38px; height: 38px;
  margin: -19px 0 0 -19px;
  border: 1px solid rgb(244 241 236 / 0.55);
  transition: width 0.45s var(--ease-out-expo),
              height 0.45s var(--ease-out-expo),
              margin 0.45s var(--ease-out-expo),
              border-color 0.45s var(--ease-out-expo);
}

.cursor.is-hovering .cursor__ring {
  width: 64px; height: 64px;
  margin: -32px 0 0 -32px;
  border-color: rgb(244 241 236 / 0.9);
}

/* While the wheel is moving the ring collapses toward the dot — the pointer
   physically feels like it is gripping the timeline. */
.cursor.is-scrubbing .cursor__ring {
  width: 20px; height: 20px;
  margin: -10px 0 0 -10px;
  border-color: rgb(244 241 236 / 0.85);
}


/* 04 — Preloader ========================================================== */

.preloader {
  position: fixed;
  inset: 0;
  z-index: 150;
  display: grid;
  grid-template-rows: 1fr auto;
  place-items: center;
  padding: var(--edge);
  background: var(--void);
  transition: opacity 1.05s var(--ease-in-out),
              visibility 0s linear 1.05s;
}
.preloader.is-done {
  opacity: 0;
  visibility: hidden;
  pointer-events: none;
}

.preloader__inner {
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: 1.35rem;
  width: min(30rem, 76vw);
  text-align: center;
  transition: transform 0.9s var(--ease-in-out), opacity 0.6s ease;
}
.preloader.is-done .preloader__inner {
  transform: translate3d(0, -0.75rem, 0);
  opacity: 0;
}

.preloader__brand {
  font-family: var(--font-mono);
  font-size: var(--fs-label);
  letter-spacing: 0.36em;
  text-transform: uppercase;
  color: var(--ink-faint);
}

.preloader__count {
  display: flex;
  align-items: baseline;
  gap: 0.15em;
  font-family: var(--font-display);
  line-height: 0.9;
  color: var(--ink);
}
.preloader__num {
  font-size: clamp(4.5rem, 15vw, 9rem);
  /* Tabular figures stop the counter from jittering as digits change width. */
  font-variant-numeric: tabular-nums;
}
.preloader__pct {
  font-size: clamp(1.1rem, 2.6vw, 1.8rem);
  color: var(--ink-faint);
}

.preloader__rule {
  position: relative;
  width: 100%;
  height: 1px;
  background: var(--ink-ghost);
  overflow: hidden;
}
.preloader__rule-fill {
  position: absolute;
  inset: 0;
  transform: scaleX(0);
  transform-origin: left center;
  background: linear-gradient(90deg, var(--ember-deep), var(--ember));
  /* Driven by JS; the transition smooths the discrete jumps between frames. */
  transition: transform 0.35s var(--ease-out-expo);
}

.preloader__status {
  font-family: var(--font-mono);
  font-size: var(--fs-label);
  letter-spacing: 0.22em;
  text-transform: uppercase;
  color: var(--ink-faint);
  min-height: 1.2em;
  transition: color 0.4s ease;
}
.preloader__status.is-ready { color: var(--ember); }

.preloader__legal {
  align-self: end;
  font-family: var(--font-mono);
  font-size: 0.6rem;
  letter-spacing: 0.22em;
  text-transform: uppercase;
  color: rgb(244 241 236 / 0.16);
}


/* 05 — Stage ============================================================== */

.stage {
  position: fixed;
  inset: 0;
  z-index: 0;
  background: var(--void);
  /* Held back until the sequence is exposed, then eased in by --reveal. */
  opacity: var(--reveal);
  transition: opacity 1.4s var(--ease-in-out);
}

.stage__canvas {
  display: block;
  width: 100%;
  height: 100%;
  /* The canvas backing store is sized in JS at device pixel ratio; these are
     purely the CSS box. */
}

/* WebGL does its own vignette in the composite pass. This element only comes
   alive when the Canvas2D fallback is in use. */
.stage__vignette {
  position: absolute;
  inset: 0;
  pointer-events: none;
  opacity: 0;
  background:
    radial-gradient(ellipse 78% 68% at 50% 46%,
      transparent 38%,
      rgb(3 3 3 / 0.42) 78%,
      rgb(3 3 3 / 0.86) 100%);
}
.stage__vignette.is-active { opacity: 1; }


/* 06 — Chrome ============================================================= */

/* Everything in the chrome layer fades in with the film and out with the
   outro, so the final card is left completely undressed.

   The 2.4x multiplier retires the chrome by the time the outro is 40% arrived,
   rather than tracking it linearly to zero. A linear fade leaves the masthead
   and scrubber sitting at 2-3% opacity over the closing card — not invisible,
   just faintly, unaccountably present, which reads as a bug rather than as
   restraint. */
.masthead,
.hud,
.scroll-hint {
  opacity: calc(var(--reveal) * clamp(0, (1 - var(--outro-t)) * 2.4, 1));
}

.masthead {
  position: fixed;
  top: 0; left: 0; right: 0;
  z-index: 60;
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 1rem;
  padding: var(--edge);
  transition: opacity 1s var(--ease-in-out);
  mix-blend-mode: difference;
}

.masthead__brand {
  display: inline-flex;
  align-items: center;
  gap: 0.65rem;
  font-family: var(--font-mono);
  font-size: var(--fs-label);
  font-weight: 500;
  letter-spacing: 0.26em;
  text-transform: uppercase;
  color: var(--ink);
  text-decoration: none;
}

.masthead__mark {
  width: 7px; height: 7px;
  border-radius: 50%;
  background: var(--ember);
  /* A slow ember pulse. Deliberately off the beat of anything else so the page
     never feels metronomic. */
  animation: ember-pulse 3.4s var(--ease-in-out) infinite;
}

@keyframes ember-pulse {
  0%, 100% { opacity: 1;    box-shadow: 0 0 0 0 rgb(255 122 47 / 0.45); }
  50%      { opacity: 0.55; box-shadow: 0 0 0 6px rgb(255 122 47 / 0); }
}

.masthead__meta {
  font-family: var(--font-mono);
  font-size: var(--fs-label);
  letter-spacing: 0.22em;
  text-transform: uppercase;
  color: var(--ink-faint);
}

/* --- HUD / film-strip scrubber ------------------------------------------ */

.hud {
  position: fixed;
  right: var(--edge);
  top: 50%;
  transform: translateY(-50%);
  z-index: 60;
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: 1.1rem;
  transition: opacity 1s var(--ease-in-out);
}

.hud__label {
  font-family: var(--font-mono);
  font-size: 0.58rem;
  letter-spacing: 0.28em;
  text-transform: uppercase;
  color: var(--ink-dim);
  /* Rotated so the chapter name runs along the scrubber like a film edge. */
  writing-mode: vertical-rl;
  transition: color 0.5s ease;
}

.hud__track {
  position: relative;
  width: 1px;
  height: min(34vh, 20rem);
  background: var(--ink-ghost);
}
/* The track is one pixel wide; the pseudo-element is what the pointer hits. */
.hud.is-seekable { pointer-events: auto; }
.hud.is-seekable .hud__track { cursor: pointer; touch-action: none; }
.hud.is-seekable .hud__track::before {
  content: '';
  position: absolute;
  inset: -0.6rem -1.1rem;
}
.hud.is-seekable .hud__track:hover .hud__head {
  box-shadow: 0 0 14px 2px rgb(255 122 47 / 0.75);
}

.hud__ticks {
  position: absolute;
  inset: 0;
  display: flex;
  flex-direction: column;
  justify-content: space-between;
  align-items: center;
}
.hud__ticks span {
  width: 5px;
  height: 1px;
  background: var(--ink-ghost);
}
/* Every fifth tick is a chapter mark. */
.hud__ticks span.is-major {
  width: 11px;
  background: var(--ink-faint);
}

.hud__progress {
  position: absolute;
  top: 0; left: 0;
  width: 1px;
  height: 100%;
  transform: scaleY(0);
  transform-origin: top center;
  background: linear-gradient(180deg, var(--ember), var(--ember-deep));
}

.hud__head {
  position: absolute;
  top: 0; left: 50%;
  width: 15px; height: 1px;
  margin-left: -7.5px;
  background: var(--ember);
  box-shadow: 0 0 10px 1px rgb(255 122 47 / 0.55);
  will-change: transform;
}

.hud__timecode {
  font-family: var(--font-mono);
  font-size: 0.62rem;
  font-weight: 500;
  letter-spacing: 0.1em;
  color: var(--ink-dim);
  font-variant-numeric: tabular-nums;
}

/* --- Scroll hint --------------------------------------------------------- */

.scroll-hint {
  position: fixed;
  left: 50%;
  bottom: calc(var(--edge) + 0.25rem);
  transform: translateX(-50%);
  z-index: 60;
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: 0.7rem;
  transition: opacity 0.7s var(--ease-in-out);
}
/* Once the user has committed to scrolling, the hint retires for good. */
.scroll-hint.is-retired { opacity: 0 !important; }

.scroll-hint__label {
  font-family: var(--font-mono);
  font-size: 0.58rem;
  letter-spacing: 0.32em;
  text-transform: uppercase;
  color: var(--ink-faint);
}

.scroll-hint__line {
  position: relative;
  width: 1px;
  height: 46px;
  background: var(--ink-ghost);
  overflow: hidden;
}
.scroll-hint__line::after {
  content: '';
  position: absolute;
  top: 0; left: 0;
  width: 1px; height: 40%;
  background: linear-gradient(180deg, transparent, var(--ember));
  animation: hint-run 2.1s var(--ease-in-out) infinite;
}

@keyframes hint-run {
  0%        { transform: translateY(-100%); }
  55%, 100% { transform: translateY(250%); }
}


/* 07 — Captions =========================================================== */

.captions {
  position: fixed;
  inset: 0;
  z-index: 40;
  pointer-events: none;
  padding: calc(var(--edge) * 2.6) var(--gutter);
  /* Scrims bleed well past their beat; this trims them at the viewport. */
  overflow: clip;
}

.beat {
  position: absolute;
  width: min(24rem, 38vw);
  /* --t is the beat's own 0..1 envelope, written by JS every frame.
     Everything below is derived from it, so the whole block reveals and
     retreats as one system while individual characters keep their stagger. */
  --t: 0;
  /* Beats are only painted while they have presence. `visibility` keeps them
     out of hit-testing and off the compositor when idle. */
  visibility: hidden;
  will-change: transform, opacity;
}
.beat.is-live { visibility: visible; }

/* Placement.

   The plate holds dead centre from roughly 40% of the film onward and the
   burger itself spans about 28%-72% of the frame width, so side beats sit in
   the outer quarters where the footage stays dark. Right-side beats also have
   to clear the HUD scrubber, which lives inside the edge margin — without the
   extra offset the headline's final period lands on the scrubber track. */
.beat--center {
  left: 50%; top: 50%;
  width: min(46rem, 84vw);
  transform: translate3d(-50%, calc(-50% + (1 - var(--t)) * 2.2rem), 0);
  text-align: center;
}
.beat--top-left {
  left: var(--gutter); top: calc(var(--edge) * 4.5);
  transform: translate3d(calc((1 - var(--t)) * -1.6rem), 0, 0);
}
.beat--left {
  left: var(--gutter); top: 50%;
  transform: translate3d(calc((1 - var(--t)) * -1.6rem), -50%, 0);
}
.beat--right {
  right: calc(var(--gutter) + var(--hud-clear)); top: 50%;
  text-align: right;
  transform: translate3d(calc((1 - var(--t)) * 1.6rem), -50%, 0);
}

/* --- Scrims --------------------------------------------------------------

   Type over food photography needs ground under it. Rather than a box behind
   each caption, each side beat casts a wide directional gradient that reads as
   the vignette deepening on that side of the frame — the same device a film
   title uses to hold a lower third over a busy plate. It fades with the beat,
   so an empty screen never carries an unexplained dark edge.

   The bleed is deliberately enormous: a gradient that ends anywhere near the
   text shows its own edge, which is far more visible than the overlap it was
   added to solve. */
.beat::before {
  content: '';
  position: absolute;
  z-index: -1;
  pointer-events: none;
  opacity: var(--t);
}

.beat--left::before,
.beat--top-left::before {
  /* 100vh of vertical bleed guarantees the gradient's own top and bottom edges
     are always off-screen. At 30vh they were not: a mid-height beat is only a
     few hundred pixels tall, so the scrim ended inside the viewport and left a
     faint horizontal seam across the frame. `overflow: clip` on the captions
     layer trims all of this at the viewport, so the excess costs nothing. */
  inset: -100vh 0 -100vh -60vw;
  background: linear-gradient(90deg,
    rgb(3 3 3 / 0.88) 0%,
    rgb(3 3 3 / 0.86) 58%,
    rgb(3 3 3 / 0.42) 80%,
    transparent 100%);
}

.beat--right::before {
  inset: -100vh -60vw -100vh 0;
  background: linear-gradient(270deg,
    rgb(3 3 3 / 0.88) 0%,
    rgb(3 3 3 / 0.86) 58%,
    rgb(3 3 3 / 0.42) 80%,
    transparent 100%);
}

/* The title card sits over the empty slate, which is already dark. It needs a
   whisper of separation, not a wall. */
.beat--center::before {
  inset: -24vh -24vw;
  background: radial-gradient(ellipse 52% 48% at 50% 50%,
    rgb(3 3 3 / 0.62) 0%,
    rgb(3 3 3 / 0.34) 52%,
    transparent 78%);
}

/* --- Beat internals ------------------------------------------------------ */

.beat__index {
  font-family: var(--font-mono);
  font-size: 0.6rem;
  font-weight: 500;
  letter-spacing: 0.3em;
  color: var(--ember);
  margin-bottom: 0.9rem;
  opacity: var(--t);
}

.beat__eyebrow {
  font-family: var(--font-mono);
  font-size: var(--fs-label);
  letter-spacing: 0.34em;
  text-transform: uppercase;
  color: var(--ink-dim);
  margin-bottom: 1.05rem;
}

.beat__display {
  font-family: var(--font-display);
  font-weight: 400;
  font-size: var(--fs-display-s);
  line-height: 0.98;
  letter-spacing: -0.018em;
  color: var(--ink-strong);
  /* A soft ambient glow so the type sits *in* the scene rather than pasted
     on top of it. Tightened by --t so it blooms in as the line arrives. */
  text-shadow: 0 0 calc(2.5rem * var(--t)) rgb(3 3 3 / 0.85),
               0 1px 2px rgb(3 3 3 / 0.7);
}
.beat--center .beat__display {
  font-size: var(--fs-display);
  /* The 0.98 line-height pulls the box in tighter than the glyphs, so
     descenders in the display face overhang it. The generous bottom margin is
     what keeps "by" clear of the line beneath. */
  margin-block: 0.35rem 2rem;
}
.beat:not(.beat--center) .beat__display { margin-bottom: 1.15rem; }

.beat__body {
  max-width: 21rem;
  font-size: var(--fs-body);
  line-height: 1.62;
  color: var(--ink-dim);
  text-shadow: 0 1px 8px rgb(3 3 3 / 0.75);
  text-wrap: pretty;
}
.beat--center .beat__body {
  max-width: 36rem;
  margin-inline: auto;
  /* Keeps the strapline on one line at desktop widths, and evenly split rather
     than leaving "assembly." alone on a second line when it does wrap. */
  text-wrap: balance;
}
.beat--right .beat__body { margin-left: auto; }

/* --- Split text ---------------------------------------------------------- */

/* Words are the line-break unit; characters are the animation unit. Keeping
   both means the stagger never causes a mid-word wrap. */
.split-word {
  display: inline-block;
  white-space: nowrap;
}
.split-char,
.split-unit {
  display: inline-block;
  /* --l is this glyph's own local progress, 0..1, written by JS with a
     stagger applied. Every visual property below hangs off it. */
  --l: 0;
  opacity: var(--l);
  transform: translate3d(0, calc((1 - var(--l)) * 0.55em), 0);
  filter: blur(calc((1 - var(--l)) * 7px));
  will-change: transform, opacity, filter;
}
/* Words animate as blocks in body copy — per-character blur on 20 words of
   small text is noise, not craft. */
.split-unit {
  transform: translate3d(0, calc((1 - var(--l)) * 0.7em), 0);
  filter: blur(calc((1 - var(--l)) * 4px));
}
.split-space { display: inline-block; width: 0.26em; }


/* 08 — Scroll geometry & outro ============================================ */

.track {
  /* The runway. Every 100vh here is roughly one act of the film.
     760vh over 240 frames lands near 27px of scroll per frame at 1080p —
     enough that a single wheel notch never jumps a whole ingredient. */
  height: 760vh;
  position: relative;
}

.outro {
  position: relative;
  z-index: 50;
  min-height: 100vh;
  display: flex;
  flex-direction: column;
  justify-content: space-between;
  padding: calc(var(--edge) * 2) var(--gutter) var(--edge);
  /* Opaque ground so the dissolving canvas has something to resolve into.
     Reaches full opacity early — the canvas is still faintly alive behind the
     first slice of this section, and the closing card wants clean black. */
  background:
    linear-gradient(180deg, rgb(3 3 3 / 0) 0%, var(--void) 11%, var(--void) 100%);
}

.outro__inner {
  flex: 1;
  display: flex;
  flex-direction: column;
  justify-content: center;
  gap: 1.6rem;
  max-width: 62rem;
  margin-inline: auto;
  width: 100%;
  text-align: center;
  /* Rises as the section takes the screen. */
  opacity: var(--outro-t);
  transform: translate3d(0, calc((1 - var(--outro-t)) * 2.5rem), 0);
}

.outro__eyebrow {
  font-family: var(--font-mono);
  font-size: var(--fs-label);
  letter-spacing: 0.34em;
  text-transform: uppercase;
  color: var(--ink-faint);
}

.outro__display {
  font-family: var(--font-display);
  font-size: clamp(3rem, 10vw, 9rem);
  line-height: 0.92;
  letter-spacing: -0.03em;
  color: var(--ink-strong);
}
.outro__amp {
  display: inline-block;
  margin-inline: 0.18em;
  font-style: italic;
  color: var(--ember);
}

.outro__rule {
  width: 100%;
  height: 1px;
  background: linear-gradient(90deg,
    transparent, var(--ink-ghost) 22%, var(--ink-ghost) 78%, transparent);
  margin-block: 1rem;
}

.outro__grid {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(11rem, 1fr));
  gap: 2rem 1.5rem;
  text-align: left;
}
.outro__key {
  font-family: var(--font-mono);
  font-size: 0.58rem;
  letter-spacing: 0.28em;
  text-transform: uppercase;
  color: var(--ink-faint);
  margin-bottom: 0.5rem;
}
.outro__val {
  font-size: 0.92rem;
  color: var(--ink-dim);
  font-variant-numeric: tabular-nums;
}

.outro__cta {
  display: flex;
  align-items: center;
  justify-content: center;
  gap: 1.6rem;
  flex-wrap: wrap;
  margin-top: 0.4rem;
}
.outro__btn {
  display: inline-flex;
  align-items: center;
  padding: 0.95rem 1.9rem;
  border: 1px solid var(--ember);
  border-radius: 999px;
  color: var(--ink-strong);
  font-family: var(--font-mono);
  font-size: var(--fs-label);
  letter-spacing: 0.28em;
  text-transform: uppercase;
  text-decoration: none;
  transition: background 0.5s var(--ease-out-expo), color 0.5s var(--ease-out-expo),
              box-shadow 0.5s var(--ease-out-expo);
}
.outro__btn:hover {
  background: var(--ember);
  color: var(--void);
  box-shadow: 0 18px 40px -18px rgb(255 122 47 / 0.8);
}
.outro__link {
  font-family: var(--font-mono);
  font-size: var(--fs-label);
  letter-spacing: 0.28em;
  text-transform: uppercase;
  color: var(--ink-dim);
  text-decoration: none;
  border-bottom: 1px solid var(--ink-ghost);
  padding-bottom: 0.2rem;
  transition: color 0.4s ease, border-color 0.4s ease;
}
.outro__link:hover { color: var(--ink-strong); border-color: var(--ember); }
.outro__credit {
  color: rgb(244 241 236 / 0.35);
  text-decoration: none;
  transition: color 0.4s ease;
}
.outro__credit:hover { color: var(--ember); }

.outro__foot {
  display: flex;
  justify-content: space-between;
  gap: 1rem;
  padding-top: 2rem;
  font-family: var(--font-mono);
  font-size: 0.58rem;
  letter-spacing: 0.2em;
  text-transform: uppercase;
  color: rgb(244 241 236 / 0.2);
  opacity: var(--outro-t);
}


/* 09 — Responsive ========================================================= */

@media (max-width: 900px) {
  /* No HUD at this size, so nothing to steer around. */
  :root { --hud-clear: 0rem; }

  /* Beats stop competing with the plate for the middle of the frame and take
     the lower third, which stays dark for the whole sequence. */
  .beat--left,
  .beat--right,
  .beat--top-left {
    left: var(--gutter);
    right: var(--gutter);
    top: auto;
    bottom: calc(var(--edge) * 5);
    width: auto;
    text-align: left;
  }

  /* Full-width beats need the scrim to rise from the bottom edge instead of
     sweeping in from a side. */
  .beat--left::before,
  .beat--right::before,
  .beat--top-left::before {
    inset: -14vh -50vw -60vh -50vw;
    background: linear-gradient(0deg,
      rgb(3 3 3 / 0.9) 0%,
      rgb(3 3 3 / 0.86) 46%,
      rgb(3 3 3 / 0.4) 74%,
      transparent 100%);
  }
  .beat--left,
  .beat--top-left { transform: translate3d(calc((1 - var(--t)) * -1.1rem), 0, 0); }
  .beat--right    { transform: translate3d(calc((1 - var(--t)) *  1.1rem), 0, 0); }
  .beat--right .beat__body { margin-left: 0; }

  .beat__body { max-width: 32rem; }
  .beat--center .beat__body { max-width: 26rem; }

  .hud { display: none; }
  .masthead__meta { display: none; }
  .track { height: 620vh; }
}

@media (max-width: 560px) {
  :root { --fs-display: clamp(2.4rem, 11vw, 3.6rem); --fs-display-s: clamp(2rem, 9vw, 3rem); }
  .beat--center { width: min(30rem, 88vw); }
  .outro__grid { text-align: center; }
}

/* Short landscape windows (laptops at 100% zoom, phones on their side) need
   the caption block pulled off the vertical centre or it collides with the
   plate. */
@media (max-height: 620px) and (min-width: 901px) {
  .beat { width: min(28rem, 46vw); }
  .beat__body { font-size: 0.88rem; }
  .beat--center .beat__display { margin-block: 0.2rem 0.8rem; }
}


/* 10 — Motion & input preferences ========================================= */

/* Touch pointers get no custom cursor and no hover states. */
@media (hover: none), (pointer: coarse) {
  .cursor { display: none; }
}

@media (prefers-reduced-motion: reduce) {
  /* Scrubbing is user-driven, so the sequence itself stays. What goes is
     everything that moves on its own: pulses, runners, blur transitions. */
  .masthead__mark { animation: none; }
  .scroll-hint__line::after { animation: none; opacity: 0.5; }
  .cursor { display: none; }

  .split-char,
  .split-unit {
    filter: none;
    transform: none;
  }

  .beat--center     { transform: translate3d(-50%, -50%, 0); }
  .beat--left       { transform: translate3d(0, -50%, 0); }
  .beat--right      { transform: translate3d(0, -50%, 0); }
  .beat--top-left   { transform: none; }

  .outro__inner { transform: none; }

  *, *::before, *::after {
    transition-duration: 0.01ms !important;
    animation-duration: 0.01ms !important;
  }
}
