/* AERIE RADIO
   dusk #20203A · lavender #9E8DE5 · peach #F4A58B · pale yellow #F8D98B
   cloud white #F7F4FF · ink #11111D
   display serif / compact grotesk / tracklist mono
   Nothing here is shared with any other world. */

/* ── tokens ───────────────────────────────────────────────────────────── */
:root {
  --dusk: #20203A;
  --ink: #11111D;
  --lavender: #9E8DE5;
  --peach: #F4A58B;
  --pale: #F8D98B;
  --cloud: #F7F4FF;

  /* rewritten by world.js as the dial turns */
  --sky-top: #191930;
  --sky-mid: #232343;
  --sky-bot: #2A2A48;
  --glow: #9E8DE5;
  --warm: 0;

  /* Fraunces leads: a 4am broadcast wants letterforms that are slightly
     off-square, not a clean editorial didone. The local names that used to
     lead here (Iowan Old Style) are OS-only and win the stack on most
     machines, which silently disabled the webfont. Space Grotesk fills the
     previously-undefined --grotesk. Mono stays local on purpose: the
     transmitter lettering is equipment, not identity, and the system mono
     stack covers Windows/macOS/Linux without a network round trip. */
  --serif: "Fraunces", "Iowan Old Style", "Palatino Linotype", Palatino, Georgia, "Times New Roman", serif;
  --grotesk: "Space Grotesk", "Segoe UI", system-ui, -apple-system, "Helvetica Neue", Arial, sans-serif;
  --mono: "Cascadia Mono", Consolas, "SF Mono", ui-monospace, "DejaVu Sans Mono", "Courier New", monospace;

  --hair: rgba(247, 244, 255, .16);
  --hair-soft: rgba(247, 244, 255, .09);
  --pad: clamp(1.25rem, 5vw, 5.5rem);
  --ease: cubic-bezier(.22, .82, .28, 1);
}

@property --push {
  syntax: "<number>";
  inherits: true;
  initial-value: 0;
}

@property --vu {
  syntax: "<number>";
  inherits: true;
  initial-value: 0;
}

/* Fraunces' SOFT and WONK axes are what separate it from every other serif
   on the machine. SOFT rounds the terminals; WONK lets ascenders, descenders
   and the italic 'g' swap in deliberately odd alternates. Big type gets both
   hard; small type gets only a little SOFT so it stays legible. */
.callsign, .board__hour, .recs h2, .seek h2, .crew h2, .tail__word, .rec__title, .band__cs, .dial__freq {
  font-variation-settings: "SOFT" 72, "WONK" 1, "opsz" 120;
  font-synthesis: none;
}
.ruler__name, .dial__unit, .signal, .legend button {
  font-variation-settings: "SOFT" 30, "WONK" 0, "opsz" 14;
}
.callsign, .readout__cs, .tail__word span { font-style: italic; }

*, *::before, *::after { box-sizing: border-box; }

/* overflow-x: clip on the ROOT is what actually pins scrollWidth to 0 here.
   The hero's sliced SVG paints wider than its box; that ink is attributed to
   the root's scrollable area and no intermediate clip — overflow:hidden or
   clip on the section, contain:paint, or a user-space clipPath — changes it
   (measured 405 at 390 in every variant; only clipping the root gave 390).
   `clip` rather than `hidden` so the y axis is left visible and sticky
   positioning and smooth scrolling keep working. */
html {
  -webkit-text-size-adjust: 100%;
  scroll-behavior: smooth;
  overflow-x: clip;
}

body {
  margin: 0;
  background: var(--dusk);
  color: var(--cloud);
  font-family: var(--grotesk);
  font-size: 16px;
  line-height: 1.62;
  overflow-x: hidden;
  -webkit-font-smoothing: antialiased;
}

h1, h2, h3 { font-family: var(--serif); font-weight: 400; letter-spacing: -.01em; margin: 0; line-height: 1.08; }
p, ul, ol { margin: 0; }
ul, ol { list-style: none; padding: 0; }
button { font: inherit; color: inherit; background: none; border: 0; cursor: pointer; }
a { color: inherit; text-decoration: none; }
svg { display: block; }

.mono { font-family: var(--mono); font-variant-numeric: tabular-nums; letter-spacing: .02em; }

:where(a, button, [tabindex]):focus-visible {
  outline: 2px solid var(--peach);
  outline-offset: 3px;
  border-radius: 2px;
}

.skip {
  position: fixed; top: .5rem; left: 50%; translate: -50% -180%;
  z-index: 90; padding: .5rem 1rem; background: var(--peach); color: var(--ink);
  font: 600 .74rem/1 var(--grotesk); letter-spacing: .12em; text-transform: uppercase;
  transition: translate .2s;
}
.skip:focus-visible { translate: -50% 0; }

