/* GZS — groundzerosum.com
   The station and the label on one property, split off from nicheof.one. The player and the
   member catalogue deliberately stay on the network site; everything here links to them.

   Deliberately NOT a fork of the network's design-tokens.css. That file carries a
   computed-style fingerprint contract for nicheof.one and must not be duplicated where it can
   drift. This is a lean, self-contained sheet: a dark console, not a magazine. */

:root {
  --radius: 14px;
  --shadow-soft: 0 1px 2px rgb(0 0 0 / .3), 0 10px 30px -14px rgb(0 0 0 / .6);

  /* Depth retune, 2026-08-20 (Mark): the old band ran #0b0c0f..#1b1f27, four steps that read
     as one flat grey once printed. Widened at both ends so the room actually has a floor and a
     lit surface, not just "dark" and "less dark." */
  --bg: #050506;
  --bg-2: #0b0c0f;
  --surface: #15181e;
  --surface-2: #232838;
  /* Reserved for exactly two things: the Now Playing card and the Clock's live hour. A third
     use turns "this is happening right now" into "this is a nicer box," which is the one thing
     this token must never become. */
  --surface-hot: color-mix(in srgb, var(--accent) 10%, #1b1f27);
  --ink: #eceef2;
  --ink-soft: #98a0ad;
  --ink-dim: #7c8492;
  --rule: #262b34;
  /* Tube-radio amber. Teeters' call, 2026-08-20: the cyan this replaced reads as a clean
     instrument panel, which is the wrong genre for a signal that did not launch, it leaked.
     Mark argued for keeping the cyan because it matched the popout player's kick colour --
     a fair point that loses once the whole premise is that this property has its own
     flavour and does not live on nicheof.one. Contrast checked: amber on --bg is ~9:1,
     and --on-accent on amber (the .btn-hot / .skip pairing) is far past AA. */
  --accent: #e8a33d;
  --on-accent: #140d02;       /* ink for text sitting ON --accent */
  --hot: #ff453a;             /* on-air only. Never decorative. */

  /* All three are vendored as latin-subset woff2 in src/fonts and declared in
     fonts.css (bin/vendor-fonts.py). Until 2026-08-20 Anton was named here with no
     @font-face and no file anywhere in the repo, so every visitor got Arial Narrow
     and the display identity was a fallback nobody chose. The stacks below are the
     real fallbacks now, not the actual rendering. */
  --disp: "Anton", "Arial Narrow", Impact, sans-serif;
  --body: "IBM Plex Sans", system-ui, -apple-system, "Segoe UI", Roboto, Helvetica, Arial, sans-serif;
  --mono: "IBM Plex Mono", ui-monospace, SFMono-Regular, Menlo, "Courier New", monospace;

  --measure: 68ch;
  --gap: clamp(1rem, 3vw, 2rem);
  --pad: clamp(1.1rem, 4vw, 2.6rem);

  /* Floor-safe micro label size. .68rem/.7rem measure 10.9px/11.2px at the default 16px root —
     under the 11.5px readability floor. .72rem clears it (11.5px). Every small mono label uses
     this token so a future page can't reintroduce a sub-floor size by copying an old value. */
  --fs-micro: .72rem;
}

/* The station is a dark room. A light mode here would be a different station, so this sheet
   commits to one look: background and colour are painted explicitly, with no light branch. */

*, *::before, *::after { box-sizing: border-box; }
html { scroll-behavior: smooth; }
@media (prefers-reduced-motion: reduce) { html { scroll-behavior: auto; } }

body {
  margin: 0;
  /* Vignette as a second background layer, not a third pseudo-element -- ::before/::after are
     spoken for below (scanlines, grain). Same one Mark asked for on every page: the room has
     an edge now instead of matte colour running flat to every corner. */
  background: radial-gradient(ellipse 80% 70% at 50% 30%, transparent 45%, rgb(0 0 0 / .45) 100%), var(--bg);
  color: var(--ink);
  font-family: var(--body);
  font-size: clamp(1rem, .96rem + .2vw, 1.075rem);
  line-height: 1.6;
  -webkit-text-size-adjust: 100%;
  /* Backstop for the Clock's width:100vw full-bleed (see .tl-bleed): 100vw can run a few
     pixels past the visible viewport when a vertical scrollbar is present. This clips that
     without touching overflow-y, so the sticky masthead is unaffected. */
  overflow-x: hidden;
}
/* Grain + scanlines, the network's own proven pattern (~/noo/network/src/css/os.css:122-126),
   ported rather than reinvented. This room is dark-only (see the top-of-file note), so there is
   no light-mode branch to write -- the network carries one per theme, this carries one, period. */
body::after {
  content: ""; position: fixed; inset: 0; z-index: 9000; pointer-events: none;
  background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='200' height='200'%3E%3Cfilter id='n'%3E%3CfeTurbulence type='fractalNoise' baseFrequency='0.8' numOctaves='3' stitchTiles='stitch'/%3E%3C/filter%3E%3Crect width='100%25' height='100%25' filter='url(%23n)'/%3E%3C/svg%3E");
  background-size: 200px 200px;
  opacity: .07; mix-blend-mode: screen;
}
body::before {
  content: ""; position: fixed; inset: 0; z-index: 8999; pointer-events: none;
  background: repeating-linear-gradient(0deg, rgb(0 0 0 / 0) 0 3px, rgb(0 0 0 / .055) 3px 4px);
  mix-blend-mode: multiply;
}
img { max-width: 100%; height: auto; display: block; }
a { color: inherit; text-underline-offset: .18em; }
h1, h2, h3 { line-height: 1.12; margin: 0; }

.skip {
  position: absolute; left: -9999px; z-index: 99;
  background: var(--accent); color: var(--on-accent); padding: .7rem 1rem; border-radius: 0 0 var(--radius) 0;
  font: 700 .85rem/1 var(--mono); letter-spacing: .06em;
}
.skip:focus { left: 0; }

.wrap { width: min(72rem, 100% - 2 * var(--pad)); margin-inline: auto; }

/* ---------- masthead ---------- */
.mast {
  position: sticky; inset-block-start: 0; z-index: 40;
  display: flex; align-items: center; gap: var(--gap); flex-wrap: wrap;
  padding: .8rem var(--pad);
  background: color-mix(in srgb, var(--bg) 88%, transparent);
  backdrop-filter: blur(10px);
  border-bottom: 1px solid var(--rule);
}
.mark { display: flex; align-items: baseline; gap: .5rem; text-decoration: none; margin-right: auto; }
.mark-g { font: 400 1.6rem/1 var(--disp); letter-spacing: .06em; }
.mark-sub { font: 700 var(--fs-micro)/1 var(--mono); letter-spacing: .22em; text-transform: uppercase; color: var(--ink-dim); }
.mast nav { display: flex; gap: 1.1rem; flex-wrap: wrap; }
.mast nav a {
  font: 700 .74rem/1 var(--mono); letter-spacing: .13em; text-transform: uppercase;
  color: var(--ink-soft); text-decoration: none; padding: .4rem 0;
}
.mast nav a:hover, .mast nav a:focus-visible { color: var(--accent); }
.mast nav a.nav-hot { color: var(--accent); }
.mast nav a.nav-hot:hover, .mast nav a.nav-hot:focus-visible { color: #fff; }

/* ---------- on air ---------- */
.hero {
  padding: clamp(2.5rem, 8vw, 5rem) 0 clamp(2rem, 5vw, 3rem);
  position: relative;
}
/* One light source in the room. z-index:-1 (not overflow:hidden on .hero) so it never clips
   the Now Playing card's own glow, which sits inside this same section and needs its
   box-shadow to bleed past its border-box. */
.hero::before {
  content: "";
  position: absolute; inset: 0; z-index: -1;
  background: radial-gradient(ellipse 60% 50% at 30% 40%,
    color-mix(in srgb, var(--accent) 18%, transparent), transparent 70%);
  pointer-events: none;
}
.kick {
  font: 700 var(--fs-micro)/1 var(--mono); letter-spacing: .2em; text-transform: uppercase;
  color: var(--ink-dim); margin: 0 0 1rem;
}
.station { font: 400 clamp(3rem, 13vw, 7.5rem)/.86 var(--disp); letter-spacing: .01em; }
.station em { font-style: normal; color: var(--accent); }
.tagline { color: var(--ink-soft); max-width: 46ch; margin: 1.1rem 0 0; }

/* ---------- the dial ----------
   Mark's one unsafe move: a literal tuner strip parked under the station numeral. Ticks are one
   repeating-linear-gradient (zero DOM weight, no 30 hand-placed elements); the needle is the
   only thing that moves, a slow +/-1px drift so it reads as an idling instrument rather than a
   dead graphic. Guarded exactly like .blip below: prefers-reduced-motion turns the animation off
   completely, not just slower. */
.dial {
  position: relative; height: 2.2rem; margin: 1.6rem 0 0; overflow: hidden;
  --dial-pos: 58%;
}
.dial-ticks {
  position: absolute; inset: 0;
  background-image: repeating-linear-gradient(90deg,
    color-mix(in srgb, var(--accent) 55%, transparent) 0 1px,
    transparent 1px calc(100% / 30));
}
.dial-needle {
  position: absolute; top: 0; bottom: .55rem; left: var(--dial-pos); width: 2px;
  background: var(--accent);
  box-shadow: 0 0 10px 2px var(--accent);
  animation: dial-drift 9s ease-in-out infinite alternate;
}
.dial-freq {
  position: absolute; bottom: 0; left: var(--dial-pos); transform: translateX(-50%);
  font: 700 var(--fs-micro)/1 var(--mono); letter-spacing: .06em; color: var(--accent);
}
@keyframes dial-drift {
  from { transform: translateX(-1px); }
  to   { transform: translateX(1px); }
}
@media (prefers-reduced-motion: reduce) { .dial-needle { animation: none; } }

.live {
  display: inline-flex; align-items: center; gap: .5rem; vertical-align: middle;
  font: 700 .74rem/1 var(--mono); letter-spacing: .14em; text-transform: uppercase; color: var(--hot);
}
.blip { width: .55rem; height: .55rem; border-radius: 50%; background: var(--hot); box-shadow: 0 0 12px 2px var(--hot); animation: blip 2s ease-in-out infinite; }
@keyframes blip { 0%, 100% { opacity: 1 } 50% { opacity: .25 } }
@media (prefers-reduced-motion: reduce) { .blip { animation: none } }

/* now playing + listen */
.np {
  display: grid; grid-template-columns: auto 1fr; gap: 1rem 1.2rem; align-items: center;
  margin: 2rem 0 0; padding: 1.1rem 1.2rem;
  /* One of exactly two --surface-hot uses (see the token). This card IS the live thing. */
  background: var(--surface-hot);
  border: 1px solid color-mix(in srgb, var(--accent) 35%, var(--rule));
  border-radius: var(--radius);
  box-shadow: 0 0 48px -12px rgb(232 163 61 / .35), var(--shadow-soft);
}
.np-art {
  width: 72px; height: 72px; border-radius: 10px; object-fit: cover;
  background: var(--surface-2); border: 1px solid var(--rule);
}
/* ink-soft, not ink-dim: this card's background is --surface-hot now (see .np below), and
   ink-dim measures 3.67:1 there -- same class of failure Merritt caught on the Clock's
   disclosure, found here by the same check before it shipped. */
.np-lbl { font: 700 var(--fs-micro)/1 var(--mono); letter-spacing: .18em; text-transform: uppercase; color: var(--ink-soft); }
.np-track { font-weight: 600; margin: .35rem 0 .2rem; }
.np-show { font: .82rem/1.4 var(--mono); color: var(--ink-soft); }
/* The old 4-box .stats grid, folded into one line under the track — same four numbers,
   a fifth of the vertical weight. */
.np-stats { font: var(--fs-micro)/1.5 var(--mono); letter-spacing: .04em; color: var(--ink-soft); margin: .5rem 0 0; }
.np-cta { grid-column: 1 / -1; display: flex; gap: .6rem; flex-wrap: wrap; }

.btn {
  display: inline-flex; align-items: center; gap: .5rem;
  font: 700 .78rem/1 var(--mono); letter-spacing: .1em; text-transform: uppercase;
  padding: .8rem 1.1rem; border-radius: 999px; border: 1px solid var(--rule);
  background: var(--surface-2); color: var(--ink); text-decoration: none; cursor: pointer;
}
.btn:hover, .btn:focus-visible { border-color: var(--accent); color: var(--accent); }
.btn-hot { background: var(--accent); color: var(--on-accent); border-color: var(--accent); }
.btn-hot:hover, .btn-hot:focus-visible { background: #fff; border-color: #fff; color: var(--on-accent); }

/* ---------- sections ---------- */
.sec { padding: clamp(2.6rem, 7vw, 4.5rem) 0; border-top: 1px solid var(--rule); }
.seclbl { font: 400 clamp(1.8rem, 5vw, 2.8rem)/1 var(--disp); letter-spacing: .02em; }
.secsub { color: var(--ink-soft); max-width: var(--measure); margin: .8rem 0 0; }

/* ---------- the clock: multitrack timeline ----------
   Two lanes over a 24h axis, full-bleed at every width now (Mark, rhythm break 2026-08-20) --
   it used to settle back inside .wrap at 1024px+; now it stays edge-to-edge always, on its own
   darker --bg-2 panel with a top/bottom rule, so it reads as an instrument strip dropped into
   the page rather than just a wide .sec. The heading above it (kick/h2/subhead) stays in .wrap
   at the page's own --bg, which is what actually produces the "dropped out of the rhythm" cut —
   the panel changes surface where the copy does not.
   The 100vw bleed trick can push a hair past the visible viewport when a vertical scrollbar is
   present — `overflow-x: hidden` on body (below, in the base rules) is the backstop so that
   never becomes page-level horizontal scroll. */
.tl-bleed {
  width: 100vw;
  margin-inline: calc(50% - 50vw);
  margin-top: 2rem;
  overflow-x: auto;
  overscroll-behavior-x: contain;
  -webkit-overflow-scrolling: touch;
  scroll-snap-type: x proximity;
  padding: 1.4rem var(--pad) 1.6rem;
  background: var(--bg-2);
  border-block: 1px solid var(--rule);
}
.tl {
  position: relative;
  display: flex; flex-direction: column; gap: 3px;
  min-width: max(100%, calc(44px * 24));
}
.tl-ribbon, .tl-ticks, .tl-labels { display: flex; }
.tl-ribbon { gap: 1px; }
.tl-seg {
  flex-shrink: 0; flex-basis: 0; min-width: 44px;
  display: flex; align-items: center; justify-content: center; text-align: center;
  height: 3.2rem; padding: 0 .4rem;
  background: var(--surface-2); border: 1px solid var(--rule); border-radius: 6px;
  color: var(--ink); text-decoration: none; scroll-snap-align: start;
}
a.tl-seg:hover, a.tl-seg:focus-visible { border-color: var(--accent); color: var(--accent); }
.tl-seg--furniture { color: var(--ink-dim); background: var(--bg-2); }
.tl-seg--gap { background: transparent; border-style: dashed; }
.tl-seg-name {
  font: 700 var(--fs-micro)/1.15 var(--mono); letter-spacing: .02em; max-width: 100%;
  /* Two lines, clamped, instead of one truncated line. A one-hour segment is ~55px wide at
     1440px; on a single line that fits about five characters, which is how five neighbouring
     blocks all came out as "The …". Wrapping to two lines roughly doubles what survives, and
     the leading article is already stripped upstream (see clockRibbon in eleventy.config.js). */
  display: -webkit-box; -webkit-box-orient: vertical; -webkit-line-clamp: 2;
  /* Break between words, never inside one. `overflow-wrap: anywhere` fitted more characters
     but rendered "Hidden Light Revival" as "Hidde / n…", which reads as corruption rather than
     abbreviation. Whole words that run out of room are honest; a severed one is not. */
  overflow: hidden; overflow-wrap: normal; word-break: normal; hyphens: none;
}

/* Colour by KIND, three hues, not one per show. Bill's call and it is the right one: when a
   narrow segment's text clips, the band still reads as a texture map of the day — where the
   music is, where the talk is, where the station is just clearing its throat. */
.tl-music       { border-color: color-mix(in srgb, var(--accent) 45%, var(--rule)); }
.tl-spoken      { border-color: color-mix(in srgb, #7fbf8f 45%, var(--rule)); }
.tl-spoken .tl-seg-name { color: #9ed0aa; }
.tl-interstitial, .tl-rotation { border-style: dashed; }

/* The other --surface-hot use (see the token comment). JS-applied (.is-live below) because
   "which segment covers this hour" is the reader's local clock, same call as the playhead --
   a static build cannot know it server-side. */
.tl-seg.is-live {
  background: var(--surface-hot);
  border-color: var(--accent);
  box-shadow: 0 0 48px -12px rgb(232 163 61 / .35);
}
a.tl-seg.is-live:hover, a.tl-seg.is-live:focus-visible { border-color: #fff; }

.tl-legend {
  display: flex; align-items: center; gap: .4rem; flex-wrap: wrap;
  margin: .9rem 0 0; font: var(--fs-micro)/1.6 var(--mono); color: var(--ink-dim);
}
.tl-legend b { color: var(--ink-soft); }
.tl-legend-sep { color: var(--rule); margin: 0 .2rem; }
.tl-key {
  width: .85rem; height: .5rem; border-radius: 2px; display: inline-block;
  background: var(--surface-2); border: 1px solid var(--rule);
}
.tl-key.tl-music  { border-color: color-mix(in srgb, var(--accent) 60%, var(--rule)); }
.tl-key.tl-spoken { border-color: color-mix(in srgb, #7fbf8f 60%, var(--rule)); }
.tl-key.tl-interstitial { border-style: dashed; }
.tl-ticks { gap: 1px; }
.tl-hr {
  flex: 1 0 0; min-width: 44px; height: 1.3rem;
  display: flex; align-items: center; justify-content: center;
  background: var(--bg-2); border-radius: 4px; scroll-snap-align: start;
}
.tl-hr.has-more { background: var(--surface-2); }
.tl-more { position: relative; }
.tl-more summary {
  list-style: none; cursor: pointer;
  /* This is a real disclosure button, not decoration -- Merritt measured ink-dim on
     --surface-2 (.tl-hr.has-more's background) at 3.89:1, under the 4.5:1 floor, and the
     depth retune made it worse (was already failing at 4.38 before). ink-soft on the same
     surface-2 measures 5.56:1. Scoped fix, not a global token change: ink-dim still passes
     clean against bg/bg-2/surface (5.41/5.19/4.72), which is where it does the rest of its
     work. */
  font: 700 var(--fs-micro)/1 var(--mono); color: var(--ink-soft);
  min-width: 44px; min-height: 1.3rem; display: flex; align-items: center; justify-content: center;
}
.tl-more summary::-webkit-details-marker { display: none; }
.tl-more summary:hover, .tl-more summary:focus-visible { color: var(--accent); }
.tl-more[open] summary { color: var(--accent); }
.tl-more-list {
  position: absolute; z-index: 10; top: calc(100% + 4px); left: 50%; transform: translateX(-50%);
  display: flex; flex-direction: column; gap: .3rem; min-width: 12rem; max-width: 16rem;
  padding: .7rem .8rem; background: var(--surface); border: 1px solid var(--rule);
  border-radius: 10px; box-shadow: var(--shadow-soft);
  font: .8rem/1.3 var(--mono);
}
.tl-more-list a { color: var(--ink); }
.tl-more-list a:hover, .tl-more-list a:focus-visible { color: var(--accent); }
.tl-more-list span { color: var(--ink-dim); }
.tl-labels { gap: 1px; margin-top: 2px; }
.tl-lbl {
  flex: 1 0 0; min-width: 44px; text-align: center;
  font: var(--fs-micro)/1 var(--mono); color: var(--ink-dim);
}
.tl-playhead {
  position: absolute; top: 0; bottom: 1.5rem; width: 2px; background: var(--hot);
  box-shadow: 0 0 8px 1px var(--hot);
  pointer-events: none; transition: none;
}
.tl-playhead::before {
  content: ""; position: absolute; top: -4px; left: 50%; transform: translateX(-50%);
  width: 8px; height: 8px; border-radius: 50%; background: var(--hot);
  box-shadow: 0 0 8px 1px var(--hot);
}

/* No 1024px+ breakpoint here any more: the panel is full-bleed everywhere now, so the 44px
   touch-target floor (WCAG 2.5.5) just stays on at every width instead of being dropped once
   the layout no longer needed the room. A little optional horizontal scroll on a desktop
   pointer is a fair trade against a target that shrinks out from under a touch reader who
   resizes their window. */

/* ---------- show cards ---------- */
.shows {
  display: grid; grid-template-columns: repeat(auto-fill, minmax(17rem, 1fr));
  gap: var(--gap); margin: 2rem 0 0; list-style: none; padding: 0;
}
.show {
  background: var(--surface); border: 1px solid var(--rule); border-radius: var(--radius);
  overflow: hidden; display: flex; flex-direction: column; box-shadow: var(--shadow-soft);
  text-decoration: none; height: 100%;
}
a.show:hover, a.show:focus-visible { border-color: var(--accent); }
.show-cov { aspect-ratio: 16 / 9; width: 100%; object-fit: cover; background: var(--surface-2); }
.show-blank { aspect-ratio: 16 / 9; display: grid; place-items: center; background: var(--surface-2); font: 400 2.2rem/1 var(--disp); color: var(--rule); }
.show-body { padding: 1rem 1.1rem 1.2rem; display: flex; flex-direction: column; gap: .45rem; flex: 1; }
.show-time {
  font: 700 .72rem/1.35 var(--mono); letter-spacing: .1em; text-transform: uppercase; color: var(--accent);
  /* Reserved height, not a clamp: Brother Dewey Sparks' schedule is a real 36-character,
     4-slot string ("9a-10a Sun . 12a-2a . 12p-1p . 7p-9p") and a submitting artist needs to
     read every slot, so truncating it is off the table. Reserving 3 lines' worth of space
     keeps every card's h3 baseline aligned across a grid row without cutting anything -- the
     one show that actually needs 3 lines gets them; everything shorter just gets a little air. */
  min-height: calc(1.35em * 3);
}
.show h3 { font-size: 1.16rem; }
.show p { margin: 0; color: var(--ink-soft); font-size: .93rem; }
.show-meta { margin-top: auto; padding-top: .6rem; font: var(--fs-micro)/1 var(--mono); letter-spacing: .08em; text-transform: uppercase; color: var(--ink-dim); }

/* ---------- flagship strip (home) ---------- */
.flagship {
  display: flex; gap: .9rem; margin: 2rem 0 0; overflow-x: auto; padding-bottom: .4rem;
  scroll-snap-type: x proximity; -webkit-overflow-scrolling: touch;
  /* Fade the trailing edge so a card cut off at the boundary reads as "there is more, scroll"
     instead of "this is broken". Without it the strip ends on a card sliced mid-word, which is
     exactly what a rendering bug looks like. Masks both edges once scrolled, via the standard
     two-property form so it works in Safari too. */
  -webkit-mask-image: linear-gradient(to right, transparent 0, #000 1.6rem,
                                      #000 calc(100% - 2.4rem), transparent 100%);
          mask-image: linear-gradient(to right, transparent 0, #000 1.6rem,
                                      #000 calc(100% - 2.4rem), transparent 100%);
  scrollbar-width: thin;
}
/* A masked left edge would hide the first card's border at rest, so only fade the right until
   the reader has actually scrolled. */
.flagship:not([data-scrolled]) {
  -webkit-mask-image: linear-gradient(to right, #000 calc(100% - 2.4rem), transparent 100%);
          mask-image: linear-gradient(to right, #000 calc(100% - 2.4rem), transparent 100%);
}
.flagship-card {
  flex: 0 0 12rem; scroll-snap-align: start;
  background: var(--surface); border: 1px solid var(--rule); border-radius: var(--radius);
  overflow: hidden; text-decoration: none; color: var(--ink); box-shadow: var(--shadow-soft);
}
.flagship-card:hover, .flagship-card:focus-visible { border-color: var(--accent); }
.flagship-cov, .flagship-blank { aspect-ratio: 4 / 3; width: 100%; object-fit: cover; background: var(--surface-2); }
.flagship-blank { display: grid; place-items: center; font: 400 1.6rem/1 var(--disp); color: var(--rule); }
.flagship-time { margin: .7rem .8rem 0; font: 700 var(--fs-micro)/1 var(--mono); letter-spacing: .08em; text-transform: uppercase; color: var(--accent); }
.flagship-card h3 { margin: .3rem .8rem .8rem; font-size: 1rem; }

/* ---------- roster ---------- */
/* .roster is the home page's top-8 teaser only now -- the full 34-act /roster/ page moved
   to a dense row list (.roster-rows below); real photo art on all 8 of these tiles is
   exactly what a card grid is for. 15rem -> 18rem: at 15rem, "Sonny Blackwell & the
   Fission Five" (this dataset's longest act name) truncated to "Sonny Blackwell & t..."
   with no way to recover the rest -- title= (see the template) covers hover/long-press,
   but the wider column means fewer names need it in the first place. */
.roster { display: grid; grid-template-columns: repeat(auto-fill, minmax(18rem, 1fr)); gap: .9rem; margin: 2rem 0 0; list-style: none; padding: 0; }

/* ---------- roster: dense row list (the full /roster/ page — see roster/index.njk) ---------- */
.roster-rows { list-style: none; margin: 2rem 0 0; padding: 0; display: grid; gap: 2px; }
.rrow {
  position: relative; overflow: hidden;
  display: grid; grid-template-columns: 2.6rem 1fr auto; align-items: center; gap: .8rem;
  padding: .6rem .8rem; background: var(--bg-2); border-radius: 8px;
}
.rrow-cov, .rrow-blank {
  width: 2.6rem; height: 2.6rem; border-radius: 6px; object-fit: cover; background: var(--surface-2);
}
.rrow-blank { display: grid; place-items: center; font: 400 .95rem/1 var(--disp); color: var(--rule); }
.rrow-name { font-weight: 600; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.rrow-tracks {
  font: var(--fs-micro)/1 var(--mono); letter-spacing: .06em; text-transform: uppercase;
  color: var(--ink-dim); white-space: nowrap;
}
/* The meter as a thin full-row underline instead of a short bar under the name — "real width,"
   per Mark, means the number of tracks is legible AND the meter reads at the scale of the row
   it belongs to, not a decorative sliver. */
.rrow-bar {
  position: absolute; left: 0; right: 0; bottom: 0; height: 2px; background: var(--rule);
}
.rrow-bar i { display: block; height: 100%; background: var(--accent); width: calc(var(--w) * 100%); }
@media (min-width: 56.25rem) {
  /* ~900px. Default (row) grid-auto-flow, not column: the list is already rank-ordered by
     track count, and row-major keeps that order reading top-to-bottom then left-to-right the
     way any other ranked list does, instead of splitting the top half into column 1 and the
     bottom half into column 2. */
  .roster-rows { grid-template-columns: 1fr 1fr; gap: 2px .7rem; }
}
.act {
  display: flex; align-items: center; gap: .8rem;
  padding: .8rem .9rem; background: var(--surface); border: 1px solid var(--rule); border-radius: 11px;
}
.act-info { min-width: 0; flex: 1; }
.act-cov, .act-blank {
  flex: 0 0 auto; width: 3rem; height: 3rem; border-radius: 8px; object-fit: cover;
  background: var(--surface-2);
}
.act-blank { display: grid; place-items: center; font: 400 1rem/1 var(--disp); color: var(--rule); }
.act-name { font-weight: 600; margin: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.act-meta { margin: .3rem 0 0; font: var(--fs-micro)/1 var(--mono); letter-spacing: .08em; text-transform: uppercase; color: var(--ink-dim); }
.act-bar { height: 3px; margin: .6rem 0 0; background: var(--rule); border-radius: 2px; overflow: hidden; }
.act-bar i { display: block; height: 100%; background: var(--accent); width: calc(var(--w) * 100%); }

/* ---------- show detail page ---------- */
.show-hero-cov { max-width: 28rem; border-radius: var(--radius); overflow: hidden; }
.show-hero-cov .show-cov, .show-hero-cov .show-blank { aspect-ratio: 16 / 9; }
.show-facts {
  display: flex; flex-wrap: wrap; gap: 1.6rem; margin: 1.6rem 0 0;
  padding: 1rem 0 0; border-top: 1px solid var(--rule);
}
.show-facts div { min-width: 8rem; }
@media (max-width: 34rem) {
  /* Three facts at min-width 8rem + two 1.6rem gaps want ~35.2rem; a 390px viewport (~24.4rem
     of .wrap content) has about 22rem, so the third fact wrapped alone under two, with a gap
     of orphan whitespace next to it. Two even columns read as a grid instead of a mistake. */
  .show-facts { gap: .8rem 1.6rem; }
  .show-facts div { flex-basis: calc(50% - .8rem); min-width: 0; }
}
.show-facts dt { font: 700 var(--fs-micro)/1 var(--mono); letter-spacing: .1em; text-transform: uppercase; color: var(--ink-dim); margin: 0; }
.show-facts dd { margin: .35rem 0 0; font-weight: 600; text-transform: capitalize; }

/* ---------- prose + notes ---------- */
.prose { max-width: var(--measure); }
.prose p { margin: 0 0 1rem; }
.prose p:last-child { margin-bottom: 0; }
.note {
  padding: 1.1rem 1.2rem; background: var(--bg-2); margin: 1.6rem 0 0;
  border: 1px solid var(--rule); border-left: 3px solid var(--accent);
  border-radius: 0 var(--radius) var(--radius) 0;
}
.note p { margin: 0 0 .7rem; }
.note p:last-child { margin-bottom: 0; }

.rules { margin: 1.4rem 0 0; padding: 0; list-style: none; display: grid; gap: .7rem; max-width: var(--measure); }
.rules li { padding-left: 1.6rem; position: relative; color: var(--ink-soft); }
.rules li::before { content: "\25B8"; position: absolute; left: 0; color: var(--accent); }
.rules b { color: var(--ink); }

/* ---------- footer ---------- */
.foot { border-top: 1px solid var(--rule); padding: 2.4rem var(--pad) 3rem; text-align: center; }
.foot-line { margin: 0; font: 700 .76rem/1.7 var(--mono); letter-spacing: .1em; text-transform: uppercase; color: var(--ink-soft); }
.foot-meta { margin: .7rem 0 0; font-size: .86rem; color: var(--ink-dim); }

@media (max-width: 34rem) {
  .np { grid-template-columns: 1fr; }
  .np-art { width: 56px; height: 56px; }
}
