/* Burning Static — the Title Card system, round 13. DESIGN.md (repo root) is
   the locked system; this file implements it and nothing else.
   Round 12 (Ross, 2026-10-04): the two covers are the type tiles. EVERY word
   is the No Safe Frequency fire orange (--fire, sampled from that cover's
   title lettering); no white text anywhere.
   Round 13 (Ross, 2026-10-04): the hero IS the video, one square film; every
   heading is RT Studio lettering (images, real text kept); Geist for
   everything read or pressed. Zero outside requests: fonts self-hosted. */

@font-face {
  font-family: "Instrument Serif";
  src: url("../fonts/InstrumentSerif-Regular.woff2") format("woff2");
  font-weight: 400;
  font-style: normal;
  font-display: swap;
}
@font-face {
  font-family: "Geist";
  src: url("../fonts/Geist-Variable.woff2") format("woff2");
  font-weight: 100 900;
  font-style: normal;
  font-display: swap;
}

:root {
  --ground: #0b0a09;  /* soot */
  /* round 12: the ONE text colour. Sampled (canvas, mean of 31,000 fire
     pixels) from the "NO SAFE FREQUENCY" title on the single's cover.
     6.40:1 on --ground (WCAG formula, computed in-page). */
  --fire:   #f7650e;
  /* The Corrupted System title's gradient top (sampled the same way,
     10.78:1); the gradient runs gold to --fire. */
  --gold:   #fab21d;
  --rule:   #2a2622;
  --edge:   #3a3530;

  --display: "Instrument Serif", "Times New Roman", serif;
  --sans: "Geist", "Helvetica Neue", Arial, sans-serif;

  --t-title:    clamp(3.25rem, 1rem + 6.2vw, 8rem);
  --t-statement: clamp(2.75rem, 1rem + 4.6vw, 6rem);
  --t-label:     clamp(2.25rem, 1.9rem + 1vw, 3rem);
  /* round 10: reading sizes raised for desktop (Ross: "reads like a caption"
     at 1440). 1440 → hook ≈ 31 px, body ≈ 20 px, small ≈ 17.5 px; phones keep
     their floors (hook 20, body 16, small 15). */
  --t-hook:      clamp(1.25rem, 0.85rem + 1.25vw, 2rem);
  --t-body:      clamp(1rem, 0.86rem + 0.42vw, 1.25rem);
  --t-small:     clamp(0.9375rem, 0.82rem + 0.33vw, 1.125rem);
  --t-ui:        clamp(0.9375rem, 0.86rem + 0.25vw, 1.0625rem);

  /* Round 14 (Ross, 2026-10-10: "optimized for mobile and regular display;
     for ultrawide, fillers on the side, seamless and flawless"):
     --frame-w is the page's width as a LENGTH: the viewport, except on
     ultrawide screens (wider than 2.2:1) where the page is one 16:9 frame
     matched to the hero's stage and the circuit fillers take the sides.
     --read caps how wide the words spread inside any frame. The gutter grows
     to centre the words once the frame is wider than --read; min(100%, …)
     keeps it right on the bar and footer, whose containing block is the body. */
  --frame-w: 100vw;
  --read: 110rem;
  --gutter: max(clamp(1.25rem, 4vw, 4rem), calc((min(100%, var(--frame-w)) - var(--read)) / 2));
  /* round 10: tighter — every screen carries art or words, not soot */
  --section: clamp(3.5rem, 6vw, 6rem);
  --measure: 40rem;
}

/* ---------- the frame (round 14) + the ultrawide sides (round 14b) ----------
   ULTRAWIDE (wider than 2.2:1): the page is one 16:9 frame matched to the
   hero's stage. Its sides are the hero's AMBIENT (film.js: one fixed layer
   under the bar, mirrored out from the square — the glow behind the square
   inside the frame and the sides outside it are one picture). So the film
   area is transparent and every section carries its own soot (z-index 1):
   they cover the glow inside the frame as they scroll in, the sides keep it
   — and the soot is REQUIRED anyway (the lettering blend rule above). */
@media (min-aspect-ratio: 11/5) {
  :root { --frame-w: calc((100svh - var(--bar-h, 3.5rem)) * 16 / 9); }
  .bar, main, .footer { max-width: var(--frame-w); margin-inline: auto; }
  .bar, .footer, main > section:not(.film) {
    position: relative; z-index: 1; background: var(--ground);
    /* a soft soot fringe past the frame's edge: a section dissolves into the
       glow instead of ending on a line (the album cover sits flush right) */
    box-shadow: 0 0 6rem 3rem var(--ground);
  }
  main > .film { background: none; }   /* beats the base .film soot further down */
}

/* ---------- base ---------- */
*, *::before, *::after { box-sizing: border-box; }
html { -webkit-text-size-adjust: 100%; text-size-adjust: 100%; background: var(--ground); scroll-padding-top: calc(var(--bar-h, 3.5rem) + 1rem); }
body {
  margin: 0;
  background: var(--ground);
  color: var(--fire);
  font-family: var(--sans);
  font-size: var(--t-body);
  line-height: 1.55;
  -webkit-font-smoothing: antialiased;
}
img, video, canvas { display: block; max-width: 100%; }
img, video { height: auto; }
h1, h2, h3, h4, p, figure, dl, dd { margin: 0; }
h1, h2, h3, h4 { font-weight: 400; }
a { color: var(--fire); text-underline-offset: 0.25em; text-decoration-thickness: 1px; }
a:hover { text-decoration-thickness: 2px; }
/* focus: a 2px ring in the one colour, offset clear of the word — the shape
   is the signal (everything is orange, so colour alone could not be) */
:focus-visible { outline: 2px solid var(--fire); outline-offset: 4px; }
.nowrap { white-space: nowrap; }

.sr-only {
  position: absolute; width: 1px; height: 1px; padding: 0; margin: -1px;
  overflow: hidden; clip: rect(0 0 0 0); white-space: nowrap; border: 0;
}
.skip {
  position: absolute; left: 1rem; top: -4rem; z-index: 20;
  padding: 0.6rem 1rem; background: var(--ground); color: var(--fire); border: 2px solid var(--fire);
  font-weight: 500; font-size: 0.9375rem;
}
.skip:focus { top: 1rem; }

/* ---------- the title voice + lettering slots ----------
   Every headline is a slot: real serif text now; a lettering image later
   (.slot--art + img.slot__art, screened onto the soot — lettering arrives on
   pure black). The text stays in the HTML either way. */
.slot, .title, .legal__title {
  font-family: var(--display);
  font-weight: 400;
  font-synthesis: none;
  line-height: 0.95;
}
.slot--art .slot__text {
  position: absolute; width: 1px; height: 1px; padding: 0; margin: -1px;
  overflow: hidden; clip: rect(0 0 0 0); white-space: nowrap; border: 0;
}
/* Round 11: the lettering is in (RT Studio, gpt_image_2_5_sunburst, Ross's
   pick). Each render is 2400x1350 on PURE BLACK (served as 1600x900 web copies, Cisco 2026-10-04 — same 16:9, so the crop numbers hold); .slot__crop is a window
   onto the lettering's own box (--cx/--cy = where the box starts, --cw/--ch
   = its size, all as fractions of the render), so the empty black never
   takes layout. The image is screen-blended: black adds nothing, so there is
   no box edge over soot. RULE: a lettering slot
   never sits inside a stacking context without a soot background — an
   isolated group with a transparent backdrop paints the black as a box. */
.slot__crop { display: none; position: relative; overflow: hidden; max-width: 100%; }
.slot--art .slot__crop {
  display: block;
  width: var(--lw, 100%);
  aspect-ratio: calc(var(--cw) * 2400) / calc(var(--ch) * 1350);
}
.slot__art {
  position: absolute; max-width: none;
  width: calc(100% / var(--cw)); height: auto;
  left: calc(-100% * var(--cx) / var(--cw));
  top: calc(-100% * var(--cy) / var(--ch));
  mix-blend-mode: screen;
}
.slot--art.slot--label, .slot--art.slot--statement { max-width: none; }
/* a lettering slot holds no in-flow text, so a shrink-to-fit parent (the
   masthead grid's justify-items: start) would size it to 0: stretch it */
.slot--art { justify-self: stretch; }
/* The four labels share one CAP HEIGHT (--cap), so they read as one set
   whatever the word length: width = cap / (cap height as a fraction of the
   crop width, measured per render). Phones: cap 52 px. 1440: ≈ 106 px. */
.slot--art { --cap: clamp(3.25rem, 1.75rem + 5.4vw, 7rem); }
.slot--l-single    { --cx: .015; --cy: .26;  --cw: .97;  --ch: .44;  --lw: calc(var(--cap) / .157); }
.slot--l-album     { --cx: .015; --cy: .235; --cw: .97;  --ch: .505; --lw: calc(var(--cap) / .229); }
.slot--l-listen    { --cx: .035; --cy: .125; --cw: .927; --ch: .715; --lw: calc(var(--cap) / .345); }
.slot--l-contact   { --cx: .02;  --cy: .19;  --cw: .965; --ch: .59;  --lw: calc(var(--cap) / .295); }
/* two-line statements: sized by width */
/* round 12: the single's title in the cover's own molten lettering
   (lettering-nsf.webp, 2400x1350 on black), sized by width */
.slot--l-nsf       { --cx: .017; --cy: .28;  --cw: .968; --ch: .375; --lw: min(100%, 46rem); }
.slot--l-statement { --cx: .005; --cy: .12;  --cw: .985; --ch: .765; --lw: min(100%, 52rem); }
/* round 13: The Corrupted System lettering (RT Studio, 1600x900 on black —
   16:9 like the others, so the same crop maths; boxes measured by script,
   .mc-snaps/measure13.html, lettering pixels > 28/255 plus 6 px). THE
   ALBUM joins the label set by cap height (cap ≈ .163 of its crop width);
   the album title fills its column. */
.slot--t-album { --cx: .067; --cy: .328; --cw: .861; --ch: .306; --lw: min(100%, calc(var(--cap) / .163)); }
.slot--t-title { --cx: .036; --cy: .36;  --cw: .93;  --ch: .224; --lw: min(100%, 46rem); }
/* the seven track names share ONE HEIGHT (--th), set by the narrowest
   column the longest name must fit (THE CORRUPTED SYSTEM, 7.38 wide per
   unit of height): width = height × the crop's own aspect */