/* ── boot: the dial finds you first ───────────────────────────────────── */
.boot {
  position: fixed; inset: 0; z-index: 80;
  display: grid; place-content: center; gap: 1.4rem; justify-items: center;
  background: var(--ink); pointer-events: none;
  transition: opacity .5s ease, visibility .5s;
}
.boot.is-gone { opacity: 0; visibility: hidden; }
.boot__sweep {
  width: 74px; height: 74px; border-radius: 50%;
  border: 1px solid var(--hair);
  background:
    conic-gradient(from 180deg, var(--peach) 0deg, rgba(244, 165, 139, 0) 34deg, rgba(244, 165, 139, 0) 360deg);
  mask: radial-gradient(circle, transparent 0 30px, #000 30px 36px, transparent 36px);
  animation: sweep 1.5s var(--ease) infinite;
}
.boot__word {
  font: 400 1.05rem/1 var(--serif); letter-spacing: .5em;
  text-indent: .5em; color: var(--lavender);
}
@keyframes sweep { to { rotate: 360deg; } }

/* ── navigation: a ruler with ticks under it ──────────────────────────── */
.ruler {
  position: sticky; top: 0; z-index: 60;
  background: color-mix(in srgb, var(--ink) 82%, transparent);
  backdrop-filter: blur(14px);
  border-bottom: 1px solid var(--hair-soft);
}
.ruler__in {
  display: flex; align-items: center; gap: clamp(1rem, 4vw, 3rem);
  padding: .8rem var(--pad);
}
.ruler__name {
  display: block; font: 400 1.22rem/1 var(--serif); letter-spacing: .01em;
}
.ruler__sub {
  display: block; margin-top: .2rem;
  font-size: .64rem; letter-spacing: .17em; text-transform: uppercase;
  color: color-mix(in srgb, var(--cloud) 46%, transparent);
}
.ruler__nav {
  display: flex; gap: clamp(.9rem, 2.4vw, 2.1rem);
  margin-left: auto; overflow-x: auto; scrollbar-width: none;
}
.ruler__nav::-webkit-scrollbar { display: none; }
.ruler__nav a {
  position: relative; display: block; padding-bottom: .55rem; white-space: nowrap;
  font-size: .72rem; letter-spacing: .14em; text-transform: uppercase;
  color: color-mix(in srgb, var(--cloud) 62%, transparent);
  transition: color .25s;
}
.ruler__nav a i {
  position: absolute; left: 0; bottom: 0; width: 100%; height: 3px;
  background: repeating-linear-gradient(90deg, var(--hair) 0 1px, transparent 1px 5px);
  transition: background .25s;
}
.ruler__nav a:hover { color: var(--cloud); }
.ruler__nav a:hover i { background: var(--lavender); }
.ruler__live {
  display: flex; align-items: center; gap: .45rem;
  font-size: .66rem; letter-spacing: .18em; text-transform: uppercase;
  color: var(--peach);
}
.ruler__pip, .tail__pip {
  width: 7px; height: 7px; border-radius: 50%; background: var(--peach);
  animation: pip 2.4s ease-in-out infinite;
}
@keyframes pip { 0%, 100% { opacity: 1; } 50% { opacity: .28; } }

/* ── hero: hazy illustrated sky with a dial in it ──────────────────────── */
.sky {
  position: relative; isolation: isolate;
  padding: clamp(3rem, 9vw, 7.5rem) 0 0;
  background: linear-gradient(178deg, var(--sky-top) 0%, var(--sky-mid) 46%, var(--sky-bot) 78%, var(--dusk) 100%);
  transition: background 1.1s var(--ease);
  /* The displaced cloud and ridge filters declare regions up to 180% wide.
     Chrome counts that filter ink as scrollable overflow, and because those
     layers animate, the reported scrollWidth drifted 405-407 at 390 rather
     than sitting at 0. Clipping the hero contains the ink and pins it at 0;
     body{overflow-x:hidden} was hiding the symptom, not fixing it. */
  overflow: clip;
}
.sky__haze {
  position: absolute; inset: 0; width: 100%; height: 100%;
  z-index: -2; opacity: .9; transition: opacity 1s var(--ease);
  /* slice scales the 1440x760 viewBox to COVER the section, so the drawn
     content is deliberately larger than the element. Its displaced filters
     then register that overhang as document scrollWidth. clip contains it. */
  overflow: hidden;
}
.sky__stars circle { fill: var(--cloud); }
.sky__ridge { position: absolute; left: 0; width: 100%; }
.sky__ridge--far {
  bottom: 0; height: 34%; fill: color-mix(in srgb, var(--glow) 24%, var(--dusk));
  opacity: .8;
}
.sky__ridge--near { bottom: 0; height: 20%; fill: var(--ink); opacity: .55; }
.sky__haze { pointer-events: none; }

.sky__in {
  position: relative;
  display: grid; gap: clamp(2.5rem, 6vw, 4rem);
  grid-template-columns: minmax(0, 1fr);
  padding: 0 var(--pad) clamp(2.5rem, 6vw, 4rem);
}
@media (min-width: 900px) {
  .sky__in { grid-template-columns: minmax(0, 5fr) minmax(0, 6fr); align-items: center; }
}

/* copy column */
.eyebrow {
  display: flex; align-items: center; gap: .55rem;
  font-size: .68rem; letter-spacing: .24em; text-transform: uppercase;
  color: color-mix(in srgb, var(--cloud) 60%, transparent);
}
.eyebrow__pip {
  width: 26px; height: 1px; background: var(--pale);
}

/* the transmitter's own status strip: live clock, elapsed time, station lamp.
   The lamp carries the only warm point of pure light in the copy column. */
.onair {
  display: flex; align-items: center; flex-wrap: wrap; gap: .5rem .7rem;
  margin-top: 1.15rem; padding: .55rem .8rem;
  border: 1px solid var(--hair-soft); border-radius: 2px;
  background: color-mix(in srgb, var(--ink) 42%, transparent);
  width: fit-content;
  font-size: .64rem; letter-spacing: .18em; text-transform: uppercase;
  color: color-mix(in srgb, var(--cloud) 55%, transparent);
}
.onair__dot {
  width: 7px; height: 7px; border-radius: 50%; background: var(--peach);
  box-shadow: 0 0 10px color-mix(in srgb, var(--peach) 80%, transparent);
  animation: pip 2.4s ease-in-out infinite;
}
.onair__clock {
  font-size: .82rem; letter-spacing: .06em; color: var(--cloud);
  font-variant-numeric: tabular-nums;
}
.onair__len { color: var(--pale); letter-spacing: .06em; }
.onair__sep { width: 1px; height: 12px; background: var(--hair); }
.callsign {
  margin: 1.1rem 0 .35rem;
  font-size: clamp(3.1rem, 12vw, 7.4rem);
  line-height: .92;
  font-style: italic;
  letter-spacing: -.035em;
  color: var(--cloud);
  text-shadow: 0 0 60px color-mix(in srgb, var(--glow) 55%, transparent);
}
.genre {
  font-size: clamp(.86rem, 1.5vw, 1rem);
  letter-spacing: .2em; text-transform: uppercase;
  color: var(--pale);
}
.blurb {
  max-width: 44ch; margin-top: 1.35rem;
  font-size: clamp(1rem, 1.5vw, 1.16rem);
  color: color-mix(in srgb, var(--cloud) 82%, transparent);
}
.credit {
  display: flex; flex-wrap: wrap; gap: .35rem 1.5rem;
  margin-top: 1.6rem; padding-top: 1.1rem; border-top: 1px solid var(--hair-soft);
  font-size: .82rem;
}
.credit__row { display: inline-flex; gap: .5rem; }
.credit__key {
  letter-spacing: .16em; text-transform: uppercase; font-size: .66rem;
  color: color-mix(in srgb, var(--cloud) 42%, transparent);
  padding-top: .12em;
}

/* equalizer */
.eq {
  display: flex; align-items: flex-end; gap: 4px;
  height: 62px; margin-top: 1.9rem;
  --push: 0;
  transition: --push .5s ease-out;
}
.eq i {
  flex: 1 1 0; min-width: 2px; height: 100%;
  background: linear-gradient(180deg, var(--peach), var(--lavender));
  transform-origin: 50% 100%;
  animation: eqidle 1.1s ease-in-out infinite alternate;
  animation-delay: calc(var(--i) * -0.117s);
}
.eq i:nth-child(3n) { background: linear-gradient(180deg, var(--pale), var(--peach)); }
@keyframes eqidle {
  from { transform: scaleY(calc(.06 + var(--push) * .26)); }
  to   { transform: scaleY(calc(.34 + var(--push) * .66)); }
}

.sky__act { display: flex; align-items: center; flex-wrap: wrap; gap: 1rem 1.6rem; margin-top: 2rem; }
.pin {
  display: inline-flex; align-items: center; gap: .7rem;
  padding: .78rem 1.5rem .78rem 1.2rem;
  font-size: .74rem; letter-spacing: .16em; text-transform: uppercase;
  color: var(--ink); background: var(--pale);
  clip-path: polygon(0 0, 100% 0, 100% calc(100% - 11px), calc(100% - 11px) 100%, 0 100%);
  transition: background .25s, color .25s, translate .25s var(--ease);
}
.pin__dot {
  width: 8px; height: 8px; border-radius: 50%;
  background: var(--ink); opacity: .45;
  transition: opacity .25s, scale .25s var(--ease);
}
.pin:hover { translate: 3px 0; background: var(--peach); }
.pin[aria-pressed="true"] { background: var(--peach); color: var(--ink); }
.pin[aria-pressed="true"] .pin__dot { opacity: 1; scale: 1.35; background: var(--ink); }
.sky__hint { font-size: .78rem; color: color-mix(in srgb, var(--cloud) 44%, transparent); }

/* dial */
.sky__dial { display: flex; flex-direction: column; align-items: center; gap: 1.35rem; }
.dial {
  position: relative; width: min(78vw, 340px); aspect-ratio: 1;
  cursor: grab; touch-action: none; border-radius: 50%;
  transition: scale .4s var(--ease);
  /* The rotor is a full-size square rotated up to 140 degrees, so its
     transformed bounding box is ~1.41x the dial. At 390 that pushed 30px of
     scrollWidth past the viewport. The dial is already a circle, so clipping
     to it is both the correct and the invisible fix. */
  overflow: clip;
}
.dial:active, .dial.is-dragging { cursor: grabbing; scale: 1.015; }
.dial__face { position: absolute; inset: 0; width: 100%; height: 100%; }
.dial__ring { fill: none; stroke: var(--hair); stroke-width: 1; }
.dial__ring--inner { stroke: var(--hair-soft); stroke-dasharray: 2 7; }
.dial__ticks line { stroke: var(--hair); stroke-width: 1; }
.dial__ticks line.is-major { stroke: var(--lavender); stroke-width: 2; }
.dial__ticks line.is-on { stroke: var(--pale); }
.dial__ticks text {
  fill: color-mix(in srgb, var(--cloud) 55%, transparent);
  font-family: var(--mono); font-size: 9px; letter-spacing: .06em;
  transition: fill .4s;
}
.dial__ticks text.is-on { fill: var(--pale); }

/* The rotor carries NO static transform. world.js writes the individual
   `rotate` property inline, and an individual transform property composes
   WITH transform rather than replacing it — so a standing
   `transform: rotate(-140deg)` here added itself to every value JS wrote and
   rendered all eight stations exactly 140 degrees from their own tick. The
   measured inline values were always correct; the stylesheet was the layer
   that was wrong. */
.dial__rotor {
  position: absolute; inset: 0; display: grid; place-items: start center;
  transition: rotate .62s var(--ease);
  will-change: rotate;
}
.dial.is-dragging .dial__rotor { transition: none; }
.dial__needle {
  width: 2px; height: 44%; margin-top: 3%;
  background: linear-gradient(180deg, var(--peach), rgba(244, 165, 139, 0));
  box-shadow: 0 0 12px color-mix(in srgb, var(--peach) 55%, transparent);
  transform-origin: 50% 0;
  /* an analogue needle never sits perfectly still, even on a locked signal */
  animation: needle-live 3.1s ease-in-out infinite alternate;
}
@keyframes needle-live {
  from { rotate: -.5deg; opacity: .88; }
  to   { rotate: .5deg;  opacity: 1; }
}
.dial__hub {
  position: absolute; inset: 0; display: grid; place-content: center; justify-items: center;
  gap: .1rem; text-align: center; pointer-events: none;
}
.dial__freq { font-size: 2.15rem; letter-spacing: -.02em; color: var(--cloud); }
.dial__unit {
  font-size: .6rem; letter-spacing: .34em; text-transform: uppercase;
  color: color-mix(in srgb, var(--cloud) 40%, transparent);
}

.legend {
  display: flex; flex-wrap: wrap; justify-content: center; gap: .4rem .5rem;
  max-width: 46ch;
}
.legend li { display: block; }
.legend button {
  display: block; padding: .3rem .6rem;
  font: .64rem/1.2 var(--mono); letter-spacing: .12em; text-transform: uppercase;
  color: color-mix(in srgb, var(--cloud) 48%, transparent);
  border: 1px solid transparent; border-radius: 2px;
  transition: color .25s, border-color .25s;
}
.legend button:hover { color: var(--cloud); }
.legend button[aria-current="true"] {
  color: var(--pale); border-color: color-mix(in srgb, var(--pale) 45%, transparent);
}

.signal { display: flex; align-items: center; gap: .7rem; font-size: .64rem; letter-spacing: .2em; text-transform: uppercase; color: color-mix(in srgb, var(--cloud) 46%, transparent); }
.signal__bars { display: flex; align-items: flex-end; gap: 3px; height: 13px; }
.signal__bars i {
  width: 3px; background: var(--lavender); opacity: .25;
  transition: opacity .4s, background .4s;
}
.signal__bars i:nth-child(1) { height: 30%; }
.signal__bars i:nth-child(2) { height: 45%; }
.signal__bars i:nth-child(3) { height: 60%; }
.signal__bars i:nth-child(4) { height: 72%; }
.signal__bars i:nth-child(5) { height: 84%; }
.signal__bars i:nth-child(6) { height: 94%; }
.signal__bars i:nth-child(7) { height: 100%; }
.signal__bars i.is-on { opacity: 1; background: var(--pale); }
.signal__state { color: var(--pale); letter-spacing: .14em; }

/* ── the waveform, used as a rule between sections ────────────────────── */
.wave { position: relative; color: color-mix(in srgb, var(--glow) 70%, var(--cloud)); }
.wave--sky { color: color-mix(in srgb, var(--cloud) 30%, transparent); }
.wave--mid { background: var(--ink); }
.wave svg { width: 100%; height: 64px; }
.wave__line {
  stroke-dasharray: var(--len, 2600); stroke-dashoffset: var(--len, 2600);
  transition: stroke-dashoffset 1.9s var(--ease);
}
.wave.is-drawn .wave__line { stroke-dashoffset: 0; }

/* ── tracklist: a tilted board of mono rows ───────────────────────────── */
.board {
  background: var(--dusk);
  padding: clamp(3.5rem, 8vw, 6rem) 0 clamp(3.5rem, 8vw, 6rem);
}
.board__in { max-width: 1180px; margin: 0 auto; padding: 0 var(--pad); rotate: -.35deg; }
.kicker {
  font-size: .66rem; letter-spacing: .28em; text-transform: uppercase;
  color: var(--lavender);
}
.board__head {
  display: flex; flex-wrap: wrap; align-items: baseline; gap: .4rem 1.4rem;
  padding-bottom: 1.4rem; border-bottom: 1px solid var(--hair);
}
.board__hour { font-size: clamp(1.5rem, 4vw, 2.4rem); color: var(--cloud); }
.board__note { margin-left: auto; font-size: .82rem; color: color-mix(in srgb, var(--cloud) 45%, transparent); }

.tl { font-family: var(--mono); transition: opacity .2s; }
.tl.is-swapping { opacity: .18; }
.tl li {
  display: grid; grid-template-columns: 4.6rem 2.6rem minmax(0, 1fr) auto;
  gap: .8rem; align-items: baseline;
  padding: .95rem .4rem; border-bottom: 1px solid var(--hair-soft);
  font-size: .82rem;
}
.tl li:first-child { border-top: 1px solid var(--hair-soft); }
.tl__t { color: color-mix(in srgb, var(--cloud) 42%, transparent); }
.tl__n { color: var(--pale); }
.tl__title { font-family: var(--grotesk); font-size: 1rem; color: var(--cloud); }
.tl__artist { color: color-mix(in srgb, var(--cloud) 62%, transparent); font-family: var(--grotesk); font-size: .9rem; }
.tl__len { color: color-mix(in srgb, var(--cloud) 40%, transparent); }
.tl li.is-now { background: color-mix(in srgb, var(--peach) 9%, transparent); }
.tl li.is-now .tl__title { color: var(--peach); }
.tl li.is-now .tl__n::before { content: "▶ "; color: var(--peach); }
.tl__foot {
  display: flex; flex-wrap: wrap; gap: .3rem 1rem;
  margin-top: 1.6rem; font-size: .76rem;
  color: color-mix(in srgb, var(--cloud) 42%, transparent);
}
.tl__foot .mono { color: var(--lavender); letter-spacing: .1em; }

/* ── records: round cards drifting at different depths ─────────────────── */
.recs { background: var(--ink); padding: clamp(3.5rem, 8vw, 6rem) 0 clamp(4rem, 9vw, 7rem); }
.recs__in { max-width: 1180px; margin: 0 auto; padding: 0 var(--pad); }
.recs__head { max-width: 58ch; }
.recs h2, .seek h2, .crew h2 {
  margin: .7rem 0 .9rem; font-size: clamp(1.7rem, 4.4vw, 3rem);
}
.lede { font-size: clamp(.98rem, 1.5vw, 1.1rem); color: color-mix(in srgb, var(--cloud) 70%, transparent); }

.recs__list {
  display: grid; gap: clamp(2rem, 4vw, 3rem);
  grid-template-columns: repeat(auto-fit, minmax(150px, 1fr));
  margin-top: clamp(2.5rem, 5vw, 4rem);
  perspective: 900px;
}
.rec {
  translate: 0 var(--drift, 0px);
  rotate: var(--tilt, 0deg);
  transition: translate .18s linear;
  will-change: translate;
}
.rec__disc {
  position: relative; display: block; aspect-ratio: 1;
  border-radius: 50%;
  background:
    radial-gradient(circle at 34% 26%,
      hsl(var(--h1) 52% 72%) 0%,
      hsl(var(--h1) 44% 40%) 38%,
      hsl(var(--h2) 40% 22%) 72%,
      hsl(var(--h2) 34% 14%) 100%);
  box-shadow: 0 22px 44px -26px #000, inset 0 0 0 1px rgba(247, 244, 255, .1);
  transition: translate .5s var(--ease), box-shadow .5s var(--ease);
}
.rec__grooves {
  position: absolute; inset: 5%; border-radius: 50%;
  background: repeating-radial-gradient(circle at 50% 50%,
    rgba(11, 11, 20, .34) 0 1px, rgba(247, 244, 255, .05) 1px 4px);
  mask: radial-gradient(circle, #000 0 22%, transparent 22% 46%, #000 46% 100%);
}
.rec__label {
  position: absolute; inset: 33%; border-radius: 50%;
  display: grid; place-items: center;
  background: hsl(var(--h2) 46% 24%);
  box-shadow: inset 0 0 0 1px rgba(247, 244, 255, .18);
}
.rec__label i {
  width: 12%; aspect-ratio: 1; border-radius: 50%;
  background: var(--ink);
  box-shadow: 0 0 0 5px rgba(247, 244, 255, .1);
}
.rec__title {
  margin-top: 1rem; font-family: var(--serif); font-size: 1.05rem; line-height: 1.2;
  color: var(--cloud);
}
.rec__artist { margin-top: .18rem; font-size: .86rem; color: color-mix(in srgb, var(--cloud) 62%, transparent); }
.rec__meta { margin-top: .5rem; font-size: .64rem; letter-spacing: .16em; text-transform: uppercase; color: color-mix(in srgb, var(--cloud) 40%, transparent); }
.rec:hover .rec__disc { translate: 0 -8px; box-shadow: 0 34px 58px -28px #000, inset 0 0 0 1px rgba(247, 244, 255, .28); }

/* ── signal seek ──────────────────────────────────────────────────────── */
.seek { background: var(--dusk); padding: clamp(3.5rem, 8vw, 6rem) 0; }
.seek__in { max-width: 1180px; margin: 0 auto; padding: 0 var(--pad); }
.seek__head { max-width: 58ch; }

.band {
  position: relative; margin-top: clamp(2.5rem, 5vw, 3.5rem);
  padding: 1.4rem 0 0; border-top: 1px solid var(--hair);
  overflow: hidden; touch-action: pan-y;
}
.band__noise {
  position: absolute; inset: 0; opacity: 0; pointer-events: none;
  background: repeating-linear-gradient(90deg,
    rgba(247, 244, 255, .07) 0 2px, transparent 2px 5px);
  transition: opacity .25s;
}
.band.is-seeking .band__noise { opacity: 1; }
.band__track {
  position: relative; display: grid;
  grid-auto-flow: column; grid-auto-columns: minmax(118px, 1fr);
  gap: 1px; background: var(--hair-soft);
}
.band__cell {
  display: block; padding: .95rem .85rem 1.1rem; text-align: left;
  background: var(--ink); cursor: pointer;
  transition: background .3s, translate .3s var(--ease);
}
.band__cell:hover { background: color-mix(in srgb, var(--glow) 16%, var(--ink)); }
.band__cell[aria-current="true"] { background: color-mix(in srgb, var(--glow) 26%, var(--ink)); }
.band__freq { display: block; font-family: var(--mono); font-size: .68rem; letter-spacing: .1em; color: var(--pale); }
.band__cs { display: block; margin-top: .5rem; font-family: var(--serif); font-size: 1.02rem; line-height: 1.15; color: var(--cloud); }
.band__kind { display: block; margin-top: .45rem; font-size: .62rem; letter-spacing: .13em; text-transform: uppercase; color: color-mix(in srgb, var(--cloud) 40%, transparent); }
.band__bars { display: flex; align-items: flex-end; gap: 2px; height: 11px; margin-top: .7rem; }
.band__bars i { flex: 1; background: var(--lavender); opacity: .22; }
.band__bars i:nth-child(1) { height: 32%; }
.band__bars i:nth-child(2) { height: 48%; }
.band__bars i:nth-child(3) { height: 64%; }
.band__bars i:nth-child(4) { height: 80%; }
.band__bars i:nth-child(5) { height: 100%; }
.band__cell[aria-current="true"] .band__bars i { opacity: .85; background: var(--pale); }

.band__cursor {
  position: absolute; left: 0; bottom: 0; width: 2px; height: 100%;
  background: var(--peach);
  transition: left .42s var(--ease), opacity .3s;
  box-shadow: 0 0 14px color-mix(in srgb, var(--peach) 70%, transparent);
}
.band__cursor.is-hunting { animation: hunt .22s linear infinite; }
@keyframes hunt { 0% { opacity: 1; } 50% { opacity: .25; } 100% { opacity: 1; } }

.readout {
  display: flex; flex-wrap: wrap; align-items: baseline; gap: .4rem 1rem;
  margin-top: 1.8rem; padding-top: 1.1rem; border-top: 1px solid var(--hair-soft);
}
.readout__cs { font-family: var(--serif); font-style: italic; font-size: clamp(1.3rem, 3vw, 1.9rem); }
.readout__freq { font-size: 1rem; color: var(--pale); }
.readout__sep { width: 22px; height: 1px; background: var(--hair); translate: 0 -5px; }
.readout__desc { font-size: .9rem; color: color-mix(in srgb, var(--cloud) 62%, transparent); }
.seek__note { margin-top: 1.4rem; font-size: .68rem; letter-spacing: .12em; text-transform: uppercase; color: color-mix(in srgb, var(--cloud) 34%, transparent); }

/* ── residents ────────────────────────────────────────────────────────── */
.crew { background: var(--ink); padding: clamp(3.5rem, 8vw, 6rem) 0 clamp(4rem, 9vw, 7rem); }
.crew__in { max-width: 1180px; margin: 0 auto; padding: 0 var(--pad); }
.crew__head { max-width: 60ch; }
.crew__list { margin-top: clamp(2rem, 4vw, 3rem); border-top: 1px solid var(--hair); }
.crew__list li {
  display: grid; gap: .15rem 1.6rem; align-items: baseline;
  grid-template-columns: 3rem minmax(0, 15ch) minmax(0, 1fr);
  padding: 1.35rem .3rem; border-bottom: 1px solid var(--hair-soft);
  transition: background .3s;
}
.crew__list li:hover { background: color-mix(in srgb, var(--lavender) 8%, transparent); }
.crew__n { font-size: .72rem; color: var(--peach); }
.crew__name { font-family: var(--serif); font-size: clamp(1.1rem, 2.4vw, 1.5rem); }
.crew__show { font-size: .68rem; letter-spacing: .12em; text-transform: uppercase; color: color-mix(in srgb, var(--cloud) 45%, transparent); }
.crew__note { grid-column: 2 / -1; font-size: .88rem; color: color-mix(in srgb, var(--cloud) 60%, transparent); }
@media (min-width: 860px) {
  .crew__list li { grid-template-columns: 3.4rem minmax(0, 16ch) minmax(0, 1fr); }
  .crew__note { grid-column: 3; text-align: right; }
}

/* ── footer ───────────────────────────────────────────────────────────── */
.tail { background: var(--dusk); padding: clamp(3.5rem, 8vw, 6rem) 0 3rem; border-top: 1px solid var(--hair-soft); }
.tail__in { max-width: 1180px; margin: 0 auto; padding: 0 var(--pad); }
.tail__word {
  font-family: var(--serif); font-size: clamp(2.6rem, 13vw, 8rem);
  line-height: .9; letter-spacing: -.03em; color: var(--cloud);
}
.tail__word span { display: block; font-style: italic; color: var(--lavender); translate: 0 .28em; }
.tail__cols {
  display: grid; gap: clamp(1.5rem, 3vw, 2.5rem);
  grid-template-columns: repeat(auto-fit, minmax(220px, 1fr));
  margin-top: clamp(2.5rem, 5vw, 3.5rem); padding-top: 1.8rem;
  border-top: 1px solid var(--hair);
}
.tail__cols h3 {
  font-family: var(--grotesk); font-size: .68rem; letter-spacing: .2em;
  text-transform: uppercase; color: var(--pale);
}
.tail__cols p { margin-top: .7rem; font-size: .88rem; color: color-mix(in srgb, var(--cloud) 62%, transparent); }
.tail__live {
  display: flex; align-items: center; gap: .5rem; margin-top: 2.6rem;
  font-size: .74rem; letter-spacing: .1em; color: color-mix(in srgb, var(--cloud) 50%, transparent);
}

/* ── narrow screens ───────────────────────────────────────────────────── */
@media (max-width: 899px) {
  .ruler__in { flex-wrap: wrap; row-gap: .5rem; }
  .ruler__nav { margin-left: 0; width: 100%; order: 3; padding-bottom: .15rem; }
  .ruler__live { margin-left: auto; }
  .dial { width: min(84vw, 320px); }
  .legend { max-width: 100%; }
  .callsign { font-size: clamp(2.9rem, 16vw, 4.6rem); }
  .tl li { grid-template-columns: 3.8rem minmax(0, 1fr); row-gap: .1rem; }
  .tl__n { display: none; }
  .tl__len { grid-column: 2; }
  .band__track { grid-auto-columns: minmax(96px, 1fr); }
}
@media (max-width: 560px) {
  .board__in { rotate: 0deg; }
  .recs__list { grid-template-columns: repeat(auto-fit, minmax(120px, 1fr)); gap: 1.6rem; }
  .rec__title { font-size: .95rem; }
}

/* ── material: grain, fibre, and the painted sky ──────────────────────────
   Everything above this line is layout and colour. Everything below is
   surface. The hero used to be three blurred ellipses on a linear gradient;
   it now has weather in it. */

/* One fixed overlay across the whole document. Two noise fields stacked: a
   fine isotropic speckle (paper tooth) and a coarse horizontal fibre (the
   direction of a brushed surface).

   NORMAL blend, not overlay, and that is measured rather than preferred.
   Overlay is base-preserving: on a ground this dark there is no tonal range
   for it to modulate, so it deposits nothing measurable. On the same patch
   overlay scored +0.10 sd, soft-light +0.26 and multiply -0.91, against
   normal +2.49 with the tooth rising 3.97 -> 4.22. Opacity .16 rather than
   .28 because .28 lifts the hero mean by nearly 13 levels and this world is
   already dark. */
.grain {
  position: fixed; inset: 0; z-index: 70; pointer-events: none;
  mix-blend-mode: normal; opacity: .16;
  animation: grain-jump 6s steps(4, jump-none) infinite;
}
.grain__svg { width: 100%; height: 100%; }
.grain__speckle { opacity: .55; }
.grain__fibre { opacity: .3; }
@keyframes grain-jump {
  0%   { transform: translate3d(0, 0, 0); }
  25%  { transform: translate3d(-1.2%, .8%, 0); }
  50%  { transform: translate3d(.9%, -1.1%, 0); }
  75%  { transform: translate3d(-.7%, -.6%, 0); }
  100% { transform: translate3d(0, 0, 0); }
}

/* sky layers */
.sky__bloom { transform-box: fill-box; transform-origin: 50% 50%; }
.sky__bloom { animation: bloom 13s ease-in-out infinite alternate; }
.sky__bloom--warm { animation-duration: 17s; animation-delay: -6s; }
.sky__bloom--pale { animation-duration: 21s; animation-delay: -11s; }
@keyframes bloom {
  from { opacity: .72; transform: scale(.97); }
  to   { opacity: 1;   transform: scale(1.05); }
}

.sky__aurora-veil { transform-box: fill-box; transform-origin: 50% 100%; }
.sky__aurora-veil--a { animation: veil 19s ease-in-out infinite alternate; }
.sky__aurora-veil--b { animation: veil 26s ease-in-out infinite alternate; animation-delay: -9s; }
@keyframes veil {
  from { opacity: .5; transform: scaleY(.9)  translateX(-8px); }
  to   { opacity: 1;  transform: scaleY(1.08) translateX(10px); }
}

/* Clouds drift at three speeds so the sky never reads as one moving card.
   Both ends of every keyframe are inside the frame, which is what lets the
   reduced-motion state land on a fully composed sky. */
.sky__cloud { transform-box: fill-box; }
.sky__cloud--a { animation: drift-a 74s ease-in-out infinite alternate; }
.sky__cloud--b { animation: drift-b 96s ease-in-out infinite alternate; }
.sky__cloud--c { animation: drift-c 58s ease-in-out infinite alternate; }
@keyframes drift-a { from { translate: -22px 0; } to { translate: 26px 0; } }
@keyframes drift-b { from { translate: 18px 0;  } to { translate: -20px 0; } }
@keyframes drift-c { from { translate: -14px 4px; } to { translate: 22px -3px; } }

/* the mast — a lattice tower, so the structure has to read at hero scale.
   Solid fills merge into a smudge; separate legs with visible bracing and
   guy wires hold their silhouette against the haze. */
.sky__mast-leg    { fill: var(--ink); opacity: .88; }
.sky__mast-brace  { fill: none; stroke: var(--ink); stroke-width: 1.1; opacity: .62; }
.sky__mast-guy    { fill: none; stroke: var(--ink); stroke-width: .7; opacity: .26; }
.sky__mast-yagi   { fill: none; stroke: var(--ink); stroke-width: 1.4; opacity: .8; stroke-linecap: round; }
.sky__mast-beacon {
  fill: #FF6B5A;
  transform-box: fill-box; transform-origin: 50% 50%;
  animation: beacon 1.7s ease-in-out infinite;
}
.sky__mast-halo {
  fill: none; stroke: #FF6B5A; stroke-width: 1;
  transform-box: fill-box; transform-origin: 50% 50%;
  animation: halo 3.4s ease-out infinite;
  opacity: .5;
}
@keyframes beacon {
  0%, 100% { opacity: .35; transform: scale(.86); }
  50%      { opacity: 1;   transform: scale(1.12); }
}
@keyframes halo {
  0%   { opacity: .55; transform: scale(.3); }
  70%  { opacity: 0;   transform: scale(1.5); }
  100% { opacity: 0;   transform: scale(1.5); }
}

/* stars: each one gets its own period and delay from world.js, so the sky
   shimmers irregularly instead of blinking as a block */
.sky__stars circle { fill: var(--cloud); animation: twinkle 5s ease-in-out infinite alternate; }
@keyframes twinkle { from { opacity: var(--o, .4); } to { opacity: .95; } }

/* the call sign is lit by the transmitter, so its glow breathes with it */
.callsign { animation: callsign-glow 9s ease-in-out infinite alternate; }
@keyframes callsign-glow {
  from { text-shadow: 0 0 46px color-mix(in srgb, var(--glow) 40%, transparent); }
  to   { text-shadow: 0 0 82px color-mix(in srgb, var(--glow) 68%, transparent); }
}

.vu {
  position: absolute; left: 50%; bottom: 8%; width: 58%; height: auto;
  translate: -50% 0; overflow: visible;
}
.vu__arc {
  fill: none; stroke: var(--hair); stroke-width: 1;
}
.vu__arc--red { stroke: #FF8B7A; stroke-width: 1.4; opacity: .8; }
.vu__ticks line { stroke: var(--hair); stroke-width: .9; }
.vu__ticks line.is-major { stroke: var(--pale); stroke-width: 1.3; }
.vu__needle {
  stroke: var(--peach); stroke-width: 1.5; stroke-linecap: round;
  transform-box: fill-box; transform-origin: 50% 100%;
  /* driven per-frame by world.js from the modelled programme level */
  rotate: calc(var(--vu, 0) * 1deg);
}
.vu__meter--b .vu__needle { stroke: var(--lavender); rotate: calc(var(--vu-b, 0) * 1deg); }
.vu__hub { fill: var(--ink); stroke: var(--hair); stroke-width: 1; }
.vu__peak {
  stroke: var(--pale); stroke-width: 1.4; stroke-linecap: round; opacity: .8;
  transform-box: fill-box; transform-origin: 50% 100%;
  rotate: calc(var(--vu-peak, 0) * 1deg);
}
.vu__meter--b .vu__peak { rotate: calc(var(--vu-peak-b, 0) * 1deg); stroke: var(--cloud); opacity: .55; }
/* meter face glass: a slow specular sweep, the detail that makes it read as
   a physical meter rather than two lines on a screen */
.vu__meter { animation: vu-glass 11s ease-in-out infinite alternate; }
.vu__meter--b { animation-duration: 14s; animation-delay: -4s; }
@keyframes vu-glass {
  from { opacity: .78; }
  to   { opacity: 1; }
}

/* ── records actually turn ──────────────────────────────────────────────
   The section is called "records in rotation" and nothing was rotating.
   Deeper sleeves turn faster, so --depth drives the period. translate and
   rotate are independent properties, so this composes with the existing
   hover lift instead of fighting it. */
.rec__disc::before {
  content: ""; position: absolute; inset: 0; border-radius: 50%;
  background: conic-gradient(from 0deg,
    rgba(247, 244, 255, .16) 0deg, rgba(247, 244, 255, 0) 34deg,
    rgba(11, 11, 20, .3) 150deg, rgba(11, 11, 20, 0) 220deg,
    rgba(247, 244, 255, .1) 300deg, rgba(247, 244, 255, 0) 360deg);
  animation: turn calc(9s / var(--depth, .4)) linear infinite;
  transform-origin: 50% 50%;
}
@keyframes turn { to { rotate: 360deg; } }

/* ── the waveform rules are live traces, not static borders ─────────────
   A hairline that sits still reads as a divider. One that carries a slow
   travelling pulse reads as a scope. */
.wave__line { animation: trace 9s linear infinite; }
.wave--mid .wave__line { animation-duration: 12s; animation-delay: -4s; }
@keyframes trace {
 from { stroke-dashoffset: var(--len, 2600); }
 to   { stroke-dashoffset: 0; }
}

/* signal strength is a live reading, so the lit bars shimmer */
.signal__bars i.is-on { animation: bar-live 1.4s ease-in-out infinite alternate; }
.signal__bars i:nth-child(1).is-on { animation-delay: -.2s; }
.signal__bars i:nth-child(2).is-on { animation-delay: -.5s; }
.signal__bars i:nth-child(3).is-on { animation-delay: -.8s; }
.signal__bars i:nth-child(4).is-on { animation-delay: -.3s; }
.signal__bars i:nth-child(5).is-on { animation-delay: -.9s; }
.signal__bars i:nth-child(6).is-on { animation-delay: -.6s; }
.signal__bars i:nth-child(7).is-on { animation-delay: -.1s; }
@keyframes bar-live { from { opacity: .62; } to { opacity: 1; } }

.band__cell[aria-current="true"] .band__bars i { animation: band-live 1.7s ease-in-out infinite alternate; }
.band__cell[aria-current="true"] .band__bars i:nth-child(2) { animation-delay: -.4s; }
.band__cell[aria-current="true"] .band__bars i:nth-child(3) { animation-delay: -.8s; }
.band__cell[aria-current="true"] .band__bars i:nth-child(4) { animation-delay: -1.2s; }
@keyframes band-live { from { opacity: .45; } to { opacity: .95; } }

/* the pin button has a lamp in it */
.pin__dot { animation: pin-lamp 3s ease-in-out infinite alternate; }
.pin[aria-pressed="true"] .pin__dot { animation: none; }
@keyframes pin-lamp { from { box-shadow: 0 0 0 0 rgba(17, 17, 29, .5); } to { box-shadow: 0 0 0 5px rgba(17, 17, 29, 0); } }

/* ── reduced motion ───────────────────────────────────────────────────────
   Stopping the motion must not strip the scene. Every ambient layer gets an
   explicit resting value here rather than being left to collapse onto
   whichever end-keyframe happens to exist: the sky keeps its clouds, veils,
   mast and lit beacon at full strength, the records keep their sheen, the
   meters keep a plausible steady deflection (world.js writes --vu on the
   first frame for exactly this reason), and the grain stays put. */
@media (prefers-reduced-motion: reduce) {
  html { scroll-behavior: auto; }
  *, *::before, *::after {
    animation-duration: .001ms !important;
    animation-iteration-count: 1 !important;
    transition-duration: .001ms !important;
  }
  .boot { display: none; }
  .eq i { animation: none; transform: scaleY(calc(.22 + var(--push) * .5)); }
  .band__cursor.is-hunting { animation: none; }
  .wave__line { stroke-dasharray: none; stroke-dashoffset: 0; }
  .sky { transition: none; }

  /* pinned resting compositions */
  .grain { animation: none; transform: none; }
  .sky__bloom { animation: none; opacity: .92; transform: scale(1.02); }
  .sky__bloom--warm { opacity: .86; }
  .sky__bloom--pale { opacity: .8; }
  .sky__aurora-veil--a { animation: none; opacity: .82; transform: scaleY(1); }
  .sky__aurora-veil--b { animation: none; opacity: .68; transform: scaleY(.96); }
  .sky__cloud--a { animation: none; translate: 0 0; }
  .sky__cloud--b { animation: none; translate: 0 0; }
  .sky__cloud--c { animation: none; translate: 0 0; }
  .sky__stars circle { animation: none; opacity: var(--o, .4); }
  .sky__mast-beacon { animation: none; opacity: 1; transform: scale(1); }
  .sky__mast-halo { animation: none; opacity: .3; transform: scale(.7); }
  .callsign { animation: none; text-shadow: 0 0 62px color-mix(in srgb, var(--glow) 55%, transparent); }
  .dial__needle { animation: none; rotate: 0deg; opacity: 1; }
  .vu__meter { animation: none; opacity: 1; }
  .rec__disc::before { animation: none; rotate: 24deg; }
  .signal__bars i.is-on,
  .band__cell[aria-current="true"] .band__bars i,
  .pin__dot { animation: none; }
  .signal__bars i.is-on { opacity: 1; }
  .band__cell[aria-current="true"] .band__bars i { opacity: .85; }
}