/* The Miller Lights, concept D.
   A's layout and hero (the house drawn in light). C's type (Anybody + Plex) and floating dock.
   B's scroll-driven light-ups. Native scroll only; every scroll effect is html.anim-gated, so
   reduced motion, no script, or an unsupported browser all get a still, complete page. */

@font-face {
  font-family: "Anybody";
  src: url("fonts/Anybody-wdth-wght-latin.woff2") format("woff2-variations"),
       url("fonts/Anybody-wdth-wght-latin.woff2") format("woff2");
  font-weight: 100 900;
  font-stretch: 50% 150%;
  font-display: swap;
}
/* site2/fonts2 ships IBMPlexSans-400/500/600 as byte-identical copies of one file (see concept C),
   so it is loaded once. */
@font-face { font-family: "Plex"; font-weight: 400 600; font-display: swap; src: url("/fonts2/IBMPlexSans-400.woff2") format("woff2"); }

/* --p: how far an element has lit up (0 dark, 1 lit). --q: where a parallax group sits in its pass.
   Both default to 1 / .5 so the still page is the finished, fully lit page. */
@property --p { syntax: "<number>"; inherits: true; initial-value: 1; }
@property --q { syntax: "<number>"; inherits: true; initial-value: 0.5; }

:root {
  color-scheme: dark;
  /* Palette: A's night, B's brightness, C's warm bulb */
  --night: #060A33;      /* Santa Teresa night: the sky in the all-blue photo */
  --deep: #03051C;       /* below the street lamps: footer, dialog */
  --pixel: #3D5BFF;      /* the house in blue: glows, focus, the lit edges */
  --ice: #EEF2FF;        /* cold white LED: text */
  --frost: #A9B4E0;      /* secondary text, 8:1 on the night */
  --bulb: #FFB547;       /* warm bulb: numerals 56px and larger only */
  --give: #2BD67B;       /* Give buttons */
  --live: #8CFF5E;       /* the show-live dot: a lit green LED, yellower and brighter than Give, always a dot */
  --give-ink: #03210F;
  --tally: #FF3348;      /* the on-air dot only */

  --display: "Anybody", "Arial Narrow", system-ui, sans-serif;
  --text: "Plex", system-ui, -apple-system, "Segoe UI", sans-serif;

  --gutter: clamp(16px, 4.5vw, 56px);
  /* The spacing scale (2026-11-05, "tighten the shit up"): every band of night between blocks is one of
     these, so the page reads at one rhythm. */
  --sp-section: clamp(64px, 7.5vw, 112px); /* between h2 sections */
  --sp-block: clamp(56px, 6.5vw, 96px);    /* h3 blocks and the loose photos */
  --sp-head: 32px;                         /* under a section title */
  --sp-top: clamp(40px, 7vw, 104px);       /* a subpage's top: the bar to its title */
  --fm-gap: clamp(14px, 3vw, 28px);        /* the two signs' gap (the on-foot line spans it on a phone) */
  --bar-h: 60px;
  --measure: 62ch;
  --r-photo: 22px;
  --r-pane: 32px;
  --glass: color-mix(in oklab, var(--night) 70%, transparent);
  --rule: color-mix(in oklab, var(--pixel) 35%, transparent);
}
/* The header's real heights now that its status line keeps its row from the first frame (2026-11-06):
   tablet 104 px, phone 144 px. --bar-hx (js/site.js) is then only needed when a night's notes add more. */
@media (max-width: 1023px) { :root { --bar-h: 104px; } }
@media (max-width: 640px) { :root { --bar-h: 144px; } }
/* The dock: how far it floats above the bottom edge, and the full player's height, which the hero
   keeps clear for it (js/dock.js measures the real height and writes --dock-full-h; these are the
   usual heights, so the first paint is already right). */
:root { --foot-h: 56px; --dock-bottom: calc(var(--foot-h) + env(safe-area-inset-bottom, 0px) + 12px); --dock-full-h: 140px; }
/* The footer bar (fixed, like the header) costs the first screen nothing: the hero grows by exactly
   what the dock moved up, so the stage and the yard keep their size and place (the hero is Nate's). */
@media (max-width: 860px) { :root { --dock-full-h: 250px; } }
@media (max-width: 479px) { :root { --dock-full-h: 300px; } }

*, *::before, *::after { box-sizing: border-box; }
[hidden] { display: none !important; }
html { -webkit-text-size-adjust: 100%; text-size-adjust: 100%; background: var(--night); scroll-padding-top: var(--bar-hx, var(--bar-h)); scroll-padding-bottom: calc(var(--dock-bottom) + 56px + 16px); } /* a focused field scrolls clear of the fixed footer and the compact dock */
/* The jump links and Top jump, they never glide (Nate, 2026-10-21: "they should jump, not
   scroll"). Stated, so nothing inherited can turn smooth scrolling back on. */
html { scroll-behavior: auto; }
/* The jump targets are the section titles themselves (each id sits on its section's h2), so a jump
   puts the title at the top of the screen, 14 px under the bar. The ids used to sit on the
   sections, whose top padding (72 to 150 px) and, for the snow, a sticky offset put the title part
   way down the screen (Nate, 2026-10-21). */
main h2[id] { scroll-margin-top: 14px; }

body {
  margin: 0;
  background: var(--night);
  color: var(--ice);
  font: 400 1.0625rem/1.6 var(--text);
  overflow-x: clip;
}
@media (min-width: 720px) { body { font-size: 1.125rem; } }

img { max-width: 100%; height: auto; display: block; }
picture { display: block; }
figure { margin: 0; }
a { color: inherit; text-decoration-thickness: 1px; text-underline-offset: .2em; }
p { margin: 0 0 1em; max-width: var(--measure); text-wrap: pretty; }
h1, h2, h3 { margin: 0; font-weight: 700; text-wrap: balance; }
button { font: inherit; color: inherit; }

:focus-visible {
  outline: 2px solid var(--ice);
  outline-offset: 3px;
  box-shadow: 0 0 0 6px color-mix(in oklab, var(--pixel) 55%, transparent);
  border-radius: 4px;
}

.skip {
  position: fixed; left: var(--gutter); top: -100px; z-index: 60;
  background: var(--ice); color: var(--night); padding: 10px 16px; border-radius: 999px; text-decoration: none; font-weight: 600;
}
.skip:focus { top: 12px; }
.sr { position: absolute; width: 1px; height: 1px; overflow: hidden; clip-path: inset(50%); white-space: nowrap; }

/* Anybody: the display voice (C). Wide and heavy for headlines and numerals. */
.hero-title, h2, .h3, .beat-h, .np-title, .np-mini-title, .onair-title, .ro-val, .freq-num,
.snow-num, .give-num, .foot-mark, .bar-mark, .hw-addr, .community-date, .art-placeholder span {
  font-family: var(--display);
}

/* ---------- Header: A's bar, every section one tap away ---------- */
.bar {
  position: sticky; top: 0; z-index: 30;
  display: flex; align-items: center; gap: 12px 20px;
  min-height: var(--bar-h);
  padding: 8px var(--gutter);
  background: color-mix(in oklab, var(--night) 72%, transparent);
  -webkit-backdrop-filter: blur(14px) saturate(1.2); backdrop-filter: blur(14px) saturate(1.2);
  border-bottom: 1px solid color-mix(in oklab, var(--pixel) 18%, transparent);
}
.bar-mark {
  font-weight: 760; font-stretch: 125%; font-size: 1.15rem; letter-spacing: -0.01em;
  text-decoration: none; white-space: nowrap; transition: opacity .3s;
}
.bar[data-over-hero] .bar-mark { opacity: 0; pointer-events: none; }
.jump { margin-left: auto; min-width: 0; overflow-x: auto; scrollbar-width: none; }
.jump::-webkit-scrollbar { display: none; }
.bar-mark { flex: none; }
.jump ul { list-style: none; margin: 0; padding: 0; display: flex; gap: 2px; }
.jump a {
  display: block; padding: 8px 12px; border-radius: 999px; white-space: nowrap;
  color: var(--frost); text-decoration: none; font-size: .95rem; font-weight: 600;
  transition: color .2s, background-color .2s;
}
.jump a:hover { color: var(--ice); background: color-mix(in oklab, var(--pixel) 18%, transparent); }
.jump a[aria-current] { color: var(--ice); background: color-mix(in oklab, var(--pixel) 34%, transparent); box-shadow: 0 0 18px -4px color-mix(in oklab, var(--pixel) 80%, transparent); }

/* Is the show on: the dot and a few words, beside the wordmark (js/main.js). */
.bar-live { display: inline-flex; align-items: center; gap: 7px; flex: none; margin: 0; font-size: .85rem; font-weight: 600; color: var(--frost); white-space: nowrap; }
.bar-live[data-live="on"] { color: var(--live); }
.bar-live[hidden] { display: none; }
/* Waiting for the schedule: in its place, invisible (js/site.js barLive). */
.bar-live[data-wait] { visibility: hidden; }
/* Tonight's notes with the status: rain night, Community Night (js/main.js). The header never grows
   for them (its height, --bar-h, sets where a jump lands and the room the hero keeps). Phones and
   tablets: a column beside the status, one note per line, no taller than the status's two lines.
   Desktop: the status on one line and the notes under it, at most three short lines in the 60 px
   bar, never wider than the room at the top left over the hero (about 148 px at 1024). */
.bar-live:has(.bar-note:not([hidden])) { display: inline-grid; grid-template-columns: auto auto auto; column-gap: 7px; align-items: center; }
.bar-note { font-size: .72rem; line-height: 1.15; color: var(--ice); white-space: pre; padding-left: 7px; border-left: 1px solid color-mix(in oklab, var(--frost) 40%, transparent); }
.bar-note[hidden] { display: none; }
/* The status's two halves: what, and the countdown to it (js/main.js). On one line they read
   "Show Starts Dec 4, 5:00 PM · In 30 Days"; stacked, the countdown is the second line. */
.bar-live [data-bar-live] > span:empty { display: none; }
.bar-live [data-bar-live] > span + span::before { content: " · "; }
@media (min-width: 1024px) {
  .bar-live:has(.bar-note:not([hidden])) { grid-template-columns: auto auto; row-gap: 1px; line-height: 1.15; }
  .bar-live:has(.bar-note:not([hidden])) [data-bar-live] > span { display: inline; }
  .bar-live:has(.bar-note:not([hidden])) [data-bar-live] > span + span::before { content: " · "; }
  .bar-note { grid-column: 2; padding-left: 0; border-left: 0; }
}
/* 1024 to 1279: a size smaller on those nights, so the one-line status clears the links. */
@media (min-width: 1024px) and (max-width: 1279px) { .bar-live:has(.bar-note:not([hidden])) { font-size: .7rem; } .bar-note { font-size: .66rem; } }
/* Phones: the notes on their own lines under the status, wrapping inside the screen. */
@media (max-width: 1023px) {
  /* The status's two lines plus a note line, held from the first frame, so the header never grows
     when the schedule arrives or a note comes and goes (Nate, 2026-11-06: yes). */
  .bar-live { min-height: 2.6rem; }
  .bar-live:has(.bar-note:not([hidden])) { grid-template-columns: auto minmax(0, 1fr); row-gap: 2px; max-width: 100%; }
  .bar-note { grid-column: 2; padding-left: 0; border-left: 0; white-space: pre-line; }
}
/* Desktop over the hero: the status in the wordmark's place at the top left, sliding right to its place after the wordmark
   as the wordmark fills in (js/main.js measures --live-shift). It slides only then: js/main.js sets
   data-live-sliding for that move alone, so a page load, the feed arriving or a resize never animate. */
@media (min-width: 1024px) {
  /* its place: right after the wordmark (Nate, 2026-11-04: "slide to the right of where the miller
     lights is written, not the other side of the header"); the links and Give keep
     theirs at the right */
  .bar .bar-mark { order: 0; } .bar .bar-live { order: 1; } .bar .jump { order: 2; } .bar .give-pill { order: 3; }
  .bar[data-over-hero] .bar-live { translate: var(--live-shift, 0) 0; }
  .bar[data-live-sliding] .bar-live { transition: translate .45s ease; }
}
@media (prefers-reduced-motion: reduce) { .bar[data-live-sliding] .bar-live { transition: none; } }
/* 1024 to 1599 px the header is one row with seven links: the words stack in two short lines to
   leave them room (all seven fit from about 1360 px; below that the row scrolls sideways and fades
   at its edge, as it always has when they do not fit). */
@media (min-width: 1024px) and (max-width: 1599px) {
  .bar-live { font-size: .78rem; line-height: 1.15; }
  .bar-live [data-bar-live] > span { display: block; white-space: nowrap; }
  .bar-live [data-bar-live] > span + span::before { content: none; }
}
@media (min-width: 1440px) and (max-width: 1599px) { .bar { column-gap: 14px; } }
/* A phone: the first row is the wordmark and Give, so the dot and its two short lines lead the
   second row, before the chips. */
@media (max-width: 640px) {
  /* order 2: ahead of the chips (order 4) on the second row, as before the status moved beside
     Give in the HTML; Give keeps its own push to the right edge of the first row. */
  .bar .bar-live { order: 2; font-size: .75rem; line-height: 1.1; gap: 6px; }
  /* Its own row from the first frame: empty, it was narrow enough for the chips to share its row,
     and filling in pushed them down a row (the header 100 -> 144 px at 390 wide). */
  .bar .bar-live { flex-basis: 100%; }
  .bar { row-gap: 6px; }
  .bar-live [data-bar-live] > span { display: block; white-space: nowrap; }
  .bar-live [data-bar-live] > span + span::before { content: none; }
}
/* 320 px: a narrower wordmark so Give fits beside it on the first row (the header 197 -> 144 px). */
@media (max-width: 359px) { .bar-mark { font-size: 1.05rem; font-stretch: 110%; } }

.give-pill {
  display: inline-flex; align-items: center; justify-content: center; flex: none;
  min-height: 40px; padding: 0 18px; border-radius: 999px;
  background: var(--give); color: var(--give-ink); font-weight: 600; text-decoration: none;
  transition: box-shadow .2s;
}
.give-pill:hover { box-shadow: 0 0 0 4px color-mix(in oklab, var(--give) 30%, transparent), 0 0 24px color-mix(in oklab, var(--give) 50%, transparent); }

/* 1024 to 1279 px: the wordmark, the status, the links and Give in one row. Tighter spacing
   and a narrower wordmark make them fit (measured: 1024 px has 4+ px to spare). If they still do
   not (a larger default font, zoom), the row scrolls sideways and fades at the edge that has more,
   so no link is ever cut off without a sign. */
@media (min-width: 1024px) and (max-width: 1279px) {
  .bar { gap: 12px 8px; padding-inline: 16px; }
  .bar-mark { font-stretch: 92%; font-size: 1.05rem; }
  .jump a { padding: 8px 4px; font-size: .82rem; }
  .jump ul { gap: 0; } /* room for "Contact Us" (2026-11-05) */
  .give-pill { padding: 0 15px; }
}
/* 1280 to 1439 px: the full wordmark is back, so the links tighten a little to keep all seven,
   Streaming Radio included, in the row. */