.track__words { container-type: inline-size; }
.slot--tr { --th: min(2.5rem, 100cqi / 7.45); justify-self: start; }
.slot--tr .slot__crop { width: calc(var(--th) * var(--a)); }
.slot--tr-1 { --cx: .036; --cy: .36;  --cw: .93;  --ch: .224; --a: 7.381; }
.slot--tr-2 { --cx: .043; --cy: .352; --cw: .912; --ch: .246; --a: 6.591; }
.slot--tr-3 { --cx: .028; --cy: .359; --cw: .945; --ch: .233; --a: 7.21; }
.slot--tr-4 { --cx: .027; --cy: .341; --cw: .948; --ch: .271; --a: 6.219; }
.slot--tr-5 { --cx: .049; --cy: .361; --cw: .903; --ch: .247; --a: 6.499; }
.slot--tr-6 { --cx: .028; --cy: .364; --cw: .943; --ch: .247; --a: 6.787; }
.slot--tr-7 { --cx: .029; --cy: .298; --cw: .941; --ch: .364; --a: 4.596; }
.slot--label {
  font-size: var(--t-label);
  letter-spacing: 0.04em;
  text-transform: uppercase;
  color: var(--fire);
}
/* a release title waiting on (or falling back from) its lettering */
.slot--release { font-size: var(--t-title); line-height: 0.92; color: var(--fire); }
.slot--art.slot--release { max-width: none; }

/* ---------- bar ---------- */
/* round 13: sticky, so Pause stays in reach while the film is pinned under
   it (Sol); the film's stage pins at --bar-h, so the bar never covers it */
.bar {
  position: sticky; top: 0; z-index: 6;
  display: flex; align-items: center; justify-content: space-between; gap: 1rem;
  min-height: 3.5rem;
  padding: 0.35rem var(--gutter);
  background: var(--ground);
}
.bar ul { display: flex; flex-wrap: wrap; gap: 0.25rem clamp(0.6rem, 2.5vw, 1.75rem); margin: 0; padding: 0; list-style: none; }
.bar a {
  display: inline-block; padding: 0.6rem 0;
  font-size: var(--t-ui); font-weight: 500; text-decoration: none;
}
.bar a:hover { text-decoration: underline; }
/* ROUND 16 (Ross 2026-10-12: "every other buttons need to match the same
   style so all the buttons on the page"): EVERY BUTTON IS FIRE ART — a
   molten slab with its word cut through (nav, IGNITE / BURNING) or a molten
   disc with its mark cut through (Pause, the player), the same lava as the
   Spotify / YouTube icons, cut out with real transparency (alpha from
   brightness, so a filter or flicker never shows a black box — a screen
   blend did). The words ride as alt / screen-reader text. Headings stay fire LETTERING on black, so a
   button never reads as a title. Sized so the phone bar (three slabs + the
   Pause disc) fits a 360 px screen in one row. */
.bar a.navbtn { display: inline-grid; place-items: center; min-height: 2.75rem; padding: 0; transition: transform 160ms ease-out; }
.bar a.navbtn:hover { text-decoration: none; transform: scale(1.05); }
.navbtn img { height: min(2.4rem, 8.3vw); width: auto; max-width: none; }
.pause {
  flex: none; display: inline-grid; place-items: center;
  width: 2.75rem; height: 2.75rem; padding: 0;
  background: none; border: 0; border-radius: 50%; cursor: pointer;
  transition: transform 160ms ease-out;
}
.pause:hover { transform: scale(1.06); }
.pause__art { grid-area: 1 / 1; width: 2.5rem; height: 2.5rem; max-width: none; }
.pause[hidden] { display: none; }   /* the grid above must not un-hide it (Sol 16 r1 #1) */
.pause__art--play, .pause[data-paused] .pause__art { opacity: 0; }
.pause[data-paused] .pause__art--play { opacity: 1; }
@media (prefers-reduced-motion: reduce) { .bar a.navbtn, .pause { transition: none; } .bar a.navbtn:hover, .pause:hover { transform: none; } }
/* art that failed to load (site.js artFallback): the control shows its
   word, in the page's own type, inside a plain fire outline */
.art-failed > img { display: none; }
.navbtn__text,
.hear.art-failed .hear__verb, .pause.art-failed .sr-only, .player__next.art-failed .sr-only {
  position: static; width: auto; height: auto; margin: 0; overflow: visible; clip: auto; white-space: nowrap;
  display: inline-block; padding: 0.45rem 0.8rem; border: 2px solid var(--fire);
  font-family: var(--sans); font-size: var(--t-small); font-weight: 600; letter-spacing: 0.08em;
  text-transform: uppercase; color: var(--fire);
}
.pause.art-failed, .player__toggle.art-failed, .player__next.art-failed, .player__close.art-failed { width: auto; min-width: 3rem; border-radius: 0; }
.player__toggle.art-failed::after, .player__close.art-failed::after {
  content: "Play"; padding: 0.45rem 0.6rem; border: 2px solid var(--fire);
  font-family: var(--sans); font-size: var(--t-small); font-weight: 600; letter-spacing: 0.08em; text-transform: uppercase; color: var(--fire);
}
.player--on .player__toggle.art-failed::after { content: "Pause"; }
.player__close.art-failed::after { content: "Close"; }
.player__toggle.art-failed:disabled::after { opacity: 0.45; border-style: dashed; }   /* reads disabled too (Sol 16 r2 #2) */

/* ---------- the hero: ONE SQUARE (round 13) ----------
   Ross, 2026-10-04: the hero IS the video, no HTML logo, no tagline. Every
   layer is the same square, centred on soot, CONTAINED, never cropped:
   the hero loop, the film's frames, the real cover, the cover alive.
   STILL MODE (default; reduced motion, no script): the hero's first frame
   (the official logo on its board), then the cover, each a whole square.
   SCRUB MODE (motion allowed + script on): one stage pinned under the bar,
   the scroll plays the square (film.js). */
.film { position: relative; background: var(--ground); }
.film__stage { position: relative; }
.film__square {
  /* the bar's height (film.js measures it; 3.5rem until then) */
  width: min(100%, calc(100svh - var(--bar-h, 3.5rem)));
  margin: 0 auto;
  /* the anchor for the still-mode cover alive (.vslot--wide sits over the
     cover, the square's last block) — never the full-width stage (Sol r2) */
  position: relative;
}
.film__hero, .film__cover { position: relative; aspect-ratio: 1 / 1; overflow: hidden; background: var(--ground); }
.film__still, .film__cover img { width: 100%; height: 100%; object-fit: contain; }
.film__canvas { display: none; }

/* ---------- video slots ----------
   A slot is invisible until its file actually plays (site.js adds
   .is-live on `playing`); a missing file, reduced motion or Pause leaves
   what is under it: the hero still, the cover. Square in a square, so
   contain loses nothing. */
.vslot {
  position: absolute; inset: 0; width: 100%; height: 100%;
  object-fit: contain; opacity: 0; pointer-events: none;
  transition: opacity 400ms ease-out;
}
.vslot.is-live { opacity: 1; }
@media (prefers-reduced-motion: reduce) { .vslot { display: none; } }
/* Round 14: the cover ALIVE is a 16:9 film whose centre square is the cover.
   Still mode (no scrub): it sits over the cover square, cropped to that
   square. Scrub mode (below): it is the square's height and 16:9 wide,
   centred on the square, and opens out from the square as --w goes 0 → 1. */
.vslot--wide {
  inset: auto 0 0 0; height: auto; aspect-ratio: 1 / 1;
  object-fit: cover;
}

@media (prefers-reduced-motion: no-preference) and (scripting: enabled) {
  /* the scroll length; film.js sets the exact value from the manifest */
  .film { height: 400svh; height: var(--film-h, 400svh); }
  /* pinned right under the bar from the first pixel, so the square never
     moves: at scroll 0 and while pinned it holds the same place */
  .film__stage {
    position: sticky; top: var(--bar-h, 3.5rem);
    height: calc(100vh - var(--bar-h, 3.5rem));
    height: calc(100svh - var(--bar-h, 3.5rem));
    overflow: hidden;
    display: grid; place-items: center;
    /* film.js writes --side (the square), --s (hero 1..0), --c (cover 0..1) */
    --side: min(100vw, calc(100svh - var(--bar-h, 3.5rem)));
    --s: 1; --c: 0;
  }
  .film__square { position: relative; width: var(--side); height: var(--side); margin: 0; }
  .film__hero, .film__cover { position: absolute; inset: 0; aspect-ratio: auto; }
  .film__canvas { display: block; position: absolute; inset: 0; width: 100%; height: 100%; }
  .film--drawn .film__still { visibility: hidden; }
  /* the hero loop hands over to the film's first frame (its own image) */
  .film--scrub .vslot--hero { transition: none; }
  .film--scrub .vslot--hero.is-live { opacity: var(--s); }
  /* the cover alive comes up WITH the cover (--c), the same fraction the
     ambient blends it at, so the square and its glow never show different
     frames (Sol 14b r1) */
  .film--scrub .vslot--wide { transition: none; }
  .film--scrub .vslot--wide.is-live { opacity: var(--c); }
  .film--cleared .vslot--hero { visibility: hidden; }
  /* the real cover over the film's last frame (the same image) */
  .film__cover { opacity: var(--c); background: none; }
  .film__square { z-index: 1; }   /* above the ambient (round 14b) */
  /* THE PHONE FILM (round 15): on a tall stage the film fills the stage
     (film.js moves the canvas out of the square, .film--full). The square's
     layers ride on top: the still and the hero loop fade with --s, the cover
     and the cover alive come up with --c — each lies exactly over the
     film's centre square, so nothing jumps. No ambient: the picture is real. */
  .film--full .film__canvas { z-index: 0; }
  .film--full .film__ambient { display: none; }
  /* the cover ALIVE (round 14): the cover square sits at the film's centre
     third (measured: its centre square at full height IS the cover, 16 px
     off centre at 2560 — under half a pixel at any screen size). A soft
     mask shows the square first (its 5% feather melts onto the identical
     cover under it), then opens to the full 16:9 as --w goes 0 → 1; the
     outer feather leaves the film's edges on soot, never a hard line. --L =
     how much of each side is still hidden (21.875% = (16/9 - 1) / 2 / (16/9)). */
  .vslot--wide {
    /* above the cover explicitly (it is the square's last child, so DOM
       order already paints it on top — Sol r1 asked for it stated) */
    z-index: 2;
    inset: 0 auto auto 50%; aspect-ratio: auto;
    width: calc(var(--side) * 16 / 9); height: var(--side); max-width: none;
    transform: translateX(-50%);
    object-fit: cover;
    --L: calc((1 - var(--w, 0)) * 21.875%);
    -webkit-mask-image: linear-gradient(to right, transparent var(--L), #000 calc(var(--L) + 5%), #000 calc(100% - var(--L) - 5%), transparent calc(100% - var(--L)));
            mask-image: linear-gradient(to right, transparent var(--L), #000 calc(var(--L) + 5%), #000 calc(100% - var(--L) - 5%), transparent calc(100% - var(--L)));
  }
}
/* THE AMBIENT (round 14b — Ross on his phone: the circuit bands "don't really
   look seamless"; they replaced nothing, they were bolted on). Wherever the
   stage is bigger than the square — above and below on a phone, beside it
   on a laptop — film.js mirrors what the square shows RIGHT NOW out from its
   edges into a small canvas; here it is blurred, dimmed and faded out toward
   the bar and the page. The square's own edges feather into it (portrait:
   top + bottom; landscape: the sides), so the picture simply continues. */
@media (prefers-reduced-motion: no-preference) and (scripting: enabled) {
  /* ultrawide: the fixed page layer (film.js moves the canvas to <body>) */
  .film__ambient--page {
    position: fixed !important; top: var(--bar-h, 3.5rem) !important; left: 0 !important;
    width: 100% !important; height: calc(100svh - var(--bar-h, 3.5rem)) !important;
    /* wider sides carry more of the mirror: blur harder so no shape (the
       mirrored logo) reads as one */
    filter: blur(34px) brightness(0.8) saturate(1.08) !important;
  }
  /* Bright AT the square's edge (so the feathered edge has no step to
     cross), fading to soot away from it: the mask is opaque across the
     square's span (--side, film.js) and falls off toward the stage's edges. */
  .film__ambient {
    position: absolute; inset: 0; width: 100%; height: 100%; z-index: 0;
    max-width: none; pointer-events: none;
    filter: blur(22px) brightness(0.82) saturate(1.08);
    -webkit-mask-image: linear-gradient(to right, transparent 0%, #000 calc(50% - var(--side) / 2), #000 calc(50% + var(--side) / 2), transparent 100%);
            mask-image: linear-gradient(to right, transparent 0%, #000 calc(50% - var(--side) / 2), #000 calc(50% + var(--side) / 2), transparent 100%);
  }
}
/* the feather follows the STAGE's shape, not the viewport's (Sol 14b r1: the
   stage is the viewport minus the bar, so a near-square portrait phone can
   still have side gutters) — film.js sets .film--tall when the stage is
   taller than it is wide */
@media (prefers-reduced-motion: no-preference) and (scripting: enabled) {
  .film--tall .film__ambient {
    -webkit-mask-image: linear-gradient(to bottom, transparent 0%, #000 calc(50% - var(--side) / 2), #000 calc(50% + var(--side) / 2), transparent 100%);
            mask-image: linear-gradient(to bottom, transparent 0%, #000 calc(50% - var(--side) / 2), #000 calc(50% + var(--side) / 2), transparent 100%);
  }
  .film--tall .film__hero, .film--tall .film__cover {
    -webkit-mask-image: linear-gradient(to bottom, transparent, #000 7%, #000 93%, transparent);
            mask-image: linear-gradient(to bottom, transparent, #000 7%, #000 93%, transparent);
  }
  /* the cover alive keeps its horizontal opening mask AND gets the same
     top/bottom feather (both gradients must pass) */
  .film--tall .vslot--wide {
    -webkit-mask-image: linear-gradient(to right, transparent var(--L), #000 calc(var(--L) + 5%), #000 calc(100% - var(--L) - 5%), transparent calc(100% - var(--L))), linear-gradient(to bottom, transparent, #000 7%, #000 93%, transparent);
            mask-image: linear-gradient(to right, transparent var(--L), #000 calc(var(--L) + 5%), #000 calc(100% - var(--L) - 5%), transparent calc(100% - var(--L))), linear-gradient(to bottom, transparent, #000 7%, #000 93%, transparent);
    -webkit-mask-composite: source-in;
            mask-composite: intersect;
  }
}
@media (prefers-reduced-motion: no-preference) and (scripting: enabled) {
  .film--scrub:not(.film--tall) .film__hero, .film--scrub:not(.film--tall) .film__cover {
    -webkit-mask-image: linear-gradient(to right, transparent, #000 7%, #000 93%, transparent);
            mask-image: linear-gradient(to right, transparent, #000 7%, #000 93%, transparent);
  }
}
/* THE PHONE FILM's layout (round 15) — the same query film.js tests
   (TALL) and the hero still's <picture> source uses. The square is the
   film's centre square at the film's own scale: as wide as the stage, or
   wider when the stage is taller than 16:9 (then the stage clips both sides
   of film and square alike). The hero (the still, then its loop) is the
   film's first frame and covers the whole stage; it fades with --s into the
   identical first frame of the film under it. */
@media (prefers-reduced-motion: no-preference) and (scripting: enabled) and (max-aspect-ratio: 20/23) {
  .film__stage { --side: max(100vw, calc((100svh - var(--bar-h, 3.5rem)) * 9 / 16)); }
  /* wider than the stage: centre it explicitly (a grid track would grow to
     the square and park it at the left edge — measured 26 px off at 390) */
  .film .film__square { position: absolute; left: 50%; top: 50%; transform: translate(-50%, -50%); }
  .film .film__hero {
    overflow: visible; background: none;
    -webkit-mask-image: none; mask-image: none;
  }
  .film .film__still, .film .vslot--hero {
    position: absolute; inset: auto; left: 50%; top: 50%;
    width: var(--side); height: calc(var(--side) * 16 / 9); max-width: none;
    transform: translate(-50%, -50%); object-fit: cover;
  }
  .film.film--drawn .film__still { visibility: visible; opacity: var(--s); }
  /* ROUND 16c (Ross 2026-10-12, the phone's last screen: "remove the
     crows"): the cover ALIVE on a tall stage is its own 9:16 loop of the
     crow-free tall cover (data-src-tall, Seedance 2.0), covering the stage
     like the hero loop — no 16:9 opening, no side mask. It comes up with
     --c over the film's identical last frame. */
  .film .vslot--wide {
    inset: auto; left: 50%; top: 50%;
    width: var(--side); height: calc(var(--side) * 16 / 9); max-width: none;
    transform: translate(-50%, -50%); object-fit: cover;
    -webkit-mask-image: none; mask-image: none;
  }
}

/* ---------- the single ----------
   The title card for the cover the film just set down: it starts right
   under the stage's soot band (round 10 — the old 9rem top pad + bottom-
   aligned grid left a dead screen between the cover and its title). */
.single {
  position: relative; z-index: 1;   /* above the pinned stage's soot band */
  background: var(--ground);        /* the lettering's blend backdrop (z-index isolates) */
  display: grid; gap: clamp(2rem, 4vw, 3rem); align-items: start;
  padding: clamp(2rem, 3.5vw, 3.5rem) var(--gutter) var(--section);
}
/* scrub mode only: film.js publishes --film-tail (the soot band under the
   ending's cover); the single rises into it. Still mode never sets it. */
@media (prefers-reduced-motion: no-preference) and (scripting: enabled) {
  .single { margin-top: calc(-1 * var(--film-tail, 0px)); }
  /* round 14b: the single rises over the hero with a SOFT leading edge — the
     art dissolves into the page instead of being cut by a line */
  .single::before {
    content: ""; position: absolute; left: 0; right: 0; bottom: 100%;
    height: clamp(3rem, 9vh, 6rem); pointer-events: none;
    background: linear-gradient(to bottom, rgba(11, 10, 9, 0), var(--ground));
  }
}
.single__head { display: grid; gap: clamp(0.75rem, 1.5vw, 1.25rem); }
.hook { font-size: var(--t-hook); line-height: 1.3; }
.hook span { display: block; }
.single__cta { margin-top: clamp(1.5rem, 2.5vw, 2.25rem); display: flex; flex-wrap: wrap; align-items: center; gap: 1rem 2rem; }
@media (min-width: 56rem) {
  .single { grid-template-columns: minmax(0, 1.35fr) minmax(0, 1fr); align-items: start; column-gap: clamp(3rem, 6vw, 6rem); }
}

/* ---------- statement ---------- */
.statement {
  display: grid; gap: clamp(1.75rem, 3vw, 2.5rem);
  padding: var(--section) var(--gutter);
  border-top: 1px solid var(--rule);
}
.slot--statement { font-size: var(--t-statement); line-height: 1; color: var(--fire); max-width: 14ch; }
.statement__words { max-width: var(--measure); display: grid; gap: 1rem; font-size: clamp(1rem, 0.8rem + 0.6vw, 1.375rem); line-height: 1.5; }
@media (min-width: 56rem) {
  .statement { grid-template-columns: minmax(0, 1.35fr) minmax(0, 1fr); align-items: end; column-gap: clamp(3rem, 6vw, 6rem); }
}

/* ---------- the album ---------- */
.album { border-top: 1px solid var(--rule); }
.album__open { display: grid; }
/* A cover is NEVER cropped (round 10: the old stretch + object-fit: cover
   cut the album cover to its top half). The square is always the whole
   square; on desktop it is one screen tall and the words fit beside it. */
.album__cover img { width: 100%; height: auto; aspect-ratio: 1 / 1; object-fit: contain; }
.album__words { display: grid; gap: 1rem; align-content: center; padding: clamp(2.5rem, 6vw, 5rem) var(--gutter); }
/* The Corrupted System's type (round 13): its headings are the RT Studio
   lettering images (slots above); the lines under them are plain Geist in
   --fire, the single's reading voice. */
.album__words .slot--t-title { margin-bottom: 0.5rem; }
.album-line { font-size: var(--t-hook); font-weight: 500; line-height: 1.3; max-width: 26ch; }
.intro { max-width: var(--measure); font-size: var(--t-body); }
@media (min-width: 56rem) {
  /* the single's mirror: words left, the cover against the RIGHT edge, a
     whole square up to one screen tall (capped at 53 % of the width); the
     cover sets the row and the words centre beside it */
  .album__open { grid-template-columns: minmax(0, 1fr) min(100svh, 53vw); align-items: center; }
  .album__cover { order: 2; }
  .album__words { padding-block: clamp(2rem, 2.5vw, 2.5rem); }
}

/* the seven track loops: one strip of shots, edge to edge */
.tracks {
  display: grid; gap: 1px;
  margin: 0; padding: 0; list-style: none;
  background: var(--rule);
  border-top: 1px solid var(--rule);
}
.track { display: grid; grid-template-columns: minmax(8rem, 40%) minmax(0, 1fr); gap: 1.25rem; align-items: end; background: var(--ground); padding-right: var(--gutter); }
.loop { position: relative; aspect-ratio: 9 / 16; background: #000; overflow: hidden; }
.loop video, .loop img { width: 100%; height: 100%; object-fit: cover; }
.track__words { display: grid; gap: 0.75rem; justify-items: start; padding: 0 0 1.25rem; min-width: 0; overflow-wrap: break-word; }
/* names only (round 12): no numbers — the strip itself is the order, and
   the list is an <ol> for anyone not seeing it. Round 13: each name is its
   TCS lettering image; this size is the fallback if an image fails. */
.track__title { font-size: clamp(1.25rem, 1rem + 0.6vw, 1.625rem); line-height: 1.05; }
/* tap to hear: the track's 8 second excerpt (site.js). Hidden without
   script — it can do nothing then. ROUND 15 (Ross: make Hear easy to
   find): the single's is the loudest thing on the page after the film
   (.hear--lead). */
/* ROUND 16 (Ross 2026-10-12): the button is the fire slab — IGNITE, and
   BURNING (hotter, flames off the top) while its excerpt plays; the BURNING
   slab flickers. Both slabs share one grid cell, so nothing moves. */
.hear {
  display: inline-grid; place-items: center; padding: 0;
  background: none; border: 0; border-radius: 0.75rem; cursor: pointer;
  transition: transform 160ms ease-out;
}
.hear:hover { transform: scale(1.04); }
.hear__art { grid-area: 1 / 1; height: 3.25rem; width: auto; max-width: none; transition: opacity 160ms ease-out; }
.hear__art--on, .hear[data-on] .hear__art { opacity: 0; }
.hear[data-on] .hear__art--on { opacity: 1; animation: bs-flicker 1.6s ease-in-out infinite alternate; }
@keyframes bs-flicker {
  0% { filter: brightness(1); } 35% { filter: brightness(1.14); } 60% { filter: brightness(0.96); } 100% { filter: brightness(1.1); }
}
.hear--lead .hear__art { height: clamp(4.25rem, 3.5rem + 2vw, 5.25rem); }
/* an excerpt that will not play: the slab goes cold */
.hear[aria-disabled] { cursor: default; }
.hear[aria-disabled]:hover { transform: none; }
.hear[aria-disabled] .hear__art { filter: grayscale(1) brightness(0.55); }
@media (prefers-reduced-motion: reduce) {
  .hear, .hear__art { transition: none; }
  .hear:hover { transform: none; }
  .hear[data-on] .hear__art--on { animation: none; }
}
html:not(.js) .hear { display: none; }
@media (min-width: 48rem) {
  .tracks { grid-template-columns: repeat(4, minmax(0, 1fr)); }
  /* frame on top, words below — every caption starts on the same line */
  .track { grid-template-columns: 1fr; grid-template-rows: auto 1fr; align-items: start; align-content: start; gap: 0; padding: 0; }
  .track__words { padding: 1rem 1rem 1.5rem; }
}
@media (min-width: 72rem) {
  .tracks { grid-template-columns: repeat(7, minmax(0, 1fr)); }
}

/* THE TRACKS CATCH FIRE (round 15, Ross: "give the album section more of
   the hero's energy"). Scroll-driven, like the hero's ending — the
   visitor's own scroll moves it, nothing is timed, so the "no fade-up"
   rule stands: each loop comes up from embers to full colour as it rises
   into view, and a fire fuse burns along its foot. Browsers without
   scroll timelines (and reduced motion) show the loops as they are. */
@supports (animation-timeline: view()) {
  @media (prefers-reduced-motion: no-preference) {
    .loop {
      animation: bs-ignite linear both;
      animation-timeline: view(); animation-range: entry 0% cover 42%;
    }
    .loop::after {
      content: ""; position: absolute; left: 0; right: 0; bottom: 0; height: 3px;
      background: var(--fire); transform-origin: left center;
      animation: bs-fuse linear both;
      animation-timeline: view(); animation-range: entry 20% cover 50%;
    }
  }
}
@keyframes bs-ignite {
  from { filter: brightness(0.22) saturate(0.25) sepia(0.6); }
  60%  { filter: brightness(0.85) saturate(1.3) sepia(0.15); }
  to   { filter: none; }
}
@keyframes bs-fuse { from { transform: scaleX(0); } to { transform: scaleX(1); } }
/* the track that is playing keeps its fuse lit across the top too */
.track--on .loop::before {
  content: ""; position: absolute; left: 0; right: 0; top: 0; height: 3px;
  background: var(--fire); z-index: 1;
}

/* ---------- the player (round 15) ----------
   Pinned to the bottom once a Hear is tapped: play / pause, the track,
   the clip's progress, Next (album), the full song, Close. Phones: two
   rows — the name gets the width, the actions sit under it. */
.player {
  position: fixed; left: 0; right: 0; bottom: 0; z-index: 40;
  max-width: var(--frame-w); margin-inline: auto;
  display: grid; align-items: center; column-gap: 0.85rem; row-gap: 0.15rem;
  grid-template-columns: auto minmax(0, 1fr) auto;
  grid-template-areas: "tog now cls" "tog acts cls";
  padding: 0.6rem var(--gutter) calc(0.6rem + env(safe-area-inset-bottom, 0px));
  background: var(--ground); border-top: 1px solid var(--edge);
  box-shadow: 0 -1.5rem 2rem rgba(11, 10, 9, 0.85);
  color: var(--fire); font-size: var(--t-small);
}
.player[hidden] { display: none; }
html.has-player body { padding-bottom: 6.5rem; }
.player button, .player a {
  font-family: var(--sans); color: var(--fire); background: none; border: 0; border-radius: 0;
  cursor: pointer; text-decoration: none;
}
/* round 16: the player's controls are fire discs (play / pause, next,
   close), the same lava as the Spotify / YouTube icons beside them */
.player__toggle, .player__next, .player__close { display: inline-grid; place-items: center; padding: 0; border-radius: 50%; }
.player__toggle { grid-area: tog; width: 3rem; height: 3rem; }
.player__art { grid-area: 1 / 1; width: 2.75rem; height: 2.75rem; max-width: none; }
.player__art--pause, .player--on .player__art--play { opacity: 0; }
.player--on .player__art--pause { opacity: 1; }
.player__toggle:disabled { cursor: default; }
.player__toggle:disabled .player__art { filter: grayscale(1) brightness(0.55); }
.player__now {
  grid-area: now; margin: 0; min-width: 0;
  display: grid; grid-template-columns: auto minmax(0, 1fr); column-gap: 0.5rem; row-gap: 0.4rem;
  align-items: baseline;
}
.player__kicker { text-transform: uppercase; letter-spacing: 0.1em; font-weight: 600; font-size: 0.8em; }
.player__title { font-weight: 500; white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }
.player__line { grid-column: 1 / -1; height: 2px; background: var(--rule); overflow: hidden; }
.player__fill { display: block; height: 100%; background: var(--fire); transform-origin: left center; transform: scaleX(0); transition: transform 250ms linear; }
.player__acts { grid-area: acts; display: flex; gap: 1.25rem; align-items: center; }
.player .go__btn { min-width: 3rem; min-height: 3rem; }
.player .icon { --ih: 2.1rem; }   /* round 16: the size of the fire discs beside it */
.player__next { width: 3rem; height: 3rem; }
.player__next[hidden] { display: none; }
.player__next .player__art { width: 2.25rem; height: 2.25rem; }
.player__close { grid-area: cls; width: 3rem; height: 3rem; }
.player__close .player__art { width: 2.25rem; height: 2.25rem; }
@media (min-width: 48rem) {
  .player {
    grid-template-columns: auto minmax(0, 1fr) auto auto;
    grid-template-areas: "tog now acts cls";
  }
  .player__acts { gap: 1.75rem; padding-left: 1rem; }
  html.has-player body { padding-bottom: 5rem; }
}
@media (prefers-reduced-motion: reduce) { .player__fill { transition: none; } }

/* ---------- listen + contact ---------- */
.block { padding: var(--section) var(--gutter); border-top: 1px solid var(--rule); }
/* LISTEN / WATCH (round 15, Ross: "everywhere where there is a spotify
   button we need a youtube button also … just the icons as buttons"):
   a pair of fire icons, nothing else — the name rides for screen readers.
   Each icon is the whole button; the tap area is never under 48 px. */
.go { display: inline-flex; align-items: center; gap: clamp(1rem, 3vw, 1.75rem); }
.go__btn {
  display: inline-grid; place-items: center; min-width: 3rem; min-height: 3rem;
  text-decoration: none; transition: transform 160ms ease-out;
}
.go__btn:hover { transform: scale(1.06); }
.go .icon { --ih: 2.5rem; }
.go--big { margin-top: clamp(2rem, 4vw, 3rem); gap: clamp(2rem, 6vw, 3.5rem); }
.go--big .icon { --ih: clamp(3.5rem, 2.5rem + 3vw, 5rem); }
.album__words .go { margin-top: 0.5rem; }
@media (prefers-reduced-motion: reduce) { .go__btn { transition: none; } .go__btn:hover { transform: none; } }
.icon {
  position: relative; display: inline-block; flex: none; overflow: hidden;
  height: var(--ih, 3.25rem); aspect-ratio: calc(var(--iw) / var(--ihf));
}
.icon img {
  position: absolute; max-width: none;
  width: calc(100% / var(--iw)); height: auto;
  left: calc(-100% * var(--ix) / var(--iw));
  top: calc(-100% * var(--iy) / var(--ihf));
  mix-blend-mode: screen;
}
/* crop boxes measured by script (measure13.html), fractions of 768 */
.icon--spotify { --ix: .124; --iy: .138; --iw: .757; --ihf: .71; }
.icon--youtube { --ix: .098; --iy: .197; --iw: .786; --ihf: .612; }

.contact__list { margin-top: clamp(2rem, 4vw, 3rem); display: grid; gap: 1.25rem; max-width: 52rem; }
.contact__list > div { display: grid; gap: 0.25rem; }
.contact dt { font-weight: 400; }
.contact dd { font-weight: 500; }
.contact dd a { overflow-wrap: anywhere; }
@media (min-width: 48rem) {
  .contact__list > div { grid-template-columns: 7.5rem 1fr; align-items: baseline; }
}
/* Desktop: head left, rows right — the single's rhythm; no half-empty
   screens (round 10). Listen and Contact share one screen at 1440x900. */
@media (min-width: 56rem) {
  .block {
    display: grid; grid-template-columns: minmax(0, 1fr) minmax(0, 1.6fr);
    grid-template-rows: auto 1fr;
    column-gap: clamp(3rem, 6vw, 6rem); align-items: start;
    padding-block: clamp(3rem, 5vw, 4.5rem);
  }
  .block > .slot { grid-column: 1; }
  .contact__list { grid-column: 2; grid-row: 1 / span 2; margin-top: 0.4rem; }
  .listen > .go { grid-column: 2; grid-row: 1 / span 2; margin-top: 0; align-self: center; }
  .contact dd { font-size: clamp(1.125rem, 0.9rem + 0.7vw, 1.5rem); line-height: 1.35; }
  .contact dt { font-size: var(--t-body); }
}

/* ---------- footer ---------- */
.footer {
  padding: clamp(2.5rem, 5vw, 3.5rem) var(--gutter) 2.5rem;
  border-top: 1px solid var(--rule);
  font-size: 0.9375rem; line-height: 1.6;
}
.footer__credit { margin-bottom: 1rem; font-size: var(--t-body); font-weight: 500; }
.footer__fine { font-size: 0.875rem; }

/* ---------- legal page ---------- */
.legal { max-width: 42rem; padding: clamp(3rem, 8vw, 6rem) var(--gutter); }
.legal__title { font-size: var(--t-title); color: var(--fire); }
.legal__updated { margin: 1.25rem 0 2.5rem; font-size: 0.9375rem; }
.legal h2 { margin: 2.25rem 0 0.6rem; font-size: 1.25rem; font-weight: 600; line-height: 1.3; }
.legal p + p { margin-top: 1rem; }

/* ---------- script-dependent visibility ---------- */
html:not(.js) .loop video { display: none; }

/* ---------- motion ---------- */
@media (prefers-reduced-motion: reduce) {
  *, *::before, *::after { transition: none !important; animation: none !important; }
}