@media (min-width: 1280px) and (max-width: 1439px) {
  .bar { gap: 12px 14px; }
  .jump a { padding: 8px 5px; font-size: .88rem; }
  .jump ul { gap: 0; } /* room for "Contact Us" (2026-11-05) */
}
@media (min-width: 1024px) {
  .jump[data-more~="end"] { -webkit-mask-image: linear-gradient(to right, #000 calc(100% - 44px), transparent); mask-image: linear-gradient(to right, #000 calc(100% - 44px), transparent); }
  .jump[data-more~="start"] { -webkit-mask-image: linear-gradient(to right, transparent, #000 44px); mask-image: linear-gradient(to right, transparent, #000 44px); }
  .jump[data-more~="start"][data-more~="end"] { -webkit-mask-image: linear-gradient(to right, transparent, #000 44px, #000 calc(100% - 44px), transparent); mask-image: linear-gradient(to right, transparent, #000 44px, #000 calc(100% - 44px), transparent); }
}

/* Tablets keep the wordmark in the header over the hero; phones fade it while the hero's own title
   is on screen, like desktop (Nate, 2026-11-02: "why does it say the miller lights twice"). */
@media (min-width: 641px) and (max-width: 1023px) { .bar[data-over-hero] .bar-mark { opacity: 1; pointer-events: auto; } }
/* Tablets: the status sits beside Give at the end of the first row (phones put it on the second). */
@media (min-width: 641px) and (max-width: 1023px) { .bar .bar-live { margin-left: auto; } .bar .bar-live + .give-pill { margin-left: 0; } }

/* Under 1024px the jump links become a row of chips under the wordmark that scrolls sideways. */
@media (max-width: 1023px) {
  .bar { flex-wrap: wrap; padding-bottom: 6px; }
  .bar .give-pill { margin-left: auto; }
  /* Their own row, always: the chips scrolling sideways, edge to edge, full width (with the Visit
     pill gone nothing else pushes them down, and beside the status they shrank to a sliver). */
  .jump {
    order: 4; flex: 0 0 calc(100% + 2 * var(--gutter)); margin: 0 calc(-1 * var(--gutter));
    overflow-x: auto; scrollbar-width: none; overscroll-behavior-x: contain;
    -webkit-mask-image: linear-gradient(to right, transparent, #000 var(--gutter), #000 calc(100% - 40px), transparent);
            mask-image: linear-gradient(to right, transparent, #000 var(--gutter), #000 calc(100% - 40px), transparent);
  }
  .jump::-webkit-scrollbar { display: none; }
  .jump ul { padding: 0 var(--gutter); width: max-content; }
  .jump a { padding: 6px 12px; font-size: .9rem; background: color-mix(in oklab, var(--pixel) 12%, transparent); margin-right: 4px; }
}

/* ---------- Hero: concept A, in C's type ---------- */
/* The title sits top left and 94.3 top right. Everything else on the first screen (play, what's on,
   the readouts, the live dot, Give) is the floating dock (see "The dock" below). The house fills the
   stage between them: js/field.js fits the whole yard into it, so the dock never covers a prop. The
   hero keeps room for the dock's full height at its foot (--dock-full-h, measured by js/dock.js). */
.hero {
  position: relative;
  min-height: calc(100svh + var(--dock-bottom) - max(12px, env(safe-area-inset-bottom, 0px)));
  display: grid;
  grid-template-columns: 1fr auto;
  /* minmax(0, 1fr): the stage is what is left of the first screen. With a 180 px floor (before
     2026-10-21) a short screen (1366x650) made the hero taller than the screen, the stage ran on
     under the fixed dock, and up to 195 lights were fitted under it. */
  grid-template-rows: auto minmax(0, 1fr);
  grid-template-areas: "top where" "stage stage";
  row-gap: clamp(12px, 2.4vh, 28px);
  padding: calc(var(--bar-hx, var(--bar-h)) + clamp(8px, 2vw, 24px)) var(--gutter) calc(var(--dock-full-h) + var(--dock-bottom) + clamp(10px, 2vh, 20px));
  margin-top: calc(-1 * var(--bar-hx, var(--bar-h)));
  isolation: isolate;
}
.field {
  position: absolute; inset: 0; z-index: -1; overflow: hidden;
  background: radial-gradient(120% 70% at 50% 40%, color-mix(in oklab, var(--pixel) 14%, var(--night)) 0%, var(--night) 55%, var(--deep) 100%);
}
.field canvas { position: absolute; inset: 0; width: 100%; height: 100%; display: block; transition: opacity .6s ease; }
.field-photo img {
  position: absolute; inset: 0; width: 100%; height: 100%; object-fit: cover; object-position: 58% 42%;
  transition: filter .6s ease;
}
.field-ext { display: none; }
/* Which photo: the lit one without the light show, the bulbs-out one under it. Hidden pictures are
   lazy or never shown, so a visitor downloads one photo. */
.field-photo-off, .js .field-photo-lit { display: none; }
.js .field-photo-off, .hero[data-field="off"] .field-photo-lit { display: inline; }
.hero[data-field="off"] .field-photo-off { display: none; }
/* The real photo is always there. With the light show running (or about to), it is dimmed so its
   own bulbs read as switched off and only the drawn lights shine. Without WebGL it stays lit. */
.hero { --photo-glow: brightness(.48) saturate(.95) contrast(1.25); --photo-dark: brightness(.3) saturate(.4) contrast(1.1); }
.js .field-photo img, .js .field-ext img { filter: var(--photo-dark); }
/* The photo waits, invisible, until js/field.js placePhoto() has fitted it (data-photo), so it never
   paints at the CSS position and then jumps (Nate, 2026-11-06: yes). If the script never places it,
   it shows where the CSS puts it after 3 s, so a failed script can never leave the hero empty. */
.js .hero:not([data-photo]) .field-photo img, .js .hero:not([data-photo]) .field-blowups img { visibility: hidden; animation: photo-wait 0s linear 3s forwards; }
@keyframes photo-wait { to { visibility: visible; } }
/* Three layers of the same photo (Nate, 2026-11-04: the blue glow was "drowning out everything
   else... I only want it on the sidewalk/street and off the trees on the sidewalk... I also see the
   glow on the singing faces which I don't want either"). The base is dark: no blue wash on the house,
   the left side, the hill or the singing faces. Over it, .field-glow keeps the photo's own light at
   about half strength, in full colour, only through img/hero-glow-mask.svg (the sidewalk, the
   street and the ground; not the trees: "doesn't look right", 2026-11-04), and .field-bulbs keeps the static white and
   blue bulbs on the hill bright through img/hero-bulbs-mask.svg. The inflatables cut-out shows them
   in their own colours. Only with the light show running: without it the photo shows as taken,
   already lit. The show section's light-up photo is not touched. */
.field-glow { display: none; }
.hero[data-field="on"][data-photo] .field-glow { display: inline; }
.hero[data-photo] .field-glow img {
  position: absolute; filter: var(--photo-glow);
  -webkit-mask-image: url(img/hero-glow-mask.svg), linear-gradient(to right, transparent 11%, #000 16%), linear-gradient(to bottom, transparent, #000 19%, #000 66%, transparent 92%);
  -webkit-mask-size: 100% 100%; -webkit-mask-repeat: no-repeat;
  -webkit-mask-composite: source-in, source-in;
          mask-image: url(img/hero-glow-mask.svg), linear-gradient(to right, transparent 11%, #000 16%), linear-gradient(to bottom, transparent, #000 19%, #000 66%, transparent 92%);
          mask-size: 100% 100%; mask-repeat: no-repeat;
          mask-composite: intersect, intersect;
}
.field-defs { position: absolute; width: 0; height: 0; overflow: hidden; }
.field-bulbs { display: none; }
.hero[data-field="on"][data-photo] .field-bulbs { display: inline; }
.hero[data-photo] .field-bulbs img {
  position: absolute; filter: url(#hero-bulbs);
  -webkit-mask-image: url(img/hero-bulbs-mask.svg), linear-gradient(to right, transparent 11%, #000 16%), linear-gradient(to bottom, transparent, #000 19%, #000 66%, transparent 92%);
  -webkit-mask-size: 100% 100%; -webkit-mask-repeat: no-repeat;
  -webkit-mask-composite: source-in, source-in;
          mask-image: url(img/hero-bulbs-mask.svg), linear-gradient(to right, transparent 11%, #000 16%), linear-gradient(to bottom, transparent, #000 19%, #000 66%, transparent 92%);
          mask-size: 100% 100%; mask-repeat: no-repeat;
          mask-composite: intersect, intersect;
}
.field-blowups { display: none; }
.hero[data-field="on"][data-photo] .field-blowups { display: inline; }
.hero[data-field="on"][data-photo] .field-blowups img { position: absolute; }
.hero[data-field="off"] .field-photo img, .hero[data-field="off"] .field-ext img { filter: none; }
/* Placed by js/field.js: the photo is centred between the first arch and the right singing tree
   (Nate, 2026-10-21), with the night beyond the tree continued from the photo's own last columns
   (.field-ext, placed flush against the photo's right edge). The crop fades in after the stop sign
   (photo x 132 to 192 of 1200) and out the same distance past the tree (x 1231 to 1291, inside the
   continuation), so the frame is even on both sides. Top and bottom fade into the night. */
.hero[data-photo] .field-ext { display: block; }
.hero[data-photo] .field-ext img { position: absolute; transition: filter .6s ease; }
.hero[data-photo] .field-photo img, .hero[data-photo] .field-blowups img {
  -webkit-mask-image: linear-gradient(to right, transparent 11%, #000 16%), linear-gradient(to bottom, transparent, #000 19%, #000 66%, transparent 92%);
  -webkit-mask-composite: source-in;
          mask-image: linear-gradient(to right, transparent 11%, #000 16%), linear-gradient(to bottom, transparent, #000 19%, #000 66%, transparent 92%);
          mask-composite: intersect;
}
.hero[data-photo] .field-ext img {
  -webkit-mask-image: linear-gradient(to right, #000 31%, transparent 91%), linear-gradient(to bottom, transparent, #000 19%, #000 66%, transparent 92%);
  -webkit-mask-composite: source-in;
          mask-image: linear-gradient(to right, #000 31%, transparent 91%), linear-gradient(to bottom, transparent, #000 19%, #000 66%, transparent 92%);
          mask-composite: intersect;
}

.hero-top { grid-area: top; align-self: start; }
.hero-title {
  font-size: clamp(3rem, 6.4vw, 6.2rem); font-weight: 760; font-stretch: 128%;
  line-height: .86; letter-spacing: -0.035em; margin-left: -0.03em; max-width: 9ch;
  text-shadow: 0 0 40px color-mix(in oklab, var(--pixel) 45%, transparent);
}
.stage { grid-area: stage; }

.hero-where { grid-area: where; justify-self: end; align-self: start; display: grid; justify-items: end; gap: 14px; padding-top: 8px; text-align: right; }
.hw-place { display: grid; justify-items: end; gap: 6px; text-decoration: none; color: var(--ice); }
.hw-addr {
  font-size: clamp(1.25rem, 2vw, 2.1rem); font-weight: 740; font-stretch: 112%; line-height: 1.05; letter-spacing: -0.02em;
  text-shadow: 0 0 28px color-mix(in oklab, var(--pixel) 55%, transparent);
}
.hw-addr > span { display: block; white-space: nowrap; }
.hw-times { color: var(--frost); font-size: clamp(.95rem, 1.2vw, 1.1rem); line-height: 1.35; max-width: 24ch; text-wrap: balance; }
.hw-place:hover .hw-addr { text-decoration: underline; text-decoration-color: var(--pixel); text-decoration-thickness: 2px; text-underline-offset: 6px; }
/* Wider than a phone: the words make one band across the top, the title on one line with its
   subtitle under it on the left, the address, the times and Visit The Show on the right, so they
   take as little height as they can and the house below can be as wide as the screen allows,
   centred under them (Nate, 2026-10-21: "so the picture can be wider and centered under the miller
   lights title subtext and the address/show info on the right"). Chosen over the two-line title in
   the corners and a centred masthead (the README, "The bigger house"). */
@media (min-width: 861px) {
  .hero-title { font-size: clamp(2.6rem, 4.4vw, 4.8rem); max-width: none; white-space: nowrap; }
  .hero-where { gap: 10px; padding-top: 4px; }
  .hw-addr { font-size: clamp(1.15rem, 1.6vw, 1.7rem); }
  .hw-addr > span { display: inline; }
  .hw-times { max-width: none; }
}

/* Listen To Our Lights, in The Show under the request block (Nate, 2026-11-04): "94.3 FM" in one
   size and colour ("make FM bigger or the same font and color"), the 94.3 sign and the Press for
   Music sign side by side, the on-foot line below them. */
.fm { margin-top: var(--sp-block); display: grid; grid-template-columns: minmax(0, 1fr); gap: 18px; justify-items: start; }
.fm .h3 { margin: 0; }
.freq { margin: 0; }
.freq-num {
  /* sized so the one line "94.3 FM" fits a 320 px phone (about 5.3 em wide) */
  font-size: clamp(2.9rem, 14vw, 8.5rem); font-weight: 800; font-stretch: 140%; line-height: .8; letter-spacing: -0.05em;
  color: var(--bulb); font-variant-numeric: tabular-nums; white-space: nowrap;
  text-shadow: 0 0 .35em color-mix(in oklab, var(--bulb) 45%, transparent);
}
.fm-pics { display: grid; grid-template-columns: repeat(2, minmax(0, 300px)); gap: var(--fm-gap); width: 100%; }
.fm-pics .sign { max-width: none; margin: 0; }
.fm-pics .sign picture img { width: 100%; aspect-ratio: 4 / 5; object-fit: cover; }
.fm-pics .sign:first-child picture img { object-position: 50% 30%; }
.fm-pics .sign--onfoot picture img { object-position: 35% 82%; }

.status { margin: 0; }
.status[data-live]::before {
  content: ""; display: inline-block; width: .55em; height: .55em; border-radius: 50%;
  background: var(--live); margin-right: .5em; box-shadow: 0 0 10px var(--live); vertical-align: .05em;
}
.play-show { text-align: left; }
.play-words { display: grid; gap: 2px; }
.play-show .play-text { font: 700 1.15rem/1.15 var(--display); font-stretch: 112%; letter-spacing: -0.01em; }
.play-sub { color: var(--frost); font-size: .85rem; line-height: 1.3; max-width: 24ch; }
.play-show:hover .play-text { text-decoration: underline; text-decoration-color: var(--pixel); text-underline-offset: 4px; }
.np-mini { min-width: 0; }
.np-mini p { margin: 0; }
.np-mini-title { font-size: clamp(1.1rem, 1.6vw, 1.3rem); font-weight: 720; font-stretch: 112%; line-height: 1.05; letter-spacing: -0.02em; overflow-wrap: break-word; }
.np-mini-title[data-long] { font-size: clamp(1rem, 1.3vw, 1.1rem); }
/* Two lines at most in the dock, so a long title never pushes the readouts off screen. */
.np-mini-title { display: -webkit-box; -webkit-box-orient: vertical; -webkit-line-clamp: 2; line-clamp: 2; overflow: hidden; }
.np-mini-artist { color: var(--frost); font-size: .85rem; margin-top: 1px !important; }
/* Fixed slots in the full player: the title always has room for two lines and the artist for one,
   whatever the song, so the player's height (which the hero keeps clear, js/dock.js) never changes
   with the music and the house never resizes mid-song. */
.dock-full .np-mini { min-height: calc(.78rem * 1.6 + 2px + 2 * 1.05 * clamp(1.1rem, 1.6vw, 1.3rem) + .85rem * 1.6 + 1px); }
.dock-full .np-mini-artist { min-height: calc(.85rem * 1.6); white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }
.src { color: var(--frost); font-size: .85rem; margin: 0 0 6px; }
.np-mini .src { margin-bottom: 2px; font-size: .78rem; }
.readouts { display: flex; flex-wrap: wrap; gap: 6px 26px; margin: 0; justify-content: flex-end; }
.ro dt { color: var(--frost); font-size: .74rem; }
.ro dd { margin: 2px 0 0; }
.ro-val { font-size: 1.75rem; font-weight: 760; font-stretch: 125%; line-height: 1; letter-spacing: -0.03em; font-variant-numeric: tabular-nums; }
.provenance { color: var(--frost); font-size: .78rem; line-height: 1.35; margin: 0; max-width: none; flex: 1 1 320px; font-style: italic; } /* sets it apart (Nate, 2026-11-04) */

/* ---------- Buttons ---------- */
.play {
  --s: 60px;
  display: inline-flex; align-items: center; gap: 12px; flex: none;
  background: none; border: 0; padding: 0; cursor: pointer; color: var(--ice);
}
.play-icon {
  width: var(--s); height: var(--s); border-radius: 50%; flex: none; position: relative; display: block;
  background: var(--ice);
  box-shadow: 0 0 0 6px color-mix(in oklab, var(--pixel) 35%, transparent), 0 0 32px color-mix(in oklab, var(--pixel) 60%, transparent);
}
.play-icon::after {
  content: ""; position: absolute; inset: 0; margin: auto; width: 0; height: 0; translate: 3px 0;
  border-left: calc(var(--s) * .3) solid var(--night);
  border-top: calc(var(--s) * .19) solid transparent;
  border-bottom: calc(var(--s) * .19) solid transparent;
}
[aria-pressed="true"] > .play-icon::after {
  translate: 0 0; width: calc(var(--s) * .3); height: calc(var(--s) * .34); border: 0;
  background: linear-gradient(to right, var(--night) 0 36%, transparent 36% 64%, var(--night) 64%);
}
[data-busy] > .play-icon { opacity: .7; }
.console .play:not(.play-show) .play-text, .dock-play .play-text { position: absolute; width: 1px; height: 1px; overflow: hidden; clip-path: inset(50%); white-space: nowrap; }
.play-lg { --s: 64px; font-weight: 600; }
.play:hover .play-icon { background: white; }

.ghost {
  background: none; border: 1px solid color-mix(in oklab, var(--frost) 50%, transparent);
  color: var(--ice); padding: 7px 14px; border-radius: 999px; cursor: pointer; font-size: .85rem;
  position: relative;
}
/* a 44 px thumb target around the 36 px button, with no change to its look (Pause The Lights over the
   hero photo, the pop-outs' Close) */
.ghost::after { content: ""; position: absolute; inset: -4px; }
.ghost:hover { border-color: var(--ice); }
.ghost[aria-pressed="true"] { background: color-mix(in oklab, var(--pixel) 30%, transparent); border-color: var(--pixel); }
.btn-outline {
  display: inline-flex; align-items: center; min-height: 48px;
  background: none; border: 1.5px solid var(--pixel); color: var(--ice); padding: 10px 22px;
  border-radius: 999px; cursor: pointer; font-weight: 600; text-decoration: none;
}
.btn-outline:hover { background: color-mix(in oklab, var(--pixel) 25%, transparent); }
.link { color: var(--ice); text-decoration-color: color-mix(in oklab, var(--pixel) 80%, transparent); text-underline-offset: 4px; text-decoration-thickness: 2px; }
.link:hover { text-decoration-color: var(--ice); }
.fine { color: var(--frost); font-size: .875rem; }

/* ---------- Sections ---------- */
main > section:not(.hero) { padding: var(--sp-section) var(--gutter) 0; }
/* The Show follows the hero at a block's distance, not a section's: the hero's own foot (the room it
   keeps for the floating player) already parts them. Portrait phones pull it up by the same block so
   the heading sits right at the hero's edge; landscape phones and a 320x568 SE are left alone. */
main > section.house { padding-top: var(--sp-block); }
@media (max-width: 860px) and (orientation: portrait) and (min-height: 600px) { .house { margin-top: -56px; } }
.sec-head { margin-bottom: var(--sp-head); }
h2 {
  font-size: clamp(2.6rem, 7vw, 6rem); font-weight: 720; font-stretch: 120%;
  line-height: .92; letter-spacing: -0.035em; margin-bottom: .3em;
}
.lede { font-size: clamp(1.1rem, 1.7vw, 1.3rem); line-height: 1.5; }
.h3 { font-size: clamp(1.5rem, 2.6vw, 2.1rem); font-weight: 700; font-stretch: 112%; line-height: 1.05; letter-spacing: -0.015em; margin-bottom: .55em; }

/* Photos share one shape language: soft corners, lit out of the dark as they come up. */
.lit { position: relative; }
.lit picture { position: relative; overflow: hidden; border-radius: var(--r-photo); isolation: isolate; }
.lit picture img { width: 100%; }
.lit picture::after {
  /* the darkness the photo is lit out of (B). Opacity only, so it stays on the compositor. */
  content: ""; position: absolute; inset: 0; pointer-events: none; background: var(--deep); opacity: 0;
}

/* ---------- Station ---------- */
.sec-head--split { display: grid; gap: 24px clamp(32px, 6vw, 96px); align-items: end; }
@media (min-width: 860px) { .sec-head--split { grid-template-columns: minmax(0, 1.6fr) minmax(0, 1fr); } }
.sign { max-width: 300px; }
.sign picture img { aspect-ratio: 885 / 900; object-fit: cover; object-position: 50% 30%; }

.station { --art: var(--pixel); position: relative; }
.station::before {
  content: ""; position: absolute; inset: 0; z-index: -1; pointer-events: none;
  background: radial-gradient(60% 50% at 20% 50%, color-mix(in oklab, var(--art) 22%, transparent), transparent 70%);
}
.np {
  display: grid; grid-template-columns: minmax(0, 460px) minmax(0, 1fr); gap: clamp(24px, 5vw, 72px); align-items: center;
  padding: clamp(18px, 3vw, 40px); border-radius: var(--r-pane);
  background: color-mix(in oklab, var(--night) 55%, transparent);
  -webkit-backdrop-filter: blur(24px) saturate(1.3); backdrop-filter: blur(24px) saturate(1.3);
  box-shadow: inset 0 0 0 1px color-mix(in oklab, var(--pixel) 24%, transparent);
}
.np-art { position: relative; aspect-ratio: 1; width: 100%; }
.np-art img, .art-placeholder {
  position: absolute; inset: 0; width: 100%; height: 100%; object-fit: cover; border-radius: var(--r-photo);
  box-shadow: 0 30px 80px -20px color-mix(in oklab, var(--art) 70%, transparent);
}
.art-placeholder {
  display: grid; place-items: center; color: var(--ice);
  background:
    radial-gradient(30% 30% at 50% 38%, color-mix(in oklab, var(--bulb) 55%, transparent), transparent 70%),
    radial-gradient(circle, color-mix(in oklab, var(--ice) 22%, transparent) 1.6px, transparent 2.2px) 0 0 / 14px 14px,
    radial-gradient(80% 80% at 50% 100%, #2340d8, transparent 70%),
    var(--night);
}
.art-placeholder span { font-size: clamp(3rem, 10vw, 6.5rem); font-weight: 800; font-stretch: 150%; letter-spacing: -0.04em; }
.np-art img:not([hidden]) + .art-placeholder { display: none; }
.np-title { font-size: clamp(2.2rem, 4.6vw, 4rem); font-weight: 760; font-stretch: 115%; line-height: .96; letter-spacing: -0.03em; overflow-wrap: anywhere; }
.np-title[data-long] { font-size: clamp(1.8rem, 3.2vw, 2.8rem); }
.np-artist { font-size: 1.25rem; font-weight: 600; margin: 14px 0 2px; }
.np-album { color: var(--frost); margin: 0; }
.tally { display: inline-block; width: 9px; height: 9px; border-radius: 50%; background: var(--frost); vertical-align: 1px; margin-right: 4px; }
.tally[data-on] { background: var(--tally); box-shadow: 0 0 10px var(--tally); }

/* Song progress: a thin lit line (C), its glowing head the colour of the cover (A). */
.meter { position: relative; height: 4px; margin: 22px 0 10px; border-radius: 4px; background: color-mix(in oklab, var(--ice) 16%, transparent); max-width: 34rem; }
.meter-fill {
  position: absolute; inset: 0 auto 0 0; width: calc(var(--pv, 0) * 1%); border-radius: inherit;
  background: linear-gradient(to right, color-mix(in oklab, var(--art, var(--pixel)) 70%, var(--ice)), var(--ice));
  box-shadow: 0 0 12px color-mix(in oklab, var(--art, var(--pixel)) 80%, transparent);
}
.np-time { color: var(--frost); font-size: .9rem; font-variant-numeric: tabular-nums; margin: 0; }
.np-actions { display: flex; flex-wrap: wrap; align-items: center; gap: 16px 28px; margin-top: 28px; }
.np-actions + .fine { margin-top: 10px; }
.next { margin-top: 24px; padding-top: 16px; border-top: 1px solid var(--rule); }
.next-label { color: var(--frost); font-size: .85rem; margin: 0 0 4px; }
.next-title { font-size: 1.15rem; margin: 0; }

.station-cols { margin-top: var(--sp-block); }
/* The station's history arrives a moment after the page (15 songs). Its room is kept from the
   start, so the page below it does not move when it lands. */
/* a phone reserves five rows, not fifteen (1095 px of night when the station is down) */

.req-dialog {
  width: min(680px, calc(100vw - 32px)); max-height: min(86svh, 900px);
  background: var(--deep); color: var(--ice);
  border: 1px solid color-mix(in oklab, var(--pixel) 60%, transparent); border-radius: 24px;
  padding: 24px clamp(16px, 4vw, 32px);
}
.req-dialog::backdrop { background: color-mix(in oklab, var(--deep) 75%, transparent); backdrop-filter: blur(4px); }
.req-dialog h2 { font-size: clamp(1.5rem, 3.4vw, 2rem); padding-right: 96px; margin: 4px 0 18px; } /* clear of Close */
/* Close stays in view while the pop-out scrolls (Nate, 2026-11-05: "should float so it doesn't
   disappear when I scroll"). Zero height, so it takes no room at the top of the content. */
/* While a pop-out is open, scrolling moves only the pop-out, never the page behind it (Nate,
   2026-11-05). The page keeps its place. */
.req-dialog { overscroll-behavior: contain; }
html:has(dialog[open]) { overflow: hidden; }
.req-close-form { position: sticky; top: 0; z-index: 5; height: 0; margin: 0; display: flex; justify-content: flex-end; align-items: flex-start; }
.req-close-form button { background: var(--deep); box-shadow: 0 4px 16px rgba(0,0,0,.45); }
.req-search { display: grid; gap: 6px; color: var(--frost); font-size: .9rem; }
.req-search input {
  font: inherit; font-size: 1rem; color: var(--ice); background: color-mix(in oklab, var(--night) 80%, var(--ice) 4%); min-height: 48px;
  border: 1px solid color-mix(in oklab, var(--frost) 35%, transparent); border-radius: 12px; padding: 10px 14px;
}
.req-list { list-style: none; padding: 0; margin: 0; }
/* the song rows match the sequence pop-out's (.rs-list): title 600, artist and state 400, pixel rule, a 44 px Request */
.req-list li { display: flex; justify-content: space-between; align-items: center; gap: 12px; padding: 10px 0; border-bottom: 1px solid color-mix(in oklab, var(--pixel) 18%, transparent); }
.req-list li > div { font-weight: 600; }
.req-list .a, .req-list .rs-state { font-weight: 400; }
.req-list .a { display: block; color: var(--frost); font-size: .88rem; }
.req-list button { flex: none; min-height: 44px; padding: 0 18px; border-radius: 999px; font-size: .9rem; font-weight: 600; background: none; color: var(--ice); border: 1.5px solid var(--pixel); cursor: pointer; }
.req-list button:disabled { border: 1px dashed color-mix(in oklab, var(--frost) 55%, transparent); color: var(--frost); cursor: not-allowed; }

/* ---------- The house ---------- */

/* B's light-up. Still page: the lit photo with its lines below it. */
.lightup { position: relative; margin: 0 calc(-1 * var(--gutter)); }
.lu-backdrop { position: relative; height: min(80svh, 75vw); overflow: hidden; container-type: size; background: var(--night); }
.frame {
  --arn: 1.3333; --fx: .5; --fy: .5;
  --W: max(100cqw, 100cqh * var(--arn));
  --H: calc(var(--W) / var(--arn));
  position: absolute; width: var(--W); height: var(--H);
  left: clamp(calc(100cqw - var(--W)), calc(50cqw - var(--fx) * var(--W)), 0px);
  top: clamp(calc(100cqh - var(--H)), calc(50cqh - var(--fy) * var(--H)), 0px);
}
.frame picture, .frame img { width: 100%; height: 100%; display: block; }
.frame img { object-fit: cover; }
.frame--house { --fx: .53; --fy: .42; }
.light { display: none; }
.lu-flow { padding: 32px var(--gutter) 0; }
.beat-h { font-size: clamp(1.8rem, 4vw, 3.2rem); font-weight: 720; font-stretch: 115%; line-height: 1; letter-spacing: -0.025em; margin-bottom: .5em; }
.beats { list-style: none; padding: 0; margin: 0; display: grid; gap: 12px; }
.beats .beat p { font-size: clamp(1.15rem, 2.2vw, 1.6rem); line-height: 1.35; max-width: 30ch; margin: 0; }

/* Loose photos, uncaptioned, each beside the block it belongs with (see "Photos sit beside" below). */
.scat picture img { height: auto; }

.onair-wrap { margin-top: var(--sp-block); }
/* Between shows: the off card in the on-air card's place, and Request A Sequence greyed out. */
.off-air { max-width: 46rem; }
.off-until { margin: 10px 0 0; font-size: clamp(1.1rem, 2vw, 1.4rem); font-weight: 650; color: var(--ice); }
.off-radio { margin: 18px 0 0; max-width: 46ch; color: var(--frost); }
.rs[data-off] .btn-outline { opacity: .45; cursor: not-allowed; }
.rs[data-off] .btn-outline:hover { background: none; }
.rs[data-off] .rs-rules { opacity: .55; }
.rs-off { margin: 10px 0 16px; color: var(--ice); font-weight: 600; }
.on-air { max-width: 46rem; }
.onair-title { font-size: clamp(2.2rem, 4.6vw, 4rem); font-weight: 760; font-stretch: 115%; line-height: 1; letter-spacing: -0.03em; overflow-wrap: anywhere; margin: 0; }
/* Snowing, in the on-air card and the snow clock: icy blue letters with white snow falling through
   them. The flakes are three tiled layers of dots inside the letters (background-clip: text), each
   tile falling its own height every loop so the fall never jumps; with reduced motion they hold. */
[data-snowing] .onair-title { font-size: clamp(2.6rem, 6.4vw, 5.2rem); text-transform: uppercase; }
.snowtext {
  color: transparent; -webkit-text-fill-color: transparent;
  background-image:
    radial-gradient(circle at 30% 20%, #fff 0 .028em, transparent .05em),
    radial-gradient(circle at 70% 60%, rgb(255 255 255 / .9) 0 .02em, transparent .04em),
    radial-gradient(circle at 50% 40%, rgb(255 255 255 / .75) 0 .014em, transparent .03em),
    linear-gradient(180deg, #D9EEFF 0%, #8CC4FF 38%, #4F8BFF 78%, #3A63F0 100%);
  background-size: .42em .5em, .31em .37em, .23em .27em, 100% 100%;
  -webkit-background-clip: text; background-clip: text;
  filter: drop-shadow(0 0 .14em rgb(120 180 255 / .55));
  text-shadow: none;
}
@media (prefers-reduced-motion: no-preference) {
  .snowtext { animation: snow-through 3.2s linear infinite; }
  @keyframes snow-through { to { background-position: .42em 1em, -.31em .74em, 0 .81em, 0 0; } }
}
.snow-clock[data-snowing] .snow-label, .snow-clock[data-nosnow] .snow-label { display: none; }
/* A rain night (FPP's "rainshow"): "No Snow Tonight" in the clock's place, sized like "Now Snowing". */
.snow-clock[data-nosnow] .snow-num { font-size: min(12vw, clamp(3.4rem, 7vw, 7rem)); font-variant-numeric: normal; line-height: .9; white-space: pre-line; }
@media (min-width: 860px) { .snow-clock[data-nosnow] .snow-num { font-size: min(5.8vw, 7rem); } }
.snow-clock[data-snowing] .snow-num { font-size: min(12vw, clamp(3.4rem, 7vw, 7rem)); font-variant-numeric: normal; line-height: .9; white-space: pre-line; }
/* "Now" over "Snowing". From 860 px the clock's column is about 40% of the screen, so the words size to it. */
@media (min-width: 860px) { .snow-clock[data-snowing] .snow-num { font-size: min(5.8vw, 7rem); } }
/* The clock's own warm glow and colour stay off the snow letters (the flakes stay white). */
.snow-num.snowtext { color: transparent; text-shadow: none; }
.sr-only { position: absolute; width: 1px; height: 1px; margin: -1px; padding: 0; overflow: hidden; clip-path: inset(50%); white-space: nowrap; border: 0; }

/* The green dot (show live). The same dot in the dock, the legend and the hero status line. */
.live-dot { display: inline-block; flex: none; width: 9px; height: 9px; border-radius: 50%; background: color-mix(in oklab, var(--frost) 45%, transparent); }
[data-live="on"] .live-dot { background: var(--live); box-shadow: 0 0 0 3px color-mix(in oklab, var(--live) 22%, transparent), 0 0 12px var(--live); }
@media (prefers-reduced-motion: no-preference) {
  [data-live="on"] .live-dot { animation: live-pulse 2.4s ease-in-out infinite; }
}
@keyframes live-pulse { 50% { box-shadow: 0 0 0 5px color-mix(in oklab, var(--live) 10%, transparent), 0 0 6px var(--live); } }

/* Request a light sequence (demo). The list is the show's songs, A to Z, with each one's state. */
.rs { margin-top: var(--sp-block); display: grid; gap: 24px clamp(32px, 6vw, 96px); align-items: start; }
/* The list is in a pop-out (#rs-dialog). Desktop: heading, rules and the button on the left, the
   queue in the right column (Nate, 2026-11-04: "why is it all stacked on the left when you have space
   on the right?"); phones: the queue under the button. */
/* The three columns above it (index.html .show-cols): the on-air card, the request queue, Up Next.
   Desktop side by side; tablet the card on top and the two lists side by side; phone stacked. */
.show-cols { display: grid; gap: 32px clamp(24px, 4vw, 56px); align-items: start; }
.rs-rules { margin: 0 0 16px; padding-left: 1.1em; display: grid; gap: 8px; max-width: 62ch; } /* one column under the heading (Nate, 2026-11-05) */
.rs-rules li::marker { color: var(--live); }
.rs-status:empty { display: none; }
.rs-status {
  margin: 16px 0 0; padding: 14px 16px; border-radius: 18px; max-width: 52ch;
  background: color-mix(in oklab, var(--live) 10%, transparent);
  box-shadow: inset 0 0 0 1px color-mix(in oklab, var(--live) 45%, transparent);
}
/* The request queue, under the rules (Nate, 2026-11-04): position, song, minutes until it plays. */
.rs-queue { margin-top: 16px; }
.rs-queue-empty { margin: 0; color: var(--frost); }
.rs-queue-empty[hidden] { display: none; }
.rs-queue-h { margin: 0 0 8px; font-size: .95rem; font-weight: 700; color: var(--ice); }
.rs-queue ol, .upnext ol { list-style: none; margin: 0; padding: 0; display: grid; gap: 6px; max-width: 52ch; }
.rs-queue li, .upnext li { display: grid; grid-template-columns: 1.6em minmax(0, 1fr) auto; gap: 10px; align-items: baseline; padding: 8px 12px; border-radius: 12px; background: color-mix(in oklab, var(--pixel) 10%, transparent); }
.rs-queue li[data-mine] { box-shadow: inset 0 0 0 1px color-mix(in oklab, var(--live) 70%, transparent); }
.rq-pos { font-weight: 700; color: var(--frost); font-variant-numeric: tabular-nums; }
.rq-name { font-weight: 600; overflow-wrap: anywhere; }
.rq-when { color: var(--frost); font-size: .85rem; white-space: nowrap; }
.rs-queue li[data-mine] .rq-when { color: var(--live); }
.rs-limit { margin: -6px 0 14px; padding: 10px 14px; border-radius: 12px; background: color-mix(in oklab, var(--live) 12%, transparent); color: var(--ice); }
.rs-limit[hidden] { display: none; }
.rs-list { list-style: none; margin: 0; padding: 0; }
.rs-list li {
  display: grid; grid-template-columns: minmax(0, 1fr) auto; gap: 2px 16px; align-items: center;
  padding: 10px 0; border-bottom: 1px solid color-mix(in oklab, var(--pixel) 18%, transparent);
}
.rs-name { font-weight: 600; overflow-wrap: anywhere; }
.rs-state { grid-column: 1; grid-row: 2; color: var(--frost); font-size: .85rem; }
.rs-list li[data-open] .rs-state { color: var(--ice); }
.rs-btn {
  grid-column: 2; grid-row: 1 / span 2; min-height: 44px; padding: 0 18px; border-radius: 999px; cursor: pointer;
  background: none; color: var(--ice); font-weight: 600; font-size: .9rem; border: 1.5px solid var(--pixel);
}
.rs-btn:hover:not(:disabled) { background: color-mix(in oklab, var(--pixel) 25%, transparent); }
.rs-btn:disabled { cursor: not-allowed; border: 1px dashed color-mix(in oklab, var(--frost) 55%, transparent); color: var(--frost); }

/* ---------- Snow ---------- */
.snow { display: grid; grid-template-columns: minmax(0, 1fr); gap: 48px clamp(32px, 6vw, 96px); position: relative; }
@media (min-width: 860px) {
  .snow { grid-template-columns: minmax(0, 1fr) minmax(0, 1.15fr); align-items: start; }
  .snow-text { position: sticky; top: calc(var(--bar-hx, var(--bar-h)) + 14px); } /* 14 px: where a jump puts its title */
}
.snow-label { color: var(--frost); font-size: .85rem; margin: 0 0 6px; }
.snow-num {
  font-size: min(15vw, clamp(4.5rem, 8.6vw, 8.5rem)); font-weight: 800; font-stretch: 140%; line-height: .82; letter-spacing: -0.045em;
  color: var(--bulb); margin: 0 0 .18em -0.04em; font-variant-numeric: tabular-nums;
  text-shadow: 0 0 .4em color-mix(in oklab, var(--bulb) 40%, transparent);
}
.drift { display: grid; grid-template-columns: repeat(2, minmax(0, 1fr)); gap: clamp(40px, 5vw, 72px) clamp(12px, 2vw, 24px); align-items: start; }
.flake--wide { grid-column: 1 / -1; }
.flake picture img { aspect-ratio: 3 / 4; object-fit: cover; }
.flake--wide picture img { aspect-ratio: 16 / 9; }
.flake:nth-child(odd):not(.flake--wide) { margin-top: 48px; }

/* ---------- Visit ---------- */
.facts { margin: 0; display: grid; gap: 0 clamp(32px, 6vw, 96px); }
@media (min-width: 860px) { .facts { grid-template-columns: repeat(2, minmax(0, 1fr)); } }
.facts > div { display: grid; grid-template-columns: 6.5em minmax(0, 1fr); gap: 16px; padding: 14px 0; border-bottom: 1px solid color-mix(in oklab, var(--pixel) 22%, transparent); }
.facts dt { color: var(--frost); }
.facts dd { margin: 0; }

/* Where and how far: the facts and the directions beside the Google map (stacked on a phone). */
.where { display: grid; gap: 32px clamp(32px, 6vw, 96px); align-items: start; }
@media (min-width: 860px) { .where { grid-template-columns: minmax(0, 1fr) minmax(0, 1fr); } .where .facts { grid-template-columns: 1fr; } }
.near { margin-bottom: 28px; }
.near-line {
  font: 700 clamp(1.3rem, 2.4vw, 1.75rem)/1.2 var(--display); font-stretch: 112%; letter-spacing: -0.01em;
  margin: 0 0 18px; max-width: 30ch;
}
.near-line strong { font-weight: inherit; text-decoration: underline; text-decoration-color: var(--pixel); text-decoration-thickness: 3px; text-underline-offset: .18em; }
.near-actions { display: flex; flex-wrap: wrap; align-items: center; gap: 12px 16px; margin-bottom: 12px; }
.btn-go {
  display: inline-flex; align-items: center; min-height: 52px; padding: 0 28px; border-radius: 999px;
  background: var(--ice); color: var(--night); font-weight: 600; font-size: 1.05rem; text-decoration: none;
  box-shadow: 0 0 0 5px color-mix(in oklab, var(--pixel) 35%, transparent), 0 0 30px color-mix(in oklab, var(--pixel) 55%, transparent);
}
.btn-go:hover { background: white; }
.map { position: relative; width: 100%; border-radius: var(--r-photo); overflow: hidden; aspect-ratio: 4 / 3.4; min-height: 300px;
  background: color-mix(in oklab, var(--pixel) 14%, var(--night));
  box-shadow: inset 0 0 0 1px color-mix(in oklab, var(--pixel) 30%, transparent), 0 30px 80px -30px color-mix(in oklab, var(--pixel) 70%, transparent); }
.map iframe { position: absolute; inset: 0; width: 100%; height: 100%; border: 0; display: block; }
/* beside the facts the map fills their height (a 249 px void under it at 1024 otherwise) */
@media (min-width: 860px) { .map { min-height: 100%; } }
/* Dark mode: the keyless Google embed has no dark style of its own, so it is turned dark here:
   inverted, then turned back round the colour wheel so the water stays blue, the parks green and the
   pin red, and softened so the white place card does not become a black hole (Nate, 2026-10-21:
   "if a browser is using dark mode, it shows in dark mode"). Light mode keeps Google's own map. */
@media (prefers-color-scheme: dark) {
  .map iframe { filter: invert(.9) hue-rotate(180deg) saturate(.85) brightness(.95) contrast(.92); }
  .map { background: #1d2230; }
}

/* The show night band. Still page: a row you can swipe sideways (native scroll). */
.band { margin-top: var(--sp-block); }
.band-head { max-width: 46rem; margin-bottom: 24px; }
.band-track {
  --w: min(48vw, 760px); --g: 16px;
  list-style: none; margin: 0 calc(-1 * var(--gutter)); padding: 0 var(--gutter) 12px;
  display: grid; grid-auto-flow: column; grid-auto-columns: var(--w); gap: var(--g);
  overflow-x: auto; scroll-snap-type: x mandatory; scroll-padding-inline: var(--gutter); overscroll-behavior-x: contain;
  scrollbar-color: color-mix(in oklab, var(--pixel) 60%, transparent) transparent;
}
@media (max-width: 859px) { .band-track { --w: 84vw; --g: 12px; } }
.band-track li { scroll-snap-align: start; }
.band-track picture { position: relative; overflow: hidden; border-radius: var(--r-photo); isolation: isolate; }
.band-track img { width: 100%; aspect-ratio: 16 / 10; object-fit: cover; }
.band-track picture::after { content: ""; position: absolute; inset: 0; background: var(--deep); opacity: 0; pointer-events: none; }

.etiquette { margin-top: var(--sp-block); }
.etiquette ul { list-style: none; display: grid; gap: 6px; margin: 0; padding: 0; }
.etiquette li { padding-left: 1.4rem; position: relative; font-weight: 600; }
.etiquette li::before { content: ""; position: absolute; left: .1rem; top: .62em; width: .5rem; height: .5rem; border-radius: 50%; background: var(--ice); box-shadow: 0 0 10px var(--pixel), 0 0 2px var(--ice); }

/* ---------- The food bank and Community Night: one section, the cause first ---------- */
.house > .snow { margin-top: var(--sp-section); }
.cause > .give { padding-bottom: 0 !important; }
.cause > .community { margin-top: var(--sp-section); }

/* ---------- Community Night ---------- */
.community { display: grid; grid-template-columns: minmax(0, 1fr); gap: 40px clamp(32px, 6vw, 96px); align-items: start; } /* photos start level with the heading, never above it (Nate, 2026-11-05) */
@media (min-width: 860px) { .community { grid-template-columns: minmax(0, 1fr) minmax(0, 1.2fr); } }
.community h2 { font-size: clamp(2.6rem, 5.4vw, 5rem); }
.community-date { font-size: clamp(1.3rem, 2.4vw, 1.75rem); font-weight: 700; font-stretch: 112%; letter-spacing: -0.01em; }
.cn-photos { display: grid; grid-template-columns: repeat(2, minmax(0, 1fr)); gap: clamp(12px, 2vw, 24px); align-items: start; }
.cn picture img { aspect-ratio: 4 / 3; object-fit: cover; }
.cn--b, .cn--d { margin-top: clamp(16px, 3vw, 40px); } /* a small stagger on the right, all four inside the section */

/* ---------- Give ---------- */
.give {
  display: grid; grid-template-columns: minmax(0, 1.5fr) minmax(0, 1fr); gap: clamp(32px, 6vw, 96px); align-items: center;
  background: radial-gradient(60% 50% at 25% 45%, color-mix(in oklab, var(--bulb) 12%, transparent), transparent 70%);
}
.give h2 { font-size: clamp(2.1rem, 4.4vw, 4.2rem); max-width: 17ch; margin: 0 0 var(--sp-head); text-wrap: balance; }
/* The food bank's own logo, on the white it is printed on, linking to the food bank. */
.refb-logo { display: inline-block; background: #fff; border-radius: 16px; padding: 14px 22px; margin-bottom: 32px; box-shadow: 0 18px 50px -24px color-mix(in oklab, var(--pixel) 70%, transparent); }
.refb-logo img { display: block; width: min(320px, 62vw); height: auto; }
.refb-logo:focus-visible { outline: 3px solid var(--ice); outline-offset: 4px; }
.give-raised { margin: 0 0 22px; }
.give-num {
  display: block; font-size: clamp(2rem, 3.7vw, 3.5rem); font-weight: 800; font-stretch: 140%; line-height: .9;
  letter-spacing: -0.045em; color: var(--bulb); margin: 0 0 12px -0.04em;
  text-shadow: 0 0 .35em color-mix(in oklab, var(--bulb) 35%, transparent);
}
.give-title { display: block; font: 700 clamp(1.2rem, 2vw, 1.5rem)/1.3 var(--text); max-width: 40ch; }
.give-actions { display: flex; flex-wrap: wrap; gap: 16px 28px; align-items: center; margin: 32px 0 14px; }
.btn-give {
  display: inline-flex; align-items: center; min-height: 60px; background: var(--give); color: var(--give-ink); text-decoration: none;
  font-weight: 600; font-size: 1.15rem; padding: 0 32px; border-radius: 999px;
  box-shadow: 0 0 0 6px color-mix(in oklab, var(--give) 25%, transparent);
}
.btn-give:hover { box-shadow: 0 0 0 6px color-mix(in oklab, var(--give) 35%, transparent), 0 0 40px color-mix(in oklab, var(--give) 45%, transparent); }
.give-photos { display: grid; grid-template-columns: repeat(2, minmax(0, 1fr)); gap: clamp(12px, 2vw, 20px); align-items: start; }
.gp picture img { aspect-ratio: 3 / 4.2; object-fit: cover; }
.gp--b { margin-top: 64px; }

/* ---------- Footer: a bar fixed to the bottom, like the header (Nate 2026-11-04: "It's a footer
   man, just like a header...should stay there"): two social icons on each side of the holiday line,
   centred between them, and nothing else. Icons keep their brand colours, dimmed until pointed at;
   every target is 44 px for a thumb. ---------- */
.foot {
  position: fixed; z-index: 30; left: 0; right: 0; bottom: 0;
  display: flex; justify-content: center; align-items: center; gap: 16px;
  min-height: var(--foot-h); padding: 6px var(--gutter) calc(6px + env(safe-area-inset-bottom, 0px));
  background: color-mix(in oklab, var(--night) 72%, transparent);
  -webkit-backdrop-filter: blur(14px) saturate(1.2); backdrop-filter: blur(14px) saturate(1.2);
  border-top: 1px solid color-mix(in oklab, var(--pixel) 18%, transparent);
}
.foot-wish { margin: 0; flex: 0 1 auto; max-width: 62ch; min-width: 0; text-align: center; color: var(--frost); font-size: .85rem; line-height: 1.3; text-wrap: balance; }
.foot .social { flex: none; }
.social { list-style: none; padding: 0; margin: 0; display: flex; gap: 6px; }
.soc { display: grid; place-items: center; width: 44px; height: 44px; border-radius: 50%;
  background: color-mix(in oklab, var(--ice) 6%, transparent); opacity: .78; transition: opacity .15s, background-color .15s; }
.soc svg { width: 22px; height: 22px; display: block; }
.soc:hover, .soc:focus-visible { opacity: 1; background: color-mix(in oklab, var(--ice) 12%, transparent); }
/* the page's end clears the bar AND the compact dock above it (56 px) with room to spare: the last
   button (Send Message) must be tappable at the very bottom */
body { padding-bottom: calc(var(--dock-bottom) + 56px + 20px); }
@media (max-width: 599px) { .foot { gap: 6px; } .social { gap: 2px; } .foot-wish { font-size: .72rem; line-height: 1.25; } }
@media (max-width: 359px) { .soc { width: 40px; height: 40px; position: relative; } .soc::after { content: ""; position: absolute; inset: -2px; } .soc svg { width: 20px; height: 20px; } .foot-wish { font-size: .66rem; } }

/* ---------- The dock (C): one floating control layer, full over the hero, compact below ---------- */
/* .dock is the fixed anchor; .dock-body is the glass that changes shape. It holds two layers: the
   full player (over the hero) and the compact dock (play, the live dot and song, Give, Top). Only
   one shows. js/dock.js sizes .dock-body to the showing layer and animates between them as you
   scroll past the hero's title. Without script the full layer simply shows, in flow. */
.dock {
  position: fixed; z-index: 40; left: 50%; bottom: var(--dock-bottom);
  translate: -50% 0; max-width: calc(100% - 24px);
}
.dock-body {
  position: relative; overflow: hidden; border-radius: 28px;
  background: color-mix(in oklab, var(--night) 74%, transparent);
  -webkit-backdrop-filter: blur(18px) saturate(1.3); backdrop-filter: blur(18px) saturate(1.3);
  box-shadow: 0 14px 40px -12px rgb(0 0 0 / .6), 0 0 34px -10px color-mix(in oklab, var(--art, var(--pixel)) 70%, transparent), inset 0 0 0 1px color-mix(in oklab, var(--pixel) 35%, transparent);
}
.js .dock-body { width: var(--dw, auto); height: var(--dh, auto); }
.js .dock-body[data-anim] { transition: width .42s cubic-bezier(.2, .8, .2, 1), height .42s cubic-bezier(.2, .8, .2, 1); }
.dock-compact { display: none; }
/* Without script a page with no hero (contact, season: no "is-full" in its HTML) shows the compact
   dock, never the full player whose words talk about the house above. */
html:not(.js) .dock:not(.is-full) .dock-compact { display: flex; }
html:not(.js) .dock:not(.is-full) .dock-full { display: none; }
.js .dock-full, .js .dock-compact {
  position: absolute; left: 50%; bottom: 0; translate: -50% 0;
  transition: opacity .28s ease, visibility 0s;
}
.js .dock-compact { display: flex; }
.js .dock:not(.is-full) .dock-full, .js .dock.is-full .dock-compact { opacity: 0; visibility: hidden; pointer-events: none; transition: opacity .18s ease, visibility 0s .18s; }

/* the full player */
/* No empty row above the words (the station line it held is gone, 2026-11-04: "too much blank space
   above the words...you can probably make it smaller"). The playing bars and "Playing" sit at the end
   of the bottom row, beside the press-play line, so they take no row of their own ("I liked that"). */
.dock-full { width: min(1180px, calc(100vw - 24px)); padding: 7px 16px 7px 12px; display: grid; gap: 4px; }
.df-foot .df-playing { margin-left: auto; font-size: .78rem; line-height: 1.35; white-space: nowrap; } /* the press-play line's height: the floater does not grow while playing */
/* The song keeps a real column (11rem, room for a long word like "(Trans-Siberian") and wraps
   between words; the play button's line under it wraps instead of taking that room. */
.df-main { display: grid; grid-template-columns: auto minmax(11rem, 1fr) auto; gap: 10px 28px; align-items: center; }
.df-main .play-show { --s: 46px; }
.df-main .play-show .play-text { font-size: 1.05rem; }
.df-main .play-sub { font-size: .78rem; max-width: none; }
@media (min-width: 861px) { .df-main .play-sub { max-width: 26ch; } }
.df-foot { display: flex; flex-wrap: wrap; align-items: center; gap: 4px 16px; padding-left: 4px; }
/* "Pause the lights", floating over the bottom of the hero's picture, on the first screen (Nate,
   2026-11-03: "you absolutely cannot see it until you scroll down"). It shares the stage's grid
   cell, so every layout puts it at the picture's bottom right, above the floating player, and it
   never takes room from the house. Shown once the light show has started (js/main.js). */
.lights-ctl { grid-area: stage; align-self: end; justify-self: end; position: relative; z-index: 3; margin: 0 0 6px; }
.lights-ctl .ghost { padding: 6px 16px; font-size: .85rem; background: color-mix(in oklab, var(--night) 70%, transparent); -webkit-backdrop-filter: blur(8px); backdrop-filter: blur(8px); }
.js .lights-ctl [data-motion-toggle][hidden] { display: inline-block !important; visibility: hidden; }
/* With the photo placed (js/field.js), the button belongs to the picture's bottom-right corner (Nate,
   2026-11-04): it floats there above the player, holds its place as you scroll, and once the
   picture's bottom reaches it, it rides up with the picture. A box from the top of the hero to the
   picture's bottom, its right edge on the picture's; the button sticks to the bottom of the screen
   (above the player) inside it, so it can never leave the picture. The box is there from the first
   paint (with scripts on), not only once the photo is placed: then it only changes size, from the
   whole hero to the picture's corner, and does not count as the page jumping (layout shift: 0.45 of
   a phone's 0.45 and half a desktop's 0.82 before, 2026-11-06). The button is hidden until then. */
.js .hero .lights-ctl {
  position: absolute; grid-area: auto; top: 0; left: 0; width: var(--pic-right, 100%); height: var(--pic-bottom, 100%); margin: 0;
  display: flex; flex-direction: column; justify-content: flex-end; align-items: flex-end; pointer-events: none;
}
.hero[data-photo] .lights-ctl .ghost { position: sticky; bottom: calc(var(--dock-full-h) + var(--dock-bottom) + 12px); pointer-events: auto; }
.lights-ctl:not(:has(button)) { display: none; }

/* the compact dock */
.dock-compact { align-items: center; gap: 8px; padding: 6px; width: max-content; max-width: calc(100vw - 24px); }
.dock-radio { display: flex; align-items: center; gap: 10px; min-width: 0; }
.dock-play { --s: 44px; background: none; border: 0; padding: 0; cursor: pointer; flex: none; border-radius: 50%; }
.dock-play .play-icon { box-shadow: none; }
.dock-np { display: block; width: min(330px, calc(100vw - 232px)); min-width: 0; text-decoration: none; line-height: 1.25; font-size: .9rem; border-radius: 14px; padding: 2px 4px; margin: -2px -4px; }
/* Over the station's own player the radio part (play and the song) steps aside; the live dot and
   its words stay, with Give and Top. */
.dock.radio-off .dock-play, .dock.radio-off .dock-marq { display: none; }
.dock.radio-off .dock-np { width: auto; padding: 2px 8px; }
.dock-line { display: flex; align-items: center; gap: 6px; min-width: 0; font-size: .75rem; color: var(--frost); white-space: nowrap; }
/* What the bar plays, over the song: always the streaming radio, never the live show (Nate, 2026-11-03). */
.dock-line .dock-src { flex: none; }
.dock.radio-off .dock-line { font-size: .85rem; }
/* The song: one line, an ellipsis when it does not fit. With motion allowed, a title that does not
   fit scrolls (two copies, translated by one copy's width) for as long as the compact dock shows,
   with a pause between passes, paused under the pointer or keyboard focus, and fading at the edges.
   The motion itself is a Web Animation in js/dock.js. */
.dock-marq { display: block; overflow: hidden; white-space: nowrap; text-overflow: ellipsis; font-weight: 600; }
.dock-track { display: inline; }
.dock-marq[data-scroll] { text-overflow: clip; mask-image: linear-gradient(to right, transparent, #000 10px, #000 calc(100% - 18px), transparent); }
.dock-marq[data-scroll] .dock-track { display: inline-flex; column-gap: 2.5em; }
/* Playing: the play buttons show pause bars, and next to the live dot three small bars move
   (still, with reduced motion) with the word "Playing" on the full player. */
.dock-playing { display: none; align-items: center; gap: 6px; color: var(--ice); font-weight: 600; }
html[data-playing] .dock-playing { display: inline-flex; }
.eq { display: inline-flex; align-items: flex-end; gap: 2px; height: 11px; }
.eq i { display: block; width: 3px; height: 100%; border-radius: 1px; background: var(--ice); transform-origin: bottom; }
.eq i:nth-child(2) { height: 70%; } .eq i:nth-child(3) { height: 45%; }
@media (prefers-reduced-motion: no-preference) {
  html[data-playing] .eq i { animation: eq .9s ease-in-out infinite alternate; }
  html[data-playing] .eq i:nth-child(2) { animation-duration: .7s; animation-delay: -.3s; }
  html[data-playing] .eq i:nth-child(3) { animation-duration: 1.1s; animation-delay: -.6s; }
}
@keyframes eq { from { scale: 1 .35; } to { scale: 1 1; } }
html[data-playing] .dock-play .play-icon { box-shadow: 0 0 0 3px color-mix(in oklab, var(--pixel) 60%, transparent), 0 0 18px color-mix(in oklab, var(--pixel) 70%, transparent); }
.dock-top {
  display: inline-flex; align-items: center; gap: 4px; min-height: 44px; padding: 0 14px 0 10px; border-radius: 999px; flex: none;
  color: var(--ice); text-decoration: none; font-weight: 600; font-size: .9rem;
  box-shadow: inset 0 0 0 1px color-mix(in oklab, var(--ice) 30%, transparent);
}
.dock-top svg { width: 20px; height: 20px; fill: currentColor; }
.dock-top:hover { background: color-mix(in oklab, var(--pixel) 25%, transparent); }

/* The full player on a narrower screen: play and Give on one row, what's on, the readouts in a row. */
@media (max-width: 860px) {
  .df-main { grid-template-columns: minmax(0, 1fr); grid-template-areas: "play" "np" "ro"; gap: 8px 12px; }
  .df-main .play-show { grid-area: play; --s: 44px; }
  .df-main .np-mini { grid-area: np; }
  .df-main .readouts { grid-area: ro; justify-content: flex-start; flex-wrap: nowrap; gap: 6px 18px; }
  .dock-full { padding: 10px 14px 10px 12px; gap: 6px; }
}
/* On a phone, Top is the arrow alone (its name stays for screen readers), so the song has room. */
@media (max-width: 479px) {
  .dock-top { padding: 0; width: 44px; justify-content: center; }
  .dock-line { gap: 4px; }
  .dock-top span { position: absolute; width: 1px; height: 1px; overflow: hidden; clip-path: inset(50%); white-space: nowrap; }
  .play-show .play-text { font-size: 1.02rem; }
  .play-sub { font-size: .78rem; }
  .ro { min-width: 0; }
  .ro dt { font-size: .7rem; line-height: 1.25; }
  .ro-val { font-size: 1.45rem; }
  .np-mini-title, .np-mini-title[data-long] { font-size: 1.05rem; -webkit-line-clamp: 1; line-clamp: 1; }
  .provenance { font-size: .72rem; line-height: 1.32; flex-basis: 0; }
  .df-foot { flex-wrap: nowrap; align-items: flex-end; gap: 8px; }
  .df-main .play-show { --s: 40px; gap: 10px; }
  .df-main .play-show .play-text { font-size: 1rem; }
  .df-main .play-sub { font-size: .72rem; }
  .np-mini { display: grid; grid-template-columns: auto minmax(0, 1fr); align-items: baseline; column-gap: 8px; }
  .np-mini .src { grid-column: 1 / -1; margin: 0; font-size: .7rem; }
  .np-mini-title, .np-mini-title[data-long] { max-width: 100%; }
  .dock-full .np-mini { min-height: calc(.7rem * 1.6 + 1.05rem * 1.6); align-content: start; }
  .np-mini-artist, .dock-full .np-mini-artist { margin-top: 0 !important; font-size: .8rem; min-height: 0; }
  .ro dt { font-size: .66rem; }
  .ro-val { font-size: 1.35rem; }
}

.snowfall { display: none; }

/* ---------- Narrow screens ---------- */
@media (max-width: 860px) {
  .hero { grid-template-columns: 1fr; grid-template-rows: auto auto minmax(120px, 1fr); grid-template-areas: "top" "where" "stage"; row-gap: 10px; }
  /* A little more air between the house and the dock on a phone (at least 08ab93d's 17 to 22 px
     between the lowest light and the dock's top edge, at 375 to 390 px wide). */
  .hero { padding-bottom: calc(var(--dock-full-h) + var(--dock-bottom) + clamp(16px, 2.8svh, 24px)); }
  /* On a phone: the address and the times on a line each, under the words; Visit The Show sits
     on the title's second line, in the room "Lights" leaves beside it, so the block costs the
     house no more height than 94.3 did. */
  .hero-where { justify-self: start; justify-items: start; text-align: left; padding-top: 0; gap: 8px; }
  .hw-place { gap: 2px; justify-items: start; }
  .hw-addr { font-size: clamp(.95rem, 4.3vw, 1.2rem); font-stretch: 100%; }
  .hw-addr > span { display: inline; }
  .hw-times { font-size: clamp(.8rem, 3.5vw, .95rem); max-width: none; white-space: nowrap; }
  .np, .station-cols, .give { grid-template-columns: 1fr; }
  .np-art { max-width: 420px; }
}

/* =====================================================================
   THE SCROLL STORY (B). Only with html.anim: motion allowed, and either native scroll-driven
   animations or js/scroll.js's fallback writing the same --p / --q from the same --tl / --r0 / --r1.
   ===================================================================== */
@keyframes p { from { --p: 0; } to { --p: 1; } }
@keyframes q { from { --q: 0; } to { --q: 1; } }

@supports ((animation-timeline: view()) and (animation-range: entry)) {
  body { timeline-scope: --cap1, --cap2, --cap3, --cap4; }
  #cap2 { view-timeline: --cap2 block; }
  #cap4 { view-timeline: --cap4 block; }
  #cap1 { view-timeline: --cap1 block; }
  #cap3 { view-timeline: --cap3 block; }
  .anim .sd {
    animation: p linear both;
    animation-timeline: var(--tl, view());
    animation-range: var(--r0, cover 0%) var(--r1, cover 40%);
  }
  .anim .sq {
    animation: q linear both;
    animation-timeline: var(--tl, view());
    animation-range: var(--r0, cover 0%) var(--r1, cover 100%);
  }
}
.band { --r0: contain 0%; --r1: contain 100%; }

/* Headings brighten as they arrive, like the lights coming on. */
.anim .sec-head h2 {
  color: color-mix(in oklab, #2A3478, var(--ice) calc(var(--p) * 100%));
  text-shadow: 0 0 calc(var(--p) * 34px) color-mix(in oklab, var(--pixel) calc(var(--p) * 85%), transparent);
}

/* Every photo is lit out of the dark as it comes up the screen, and settles as it does. */
.anim .lit picture::after { opacity: calc(.85 * (1 - var(--p))); }
.anim .lit picture img { scale: calc(1.12 - .12 * var(--p)); }

/* Parallax: groups drift at their own --depth through their pass (--q from .5 = rest). */
.anim .sq .lit { translate: 0 calc((.5 - var(--q)) * var(--depth, 0px)); }

/* The house wakes up (B): pinned photo, captions scroll past, each lights one part. */
.anim .lightup { margin-top: 0; }
.anim .lu-backdrop { position: sticky; top: var(--bar-hx, var(--bar-h)); height: calc(100svh - var(--bar-hx, var(--bar-h))); }
.anim .lu-backdrop::after {
  content: ""; position: absolute; inset: 0; pointer-events: none; z-index: 2;
  background: linear-gradient(to right, color-mix(in oklab, var(--night) 70%, transparent) 0%, color-mix(in oklab, var(--night) 20%, transparent) 50%, transparent 75%),
              linear-gradient(to top, color-mix(in oklab, var(--night) 65%, transparent), transparent 40%);
}
@media (max-width: 700px) {
  .anim .lu-backdrop::after { background: linear-gradient(to top, color-mix(in oklab, var(--night) 85%, transparent) 15%, color-mix(in oklab, var(--night) 30%, transparent) 60%, transparent); }
}
.anim .lu-flow { position: relative; z-index: 3; margin-top: calc(-100svh + var(--bar-hx, var(--bar-h))); padding-top: 0; }
.anim .beat { min-height: 80svh; display: grid; align-content: center; padding: 6svh 0; }
.anim .beat--first { min-height: 60svh; align-content: start; padding-top: 10svh; }
.anim .beats { gap: 0; }
.anim .beats .beat:last-child { min-height: 100svh; }
.anim .lu-flow .beat { text-shadow: 0 1px 2px rgb(0 0 0 / .6), 0 0 26px rgb(3 5 28 / .8); }
@media (max-width: 700px) { .anim .beats .beat { align-content: end; padding-bottom: 14svh; } }
.anim .frame--house img { filter: brightness(.14) saturate(.45); }
/* The same photo lit up: its real blue haze kept to a glow, not a fog (Nate, 2026-11-03). The
   midtones go down harder than the bulbs. On the whole frame, so it is one filtered layer. */
.anim .frame--house { filter: brightness(.6) contrast(1.9) saturate(.9); }
.anim .light { display: block; position: absolute; inset: 0; pointer-events: none; background: var(--lit) center / 100% 100% no-repeat; opacity: var(--p); }
.anim .light--1 {
  -webkit-mask-image: radial-gradient(7% 10% at 50.7% 23.5%, #000 40%, transparent), radial-gradient(3% 18% at 43% 32%, #000 35%, transparent);
          mask-image: radial-gradient(7% 10% at 50.7% 23.5%, #000 40%, transparent), radial-gradient(3% 18% at 43% 32%, #000 35%, transparent);
}
.anim .light--2 {
  -webkit-mask-image: radial-gradient(8% 23% at 50.6% 37%, #000 60%, transparent);
          mask-image: radial-gradient(8% 23% at 50.6% 37%, #000 60%, transparent);
}
.anim .light--3 {
  -webkit-mask-image: radial-gradient(11% 11% at 61.5% 38%, #000 55%, transparent), radial-gradient(8% 7% at 73.5% 46%, #000 55%, transparent), radial-gradient(13% 11% at 91% 42%, #000 50%, transparent), radial-gradient(14% 10% at 29% 49%, #000 45%, transparent);
          mask-image: radial-gradient(11% 11% at 61.5% 38%, #000 55%, transparent), radial-gradient(8% 7% at 73.5% 46%, #000 55%, transparent), radial-gradient(13% 11% at 91% 42%, #000 50%, transparent), radial-gradient(14% 10% at 29% 49%, #000 45%, transparent);
}

/* The show night band advances sideways as you scroll down (sticky + translate). */
.anim .band { height: calc(var(--n) * 62svh); }
.anim .band-pin { position: sticky; top: var(--bar-hx, var(--bar-h)); height: calc(100svh - var(--bar-hx, var(--bar-h))); display: grid; grid-template-columns: minmax(0, 1fr); align-content: center; overflow: hidden; margin: 0 calc(-1 * var(--gutter)); padding: 0 var(--gutter); }
.anim .band-track {
  overflow: visible; scroll-snap-type: none; margin: 0; padding: 0;
  translate: calc(var(--q) * -1 * (var(--n) * (var(--w) + var(--g)) - var(--g) - (100vw - 2 * var(--gutter)))) 0;
}
/* Each frame is brightest when it reaches the middle, dimmer the further it is (B's highlight). */
.anim .band-track li {
  --x: calc(var(--q) * (var(--n) - 1) - var(--i));
  --d: max(var(--x), -1 * var(--x));
}
.anim .band-track picture::after { opacity: min(.72, calc(var(--d) * .5)); }
.anim .band-track img { scale: calc(1 + min(.08, var(--d) * .05)); }
@media (max-width: 859px) { .anim .band { height: calc(var(--n) * 48svh); } }

.anim .snowfall { display: block; position: fixed; inset: 0; width: 100%; height: 100%; z-index: 20; pointer-events: none; }

/* No reduced-motion visitor ever gets .anim; belt and braces. */
@media (prefers-reduced-motion: reduce) {
  .anim .sd, .anim .sq { animation: none !important; }
  .dock, .dock-body, .dock-full, .dock-compact, .field canvas, .field-photo img { transition: none !important; }
  .dock-track { animation: none !important; }
}


/* A phone on its side (Nate's iPhone 17 in landscape, Safari's bars showing: about 310 to 400 px of
   page, 2026-11-02). The two-row header, the title, the address block and a second Visit The Show
   took the screen and left the house a thumbnail. Here the header is one row, the title (already in
   the header) and the hero's Visit The Show (the header's Visit link) step aside, the address and the
   nightly times sit small at the top right, and the house takes the rest. */
@media (max-height: 500px) and (orientation: landscape) and (pointer: coarse) {
  :root { --bar-h: 52px; }
  .bar { flex-wrap: nowrap; padding-block: 6px; gap: 10px; transition: translate .35s ease; }
  /* At the very top the header steps aside so the house gets the whole screen; it slides back on
     the first scroll (Nate, 2026-11-02, landscape option 2). The fit ignores it here (js/field.js). */
  .bar[data-at-top] { translate: 0 -100%; }
  .bar[data-at-top]:focus-within { translate: none; } /* a keyboard user never tabs into a hidden header */
  .bar .give-pill { order: 5; margin-left: 0; min-height: 34px; padding: 0 14px; }
  .jump { order: 4; margin: 0; -webkit-mask-image: linear-gradient(to right, transparent, #000 8px, #000 calc(100% - 40px), transparent); mask-image: linear-gradient(to right, transparent, #000 8px, #000 calc(100% - 40px), transparent); }
  .jump ul { padding: 0 8px; }
  /* The house never gets smaller than when the phone is upright (Nate, 2026-11-02: "why does it get
     smaller when I go to landscape mode? Shouldn't get smaller"): the address floats in the corner
     instead of taking a row, and the yard's feet may tuck under the bar as on desktop. */
  .hero { grid-template-columns: minmax(0, 1fr); grid-template-rows: 0 minmax(0, 1fr); grid-template-areas: "top" "stage"; row-gap: 0; padding-top: 6px; padding-bottom: calc(var(--dock-full-h) + var(--dock-bottom) - 14px); }
  .hero-top { display: none; }
  .hero-where { position: absolute; grid-area: auto; z-index: 1; top: 6px; right: var(--gutter); justify-items: end; text-align: right; gap: 0; padding-top: 0; white-space: nowrap; }
  .hw-place { gap: 1px; justify-items: end; }
  .hw-addr { font-size: 1rem; }
  .hw-addr > span { display: inline; }
  .hw-times { font-size: .8rem; max-width: none; }
  .lights-ctl { margin-bottom: 22px; } /* the feet tuck 14 px under the player here; the button must not */
  /* The Show's light-up photo on a short screen: sized so the band from the star (photo y 16%) to
     the yard's feet (66%) fits the height, centred, its sides fading into the night. Filling the
     width cropped the star, and framing lower put the yard off the screen (Nate, 2026-11-02: the
     whole house, "your pick"). */
  .anim .frame--house {
    --room: calc(100cqh - var(--dock-full-h) - var(--dock-bottom) + 14px); /* above the player; the feet may tuck 14px under it, as in the hero */
    --W: min(max(100cqw, 100cqh * var(--arn)), var(--room) * 2.4);
    left: calc(50cqw - var(--fx) * var(--W)); top: calc(var(--room) / 2 - .41 * var(--H));
    -webkit-mask-image: linear-gradient(to right, transparent, #000 12%, #000 88%, transparent);
            mask-image: linear-gradient(to right, transparent, #000 12%, #000 88%, transparent);
  }
}

/* The on-foot line under the two signs (.fm). */
/* Under the Press for Music sign it points at: small and quiet (Nate 11-04: "smaller and more subtle under the picture"). */
.onfoot-copy { margin: 10px 0 0; color: var(--frost); font-size: .875rem; line-height: 1.45; max-width: 30ch; }
/* on a phone the line runs under both signs (two lines, not five); the figure keeps its box, so the
   Press for Music sign still lights up as it rises */
@media (max-width: 479px) { .onfoot-copy { max-width: none; width: calc(200% + var(--fm-gap)); margin-left: calc(-100% - var(--fm-gap)); } }

/* "Tuned In From" (#27), under the station's player. */
.tuned { margin-top: clamp(40px, 6vw, 72px); }
.tuned .h3 { margin-bottom: 0; }

/* Request a song: each song's state and a live Request button (js/songreq.js). */
.req-list .rs-state { display: block; color: var(--frost); font-size: .85rem; }
.req-list li[data-open] .rs-state { color: var(--ice); }
.req-list button:not(:disabled):hover { background: color-mix(in oklab, var(--pixel) 25%, transparent); }

/* Up Next (index.html .upnext): requests marked, snow quieter. */
.upnext li[data-req] .rq-when { color: var(--live); }
.upnext li[data-snow] .rq-name { color: var(--ice); font-weight: 600; }

/* ---------- Contact Us: the last section of the home page (info left, form right) ---------- */
.contact { margin-inline: var(--gutter); padding-inline: 0 !important; }
/* the last section: tall enough that a jump to it lands its title under the header like every other */
.contact { min-height: calc(100svh - var(--bar-hx, var(--bar-h)) - 14px - var(--foot-h)); }
.ct { padding: var(--sp-top) var(--gutter) clamp(64px, 9vw, 128px); }
.ct-title {
  font-family: var(--display); font-size: clamp(3rem, 10vw, 7.5rem); font-weight: 760; font-stretch: 125%;
  letter-spacing: -0.035em; line-height: .95; color: var(--ice);
  text-shadow: 0 0 28px color-mix(in oklab, var(--pixel) 70%, transparent);
  margin: 0 0 var(--sp-head);
}
.ct-cols { display: grid; grid-template-columns: minmax(0, 1fr); gap: 48px clamp(40px, 6vw, 104px); align-items: start; max-width: 1328px; }
@media (min-width: 860px) { .ct-cols { grid-template-columns: minmax(0, .8fr) minmax(0, 1.2fr); } }

.ct-lede { font-size: clamp(1.1rem, 1.7vw, 1.3rem); line-height: 1.5; color: var(--ice); max-width: 32ch; margin: 0 0 32px; }
/* ruled rows, like When / Hours / Where in Visit (.facts); `auto` so the email never breaks mid-word */
.ct-facts { margin: 0; display: grid; gap: 0; }
.ct-facts > div { display: grid; grid-template-columns: auto minmax(0, 1fr); gap: 12px; padding: 14px 0; border-bottom: 1px solid color-mix(in oklab, var(--pixel) 22%, transparent); }
.ct-facts dt { color: var(--frost); }
.ct-facts dd { margin: 0; line-height: 1.5; overflow-wrap: anywhere; }

.ct-form {
  background: color-mix(in oklab, var(--deep) 70%, transparent);
  border: 1px solid color-mix(in oklab, var(--pixel) 28%, transparent); border-radius: 20px;
  padding: clamp(20px, 3.5vw, 40px);
}
.ct-form-h { margin: 0 0 .55em; }
.ct-row { display: grid; grid-template-columns: minmax(0, 1fr); gap: 0 20px; }
@media (min-width: 560px) { .ct-row { grid-template-columns: minmax(0, 1fr) minmax(0, 1fr); } }
.ct-field { display: grid; gap: 6px; margin-bottom: 20px; }
.ct-field label { font-weight: 600; color: var(--ice); }
.ct-field input, .ct-field textarea {
  font: inherit; font-size: 1rem; color: var(--ice); width: 100%; min-height: 48px; padding: 12px 14px;
  background: color-mix(in oklab, var(--night) 80%, var(--ice) 4%);
  border: 1px solid color-mix(in oklab, var(--frost) 35%, transparent); border-radius: 12px;
}
.ct-field textarea { resize: vertical; min-height: 9rem; line-height: 1.5; }
.ct-field input:focus, .ct-field textarea:focus, .req-search input:focus { border-color: var(--pixel); outline: none; box-shadow: 0 0 0 3px color-mix(in oklab, var(--pixel) 45%, transparent); }
.ct-hp { position: absolute; left: -10000px; width: 1px; height: 1px; overflow: hidden; }
.ct-actions { display: flex; flex-wrap: wrap; align-items: center; gap: 12px 20px; }
.ct-send[disabled] { opacity: .6; cursor: progress; }
.ct-status { margin: 0; color: var(--frost); }
.ct-status[data-state="sent"] { color: var(--ice); }
.ct-status[data-state="error"] { color: var(--ice); }
.ct-status a { color: var(--ice); }

.ct-faq { margin-top: clamp(40px, 5vw, 64px); padding-top: clamp(28px, 4vw, 40px); color: var(--frost); line-height: 1.55; max-width: 46ch; } /* the facts' last rule already divides */
.ct-faq-h { color: var(--ice); margin: 0 0 .55em; }
.ct-faq-list { list-style: none; padding: 0; margin: 20px 0; display: grid; gap: 18px; }
.ct-faq-list > li > .link { font-weight: 600; font-size: 1.05rem; }
.ct-faq-list > li > span { display: block; margin-top: 4px; }
.ct-faq .link { color: var(--ice); }

/* ---------- Photos sit beside the block they belong to, never in a row of their own ----------
   Nate, 2026-11-05: "If you're going to scatter photos throughout the pages, it shouldn't just be so
   random that there is big gaps and extra long scrolling". The blue tree and Santa sit beside Please be
   safe, the white-and-gold tree beside Listen To Our Lights, the red-white-green house under the song
   request queue. Each keeps its own --depth drift (.sq on the row). */
.etq-row, .fm-row { margin-top: var(--sp-block); display: grid; gap: 32px clamp(32px, 5vw, 96px); align-items: center; }
.etq-row > .etiquette, .fm-row > .fm { margin-top: 0; }
.row-pics { display: grid; grid-template-columns: repeat(2, minmax(0, 1fr)); gap: clamp(14px, 2vw, 28px); align-items: start; }
.row-pics .scat, .req-pic .scat { margin: 0; }
.row-pics .scat:nth-child(2) { margin-top: clamp(28px, 5vw, 72px); } /* staggered, still scattered */
.row-pics--one { grid-template-columns: minmax(0, 1fr); }
@media (min-width: 860px) {
}
/* the white-and-gold tree: under 641 px it follows the signs; 641-1319 px it stands beside the two
   signs; from 1320 px (where "94.3 FM" at full size leaves room) beside the whole block */
@media (max-width: 640px) { .row-pics--one { width: 60%; justify-self: end; } }
@media (min-width: 641px) and (max-width: 1319px) {
  .fm-row { grid-template-columns: minmax(0, 2fr) minmax(0, 1fr); align-items: start; row-gap: 18px; }
  .fm-row > .fm { display: contents; }
  .fm-row .fm .h3, .fm-row .freq { grid-column: 1 / -1; }
  .fm-row .fm-pics { grid-column: 1; }
}
@media (min-width: 1320px) {
  .fm-row { grid-template-columns: auto minmax(0, clamp(300px, 34vw, 520px)); justify-content: space-between; }
}
.req-pic { margin-top: clamp(28px, 4vw, 48px); }
.req-pic .scat { max-width: 380px; }
@media (max-width: 640px) { .req-pic .scat { width: 60%; margin-left: auto; } }

/* Request A Sequence: the heading with its button beside it, the rules in one column under the
   heading (Nate, 2026-11-05), so the right side is left open. */
.rs-title-row { display: flex; flex-wrap: wrap; align-items: center; gap: 12px 28px; margin-bottom: 16px; }
.rs-title-row .h3 { margin: 0; }

/* Contact Us: Already Answered? to the right of the form (Nate, 2026-11-05: "to balance out the page"). */
@media (min-width: 860px) and (max-width: 1199px) {
  .ct-cols > .ct-info { grid-column: 1; grid-row: 1; }
  .ct-cols > .ct-form { grid-column: 2; grid-row: 1 / span 2; }
  .ct-cols > .ct-faq { grid-column: 1; grid-row: 2; margin-top: 0; }
}
@media (min-width: 1200px) {
  .ct-cols { grid-template-columns: minmax(0, .8fr) minmax(0, 1.25fr) minmax(0, .95fr); max-width: none; gap: 48px clamp(32px, 3.5vw, 64px); }
  .ct-cols > .ct-faq { margin-top: 0; padding-top: 0; }
}

/* Come See It: the map is exactly as tall as the details beside it, never taller (Nate, 2026-11-05). */
@media (min-width: 860px) {
  .where { align-items: stretch; }
  .where .map { aspect-ratio: auto; min-height: 300px; height: 100%; }
}

/* The show block as two stacks (Nate, 2026-11-05): the on-air card with Request A Sequence under it on
   the left, Up Next with the request queue under it on the right. The queue grows down its own column
   and never moves the request block. One column under 1100 px. */
.sc-left, .sc-right { display: grid; gap: 32px; align-content: start; min-width: 0; }
.sc-right .rs-queue { margin-top: 0; }
.sc-left > .rs { margin-top: 0; }
@media (min-width: 1100px) { .show-cols { grid-template-columns: minmax(0, 1fr) minmax(320px, 440px); } }
.rs-queue-note { margin: 0 0 10px; font-size: .82rem; line-height: 1.45; color: var(--frost); max-width: 52ch; }
.rs-more { margin-top: 8px; background: none; border: 0; padding: 4px 0; font: inherit; font-size: .9rem; font-weight: 600; color: var(--ice); text-decoration: underline; text-underline-offset: 3px; cursor: pointer; }
.rs-more[hidden] { display: none; }

/* One button where a heading and a button used to say the same thing (Nate, 2026-11-05). */
.req-open { justify-self: start; }
.sr-rules { margin: 0; max-width: 62ch; }
/* The house photo joined the two beside Please be safe: three, the middle one dropped a little. */
.row-pics--three { grid-template-columns: repeat(3, minmax(0, 1fr)); }
.row-pics--three .scat:nth-child(2) { margin-top: clamp(28px, 5vw, 72px); }
.row-pics--three .scat:nth-child(3) { margin-top: 0; }
@media (min-width: 860px) { .etq-row { grid-template-columns: minmax(0, 1fr) minmax(0, min(58vw, 760px)); } }

/* Listener Statistics: a year menu, then the numbers in a pop-out (Nate, 2026-11-05). */
.stats-pick { margin: 18px 0 0; position: relative; width: max-content; }
.stats-years[hidden] { display: none; }
.stats-btn { background: none; border: 0; padding: 0; font: inherit; color: var(--ice); cursor: pointer; text-decoration: underline; text-underline-offset: 4px; }
.stats-years { position: absolute; left: 0; top: 100%; z-index: 20; margin: 6px 0 0; padding: 6px; list-style: none; border-radius: 14px;
  background: var(--deep); box-shadow: inset 0 0 0 1px color-mix(in oklab, var(--pixel) 40%, transparent), 0 12px 40px rgba(0,0,0,.5); }
.stats-years button { display: block; width: 100%; text-align: left; background: none; border: 0; color: var(--ice); font: inherit; padding: 10px 16px; border-radius: 10px; cursor: pointer; white-space: nowrap; }
.stats-years button:hover, .stats-years button:focus-visible { background: color-mix(in oklab, var(--pixel) 25%, transparent); outline: none; }
.stats-dialog { max-width: min(920px, calc(100vw - 32px)); }
.st-dates { color: var(--frost); margin: -8px 0 18px; }
.st-msg { color: var(--frost); }
.st-nums { margin: 0 0 8px; display: grid; grid-template-columns: repeat(auto-fit, minmax(150px, 1fr)); gap: 16px 24px; }
.st-nums div { display: flex; flex-direction: column-reverse; gap: 2px; }
.st-nums dt { color: var(--frost); font-size: .85rem; font-weight: 600; }
.st-nums dd { margin: 0; font-family: var(--display); font-weight: 780; font-stretch: 125%; font-size: clamp(1.4rem, 2.6vw, 2rem); line-height: 1; font-variant-numeric: tabular-nums; white-space: nowrap; }
.st-nums div:first-child dd { color: var(--bulb); }
.st-h { font-size: 1rem; font-weight: 700; color: var(--ice); margin: 22px 0 10px; }
.st-icicles { --h: 110px; list-style: none; margin: 4px 0 0; padding: 0; display: grid; grid-auto-flow: column; grid-auto-columns: minmax(0, 1fr); gap: 3px; height: var(--h); align-items: start; border-top: 2px solid color-mix(in oklab, var(--frost) 55%, transparent); }
.st-icicles li { height: calc(var(--v) * 100%); min-height: 4px; border-radius: 0 0 999px 999px; background: linear-gradient(to bottom, color-mix(in oklab, var(--pixel) 70%, var(--ice)), var(--ice)); }
.st-icicles li[data-busiest] { background: linear-gradient(to bottom, color-mix(in oklab, var(--bulb) 75%, var(--ice)), #fff); }
.stats-dialog .icicle-ends { display: flex; justify-content: space-between; color: var(--frost); font-size: .8rem; margin-top: 6px; }
.st-busiest { margin: 10px 0 0; font-size: .95rem; }
.st-busiest strong { color: var(--bulb); font-weight: 600; }
.st-chart { position: relative; }
.st-icicles li { cursor: pointer; outline: none; }
.st-icicles li[data-on], .st-icicles li:focus-visible { background: linear-gradient(to bottom, var(--ice), #fff); box-shadow: 0 0 12px color-mix(in oklab, var(--ice) 70%, transparent); }
.st-tip { position: absolute; transform: translateX(-50%); margin: 0; padding: 6px 10px; border-radius: 8px; white-space: nowrap; pointer-events: none; z-index: 2;
  font-size: .85rem; font-weight: 600; color: var(--night); background: var(--ice); box-shadow: 0 6px 20px rgba(0,0,0,.45); }
.st-tip[hidden] { display: none; }
.st-from { display: grid; gap: 20px 40px; }
@media (min-width: 640px) { .st-from { grid-template-columns: repeat(2, minmax(0, 1fr)); } }
.st-sub { margin: 0 0 6px; font-size: .85rem; font-weight: 600; color: var(--frost); }
.st-rank { list-style: none; margin: 0; padding: 0; display: grid; gap: 4px; }
.st-rank li { position: relative; display: flex; justify-content: space-between; gap: 12px; padding: 5px 10px; border-radius: 8px; font-size: .95rem; isolation: isolate; }
.st-rank li::before { content: ""; position: absolute; inset: 0; width: calc(var(--w) * 100%); border-radius: 8px; z-index: -1; background: color-mix(in oklab, var(--pixel) 22%, transparent); }
.st-rank .n { color: var(--frost); font-variant-numeric: tabular-nums; }
.st-req { margin: 0; padding-left: 1.4em; }
.st-req li { padding: 6px 0; border-bottom: 1px solid color-mix(in oklab, var(--pixel) 20%, transparent); }
.st-req .t { font-weight: 600; } .st-req .a { color: var(--frost); margin-left: .5em; } .st-req .n { float: right; color: var(--frost); }

/* The station's Up Next carries each song's album art, as Just Played did (Nate, 2026-11-05). */
.station-cols .upnext li { grid-template-columns: 1.6em 40px minmax(0, 1fr) auto; align-items: center; }
.rq-art { width: 40px; height: 40px; border-radius: 8px; object-fit: cover; background: color-mix(in oklab, var(--pixel) 30%, var(--night)); }

/* Visit: the distance and Take Me There sit right above the map (Nate, 2026-11-05). */
.where-map { display: flex; flex-direction: column; gap: 18px; min-width: 0; }
.where-map .near { margin: 0; }
.where-map .near-line { margin-bottom: 14px; }
.where-map .near-actions { margin-bottom: 0; }
.btn-go:disabled { opacity: .7; cursor: progress; }
.btn-go { border: 0; font: inherit; font-weight: 600; font-size: 1.05rem; cursor: pointer; }
.near-qr { display: flex; align-items: center; gap: 16px; margin: 14px 0 0; }
.near-qr[hidden] { display: none; }
.near-qr img { width: 112px; height: 112px; border-radius: 12px; background: #fff; padding: 6px; }
.near-qr figcaption { color: var(--frost); font-size: .95rem; max-width: 22ch; }
@media (min-width: 860px) { .where .where-map .map { height: auto; flex: 1 1 auto; min-height: 300px; } }
/* The QR sits beside the distance and the buttons, not on a row of its own under them. */
@media (min-width: 860px) {
  .where-map .near { display: grid; grid-template-columns: minmax(0, 1fr) auto; column-gap: 20px; align-items: start; }
  .where-map .near > :not(.near-qr) { grid-column: 1; }
  .where-map .near-qr { grid-column: 2; grid-row: 1 / span 2; flex-direction: column; align-items: center; gap: 6px; margin: 0; }
  .where-map .near-qr figcaption { font-size: .8rem; max-width: 14ch; text-align: center; }
}
/* The two columns end on the same line (Nate, 2026-11-05: "The bottoms should line up"). The facts set
   the height when they are taller (the map stretches to fill); when the map side is taller, the fact
   rows spread to meet it. Either way the bottoms meet. */
@media (min-width: 860px) {
  .where { align-items: stretch; }
  .where .where-map .map { min-height: 260px; flex: 1 1 0; }
  /* when the map side is the taller one, the fact rows spread to meet it: the last rule sits on the
     map's bottom edge */
  .where .where-info { display: flex; flex-direction: column; }
  .where .where-info .facts { flex: 1 1 auto; align-content: space-between; }
}

/* The interactive map, once Take Me There has loaded it: it covers the keyless embed in the same frame. */
.map .gmap { position: absolute; inset: 0; z-index: 1; }
.map[data-live-map] iframe { visibility: hidden; }

/* Arrive for the snow, under the distance (2026-11-05). */
.near-snow { margin: -6px 0 14px; color: var(--ice); font-weight: 600; }
.near-snow[hidden] { display: none; }

/* The show's own pin on the interactive map: a star that glows while the show is on and sparkles
   with falling snow while it snows; a tap shows what is playing. */
.show-pin { position: absolute; z-index: 10; transform: translate(-50%, -50%); border: 0; padding: 0; background: none; width: 44px; height: 44px; pointer-events: none; }
.show-pin .sp-dot { position: absolute; inset: 0; display: grid; place-items: center; border-radius: 50%; font-size: 22px; line-height: 1;
  color: #8b93a8; background: #141a33; box-shadow: 0 0 0 3px #2a3360, 0 4px 14px rgba(0,0,0,.5); }
.show-pin[data-on] .sp-dot { color: #ffd34d; box-shadow: 0 0 0 3px #ffd34d, 0 0 18px 6px rgba(255, 200, 60, .55); animation: sp-glow 1.6s ease-in-out infinite; }
@keyframes sp-glow { 50% { box-shadow: 0 0 0 3px #ffe9a3, 0 0 30px 12px rgba(255, 200, 60, .75); } }
.show-pin .sp-snow { position: absolute; left: -50px; right: -50px; top: -50px; height: 130px; pointer-events: none; opacity: 0; }
.show-pin[data-snow] .sp-snow { opacity: 1; }
/* Real snowflakes, big and bright, with a dark edge so they read on the dark map and the light one,
   falling with a sway and a slow spin (Nate, 2026-11-05). */
.show-pin .sp-snow i { position: absolute; top: 0; font-style: normal; line-height: 1; color: #fff;
  text-shadow: 0 0 2px rgba(14, 26, 51, .9), 0 0 1px rgba(14, 26, 51, .9), 0 0 10px rgba(255, 255, 255, .95);
  animation: sp-fall 3s linear infinite; will-change: transform, opacity; }
@keyframes sp-fall { 0% { transform: translate(0, 0) rotate(0deg); opacity: 0; } 10% { opacity: 1; } 35% { transform: translate(9px, 42px) rotate(120deg); } 65% { transform: translate(-7px, 80px) rotate(240deg); } 88% { opacity: 1; } 100% { transform: translate(4px, 122px) rotate(360deg); opacity: 0; } }
.show-pin .sp-card { position: absolute; left: 50%; bottom: calc(100% + 10px); transform: translateX(-50%); white-space: nowrap; max-width: calc(100vw - 64px); overflow: hidden; text-overflow: ellipsis;
  padding: 7px 12px; border-radius: 10px; background: var(--ice); color: var(--night); font: 600 .85rem/1.2 var(--text, inherit); box-shadow: 0 6px 20px rgba(0,0,0,.45); }
@media (prefers-reduced-motion: reduce) { .show-pin[data-on] .sp-dot, .show-pin .sp-snow i { animation: none; } }

/* The listener map in Listener Statistics (2026-11-05). */
.st-map { position: relative; height: clamp(220px, 40vw, 340px); margin: 4px 0 18px; border-radius: 14px; overflow: hidden; background: #0e1a33; }
.st-map[hidden] { display: none; }
.st-map .gmap { position: absolute; inset: 0; }
.st-map-tip { position: absolute; left: 10px; bottom: 10px; z-index: 2; margin: 0; padding: 6px 10px; border-radius: 8px;
  background: var(--ice); color: var(--night); font-size: .85rem; font-weight: 600; pointer-events: none; }
.st-map-tip[hidden] { display: none; }

/* Live and Historical Listener Statistics side by side, live first (Nate, 2026-11-05). */
.stats-row { margin: 18px 0 0; display: flex; flex-wrap: wrap; align-items: flex-start; gap: 12px 32px; }
.stats-row .stats-pick { margin: 0; }
.stats-row [hidden] { display: none; }

/* =====================================================================
   IN THE NEWS (Nate, 2026-11-05). Four pieces of press standing on one shelf, each in its own shape:
   a newsprint clipping, a magazine, a TV frame, a radio wave. Square corners say "object", not photo.
   They light up in turn as they come up (staggered --r1), like the house does.
   ===================================================================== */
.nw-shelf {
  --shelf: color-mix(in oklab, var(--frost) 35%, transparent);
  display: grid; grid-template-columns: 26fr 18fr 32fr 24fr; align-items: end; gap: clamp(18px, 2.4vw, 36px);
  padding-bottom: 18px; border-bottom: 1px solid var(--shelf); margin-top: 8px;
}
.nw-piece {
  font: inherit; color: inherit; text-align: left; border: 0; padding: 0; background: none; cursor: pointer;
  display: flex; flex-direction: column; border-radius: 2px; position: relative;
  transition: box-shadow .2s ease, translate .2s ease;
}
.anim .nw-piece { filter: brightness(calc(.3 + .7 * var(--p))); }
.nw-piece:hover, .nw-piece:focus-visible { outline: none; translate: 0 -4px;
  box-shadow: 0 0 0 2px var(--pixel), 0 0 34px color-mix(in oklab, var(--pixel) 70%, transparent); }
.nw-piece picture, .nw-piece img { display: block; width: 100%; }
.nw-sr { position: absolute; width: 1px; height: 1px; overflow: hidden; clip-path: inset(50%); white-space: nowrap; }

/* The clipping: newsprint, the paper's masthead, the photo, the headline, a torn bottom edge. */
.nw-clip { background: #E8E5DB; color: #1a1a1a; padding: 14px 14px 26px;
  clip-path: polygon(0 0, 100% 0, 100% 97%, 94% 99%, 86% 96.5%, 77% 99.5%, 66% 97%, 55% 99.5%, 44% 96.8%, 33% 99.2%, 22% 97%, 11% 99.5%, 0 97.5%); }
.nw-mast { font-family: var(--display); font-weight: 800; font-stretch: 80%; font-size: clamp(1rem, 1.5vw, 1.35rem); line-height: 1;
  padding-bottom: 8px; margin-bottom: 10px; border-bottom: 3px double #1a1a1a; text-align: center; }
.nw-clip img { filter: grayscale(.25) contrast(1.05); }
.nw-head { font-family: var(--display); font-weight: 800; font-stretch: 75%; font-size: clamp(1.15rem, 1.9vw, 1.7rem); line-height: 1; margin-top: 12px; }
.nw-date { font-size: .8rem; margin-top: 8px; color: #4a4a4a; }

/* The magazine: the real cover, with a spine down the left. */
.nw-mag { flex-direction: row; background: #f3d03e; box-shadow: 6px 8px 0 rgba(0,0,0,.35); }
.nw-mag:hover, .nw-mag:focus-visible { box-shadow: 0 0 0 2px var(--pixel), 0 0 34px color-mix(in oklab, var(--pixel) 70%, transparent); }
.nw-spine { flex: none; width: 22px; writing-mode: vertical-rl; rotate: 180deg; display: grid; place-items: center;
  font-size: .68rem; font-weight: 700; color: #1a1a1a; white-space: nowrap; overflow: hidden; }
.nw-mag picture { flex: 1; min-width: 0; }

/* The TV: the real 2018 frame, the site's play button, the quote as its lower third. */
.nw-tv { background: #000; }
.nw-screen { position: relative; display: block; aspect-ratio: 16 / 9; overflow: hidden; }
.nw-screen img { height: 100%; object-fit: cover; }
.nw-tv .play-icon { --s: 64px; position: absolute; left: 50%; top: 50%; translate: -50% -50%; }
.nw-tv:hover .play-icon, .nw-radio:hover .play-icon { background: white; }
.nw-band { display: grid; grid-template-columns: auto 1fr; gap: 4px 12px; align-items: start; padding: 12px 14px 14px; background: var(--deep);
  border-top: 3px solid var(--pixel); }
.nw-chan { grid-row: span 2; background: var(--ice); color: var(--night); font-weight: 700; font-size: .8rem; padding: 4px 8px; border-radius: 2px; white-space: nowrap; }
.nw-quote { font-weight: 600; font-size: clamp(.95rem, 1.2vw, 1.1rem); line-height: 1.3; }
.nw-who { color: var(--frost); font-size: .8rem; }
.nw-len { display: none; color: var(--frost); font-size: .85rem; font-variant-numeric: tabular-nums; }

/* The radio: the interview's own sound, drawn from the recording. */
.nw-radio { background: linear-gradient(to bottom, color-mix(in oklab, var(--pixel) 22%, var(--deep)), var(--deep));
  padding: 16px; gap: 14px; box-shadow: inset 0 0 0 1px color-mix(in oklab, var(--pixel) 45%, transparent); }
.nw-rtitle { font-family: var(--display); font-weight: 700; font-stretch: 100%; font-size: clamp(1.05rem, 1.5vw, 1.3rem); line-height: 1.1; }
.nw-wave { width: 100%; height: clamp(70px, 8vw, 110px); }
.nw-rfoot { display: flex; align-items: center; gap: 12px; color: var(--frost); font-size: .85rem; }
.nw-rfoot .play-icon { --s: 40px; }

.nw-row { display: contents; }
@media (max-width: 1099px) {
  .nw-shelf { display: block; border-bottom: 0; padding-bottom: 0; }
  .nw-row { display: grid; grid-template-columns: 58fr 42fr; align-items: end; gap: 16px; padding-bottom: 14px; margin-bottom: 26px; border-bottom: 1px solid var(--shelf); }
  .nw-row + .nw-row { grid-template-columns: 1fr; margin-bottom: 0; } /* the TV full width, the radio a short band under it */
  .nw-quote, .nw-who { display: none; }
  .nw-len { display: block; }
  .nw-band { align-items: center; }
  .nw-chan { grid-row: auto; }
  .nw-tv .play-icon { --s: 48px; }
  .nw-radio { padding: 12px; gap: 10px; }
  .nw-rfoot .play-icon { --s: 32px; }
}

/* The pop-outs. */
.news-dialog { width: min(760px, calc(100vw - 32px)); }
.nd-by { color: var(--frost); margin: -10px 0 18px; }
.nd-media { margin: 0 0 18px; }
.nd-media img, .nd-media video { display: block; width: 100%; height: auto; border-radius: 12px; background: #000; }
.nd-scans { display: grid; grid-template-columns: 1fr 1fr; gap: 12px; margin-bottom: 18px; }
.nd-scans img { display: block; width: 100%; height: auto; border-radius: 8px; }
.nd-then { color: var(--frost); font-size: .95rem; margin: 0 0 18px; max-width: 62ch; }
.nd-story { margin: 0 0 18px; padding: 0 0 0 18px; border-left: 3px solid color-mix(in oklab, var(--pixel) 60%, transparent); max-width: 62ch; }
.nd-story p { margin: 0 0 1em; line-height: 1.6; }
.nd-words { max-width: 62ch; } .nd-words p { margin: 0 0 1em; line-height: 1.6; }
.nd-src { margin: 0 0 18px; }
.nd-tracks { list-style: none; margin: 0 0 18px; padding: 0; display: grid; gap: 12px; }
.nd-tracks li { display: grid; grid-template-columns: 1fr auto; gap: 6px 12px; align-items: center; }
.nd-tn { font-weight: 600; } .nd-tl { color: var(--frost); font-variant-numeric: tabular-nums; }
.nd-tracks audio { grid-column: 1 / -1; width: 100%; height: 40px; }
.nd-walk { display: flex; flex-wrap: wrap; justify-content: space-between; gap: 12px; padding-top: 12px; border-top: 1px solid color-mix(in oklab, var(--pixel) 30%, transparent); }
@media (prefers-reduced-motion: reduce) { .nw-piece { transition: none; } .nw-piece:hover, .nw-piece:focus-visible { translate: none; } }

/* #9 pairs with the last portrait flake so no photo sits alone in its row (Nate, 2026-11-05). */
.flake--crop picture img { object-position: 55% 50%; }

/* The family photo's caption (Contact). */
.gp-cap { margin: 8px 0 0; font-style: italic; color: var(--frost); font-size: .875rem; }

/* The family photo in Contact, under the email and address. Tight spacing, and on wide screens the
   photo sits at the foot of its column so its bottom lines up with the form's (Nate, 2026-11-05). */
.ct-fam { margin: 24px 0 0; max-width: 480px; }
.ct-fam .gp-cap { margin-top: 6px; }
@media (min-width: 1200px) {
  .ct-cols { align-items: stretch; }
  .ct-info { display: flex; flex-direction: column; }
  .ct-info .ct-lede { margin-bottom: 20px; }
  .ct-info .ct-facts > div { padding: 10px 0; }
  .ct-fam { margin-top: auto; padding-top: 24px; }
}
.ct-fam picture img { aspect-ratio: 800 / 568; object-fit: contain; }
