@font-face {
  font-family: "Strippy";
  font-style: normal;
  font-weight: 400;
  font-display: swap;
  src: url("../fonts/strippy-regular.woff2") format("woff2");
}

@font-face {
  font-family: "DM Sans";
  font-style: normal;
  font-weight: 300;
  font-display: swap;
  src: url("../fonts/dm-sans-300.woff2") format("woff2");
}
@font-face {
  font-family: "DM Sans";
  font-style: normal;
  font-weight: 400;
  font-display: swap;
  src: url("../fonts/dm-sans-400.woff2") format("woff2");
}
@font-face {
  font-family: "DM Sans";
  font-style: normal;
  font-weight: 600;
  font-display: swap;
  src: url("../fonts/dm-sans-600.woff2") format("woff2");
}
@font-face {
  font-family: "DM Sans";
  font-style: normal;
  font-weight: 700;
  font-display: swap;
  src: url("../fonts/dm-sans-700.woff2") format("woff2");
}
@font-face {
  font-family: "DM Sans";
  font-style: normal;
  font-weight: 800;
  font-display: swap;
  src: url("../fonts/dm-sans-800.woff2") format("woff2");
}
@font-face {
  /* Jost: a Futura revival. One variable file covers 100-900, so it replaces
     three static Space Grotesk files at a similar total weight. */
  font-family: "Jost";
  font-style: normal;
  font-weight: 100 900;
  font-display: swap;
  src: url("../fonts/jost-var.woff2") format("woff2-variations");
}

:root {
  /* Light canvas, revised 2026-07-22 after seeing the dark build: Telepathic
     Instruments model — warm linen page, white cards lifting off it, while the
     hero and live-video band stay pinned to literal dark as deliberate
     cinematic moments, tied together by the dark floating nav pill.
     (Dark tokens, if ever wanted again: bg #121110, surface #1c1a17,
     fg #f5efe2, muted #a69e90, border #2c2925, accent-text #4da3e8.) */
  --bg: #ececee;      /* off-white canvas (2026-08). Owner felt pure white was jarring; started
     at #e8e8ea (dropped --muted's contrast to 4.35:1, under AA's 4.5:1 floor, so nudged to
     #ececee). Briefly tried a warmer #eeece9, but owner asked to go back to this cooler
     #ececee. Passes AA on every text color that sits on it (muted 4.52, accent-text 4.92,
     fg 14.75) — see the Colors section note in DESIGN.md before adjusting again without
     rechecking contrast. */
  --surface: #ececee; /* elevated card/section surface — same tone, single flat canvas */
  --fg: #1a1a1a;
  --muted: #6b6b6b;
  --border: #ddd9d2;
  --accent: #0872c0;
  --accent-text: #0768b0; /* Kelly Blue darkened one step: base #0872c0 is 4.38:1 on the linen canvas, a hair under AA. This passes on both linen (5.06) and white (5.81). */
  --accent-2: #d4a574;    /* amber — decorative fills only on light (2.2:1 as text) */
  --ink: #161514;         /* the dark pill nav + dark moments share this */
  --radius: 8px;
  --radius-pill: 999px;
  --border-w: 1px;
  --font: "DM Sans", system-ui, -apple-system, "Segoe UI", "Helvetica Neue", Arial, sans-serif;
  --font-name: "Strippy", "Jost", Impact, sans-serif;  /* the artist name lockup */
  /* Strippy now also sets the main section titles (owner's call 2026-07-29), so
     it has its own token rather than being aliased to --font-name. */
  --font-title: "Strippy", "Jost", Impact, sans-serif;
  /* Jost (Futura revival) carries captions, sub-heads, labels and UI — the
     supporting voice under Strippy's titles. Replaced Space Grotesk. */
  --font-display: "Jost", "DM Sans", system-ui, -apple-system, "Segoe UI", "Helvetica Neue", Arial, sans-serif;
  --space: 8px;
  --max: 1200px;
  --nav-h: 72px;
  --ease: cubic-bezier(0.22, 1, 0.36, 1);
  --ease-out: cubic-bezier(0.215, 0.61, 0.355, 1);
  /* Flat system — every reference site refuses box-shadow; depth is surface
     steps and hairlines. Tokens kept (as none) so old usages resolve cleanly. */
  --shadow: none;
  --shadow-sm: none;
  --shadow-lg: none;
}

*, *::before, *::after { box-sizing: border-box; margin: 0; padding: 0; }
html { scroll-behavior: smooth; }
@media (prefers-reduced-motion: reduce) {
  html { scroll-behavior: auto; }
  *, *::before, *::after {
    animation-duration: 0.01ms !important;
    animation-iteration-count: 1 !important;
    transition-duration: 0.01ms !important;
  }
}

body {
  font-family: var(--font);
  background: var(--bg);
  color: var(--fg);
  font-size: 16px;
  line-height: 1.6;
  font-weight: 400;
  -webkit-font-smoothing: antialiased;
  overflow-x: hidden;
}

img { display: block; max-width: 100%; height: auto; }
a { color: inherit; text-decoration: none; }
button { font-family: inherit; cursor: pointer; border: none; background: none; color: inherit; }
ul { list-style: none; }

.skip {
  position: absolute;
  left: -9999px;
  top: 0;
  background: var(--fg);
  color: var(--bg);
  padding: 12px 16px;
  z-index: 1000;
}
.skip:focus { left: 16px; top: 16px; }

/* ——— Nav: the original design's bar, restored ———
   Full-width light glass with a hairline base; the CTA and the SK logo mark
   both wear Kelly Blue. Since the nav now hides over the hero and only
   appears once scrolled (or immediately on pages with no hero), the old
   transparent-over-photo state is gone: this single solid state is the only
   one that ever shows. */
.nav {
  position: fixed;
  inset: 0 0 auto 0;
  height: 64px;
  z-index: 100;
  display: flex;
  align-items: center;
  background: color-mix(in srgb, #ffffff 92%, transparent);
  backdrop-filter: blur(16px);
  -webkit-backdrop-filter: blur(16px);
  box-shadow: 0 1px 0 var(--border); /* hairline, not elevation */
  color: var(--fg);
  transition: transform 0.5s var(--ease), opacity 0.4s ease, visibility 0s;
}
/* Home page only: hidden over the hero poster until the visitor scrolls.
   JS adds .nav--waiting on load (only when a .hero exists), so without JS
   the nav is simply visible. */
.nav.nav--waiting {
  transform: translateY(-110%);
  opacity: 0;
  visibility: hidden;
  transition: transform 0.5s var(--ease), opacity 0.4s ease, visibility 0s 0.5s;
}
.nav-inner {
  width: min(100% - 48px, var(--max));
  margin: 0 auto;
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 24px;
}
.logo {
  font-weight: 700;
  font-size: 15px;
  letter-spacing: 0.14em;
  text-transform: uppercase;
  display: flex;
  align-items: center;
  gap: 10px;
  color: inherit;
  white-space: nowrap;
}
.logo-mark {
  /* The real logo.png mark (2026-08, owner's call) — a thicker ring and a
     larger, more characterful interlocked "SK" than a CSS text badge could
     match. The file is opaque within its own circle (no border-radius/clip
     needed) and transparent outside it, so it drops cleanly onto any surface. */
  width: 32px;
  height: 32px;
  display: block;
}
.nav-links {
  display: flex;
  align-items: center;
  gap: 4px;
}
.nav-links a {
  font-size: 13px;
  font-weight: 600;
  letter-spacing: 0.04em;
  text-transform: uppercase;
  padding: 10px 14px;
  color: var(--muted);
  border-radius: var(--radius);
  transition: color 0.2s var(--ease), background 0.2s var(--ease);
}
.nav-links a:hover {
  color: var(--fg);
  background: color-mix(in srgb, var(--fg) 6%, transparent);
}
.nav-cta {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  min-height: 44px;
  padding: 0 22px;
  background: var(--accent);
  color: #ffffff;
  font-size: 12px;
  font-weight: 700;
  letter-spacing: 0.1em;
  text-transform: uppercase;
  border-radius: var(--radius);
  white-space: nowrap;
  transition: background 0.2s var(--ease);
}
.nav-cta:hover {
  background: color-mix(in srgb, var(--accent) 85%, #000000);
}
.menu-btn {
  display: none;
  width: 44px;
  height: 44px;
  border: var(--border-w) solid var(--border);
  border-radius: var(--radius);
  align-items: center;
  justify-content: center;
  flex-direction: column;
  gap: 5px;
  background: var(--surface);
}
.menu-btn span {
  display: block;
  width: 18px;
  height: 1.5px;
  background: var(--fg);
  transition: transform 0.25s var(--ease), opacity 0.2s;
}
.menu-btn[aria-expanded="true"] span:nth-child(1) { transform: translateY(6.5px) rotate(45deg); }
.menu-btn[aria-expanded="true"] span:nth-child(2) { opacity: 0; }
.menu-btn[aria-expanded="true"] span:nth-child(3) { transform: translateY(-6.5px) rotate(-45deg); }

.mobile-panel {
  display: none;
  position: fixed;
  inset: var(--nav-h) 0 0 0;
  background: var(--bg);
  z-index: 99;
  padding: 24px;
  flex-direction: column;
  gap: 8px;
  transform: translateY(-8px);
  opacity: 0;
  pointer-events: none;
  transition: opacity 0.3s var(--ease), transform 0.3s var(--ease);
}
.mobile-panel.is-open {
  opacity: 1;
  transform: none;
  pointer-events: auto;
}
.mobile-panel a {
  font-size: 22px;
  font-weight: 600;
  padding: 16px 8px;
  border-bottom: var(--border-w) solid var(--border);
}
.mobile-panel .nav-cta {
  margin-top: 16px;
  width: 100%;
}

/* ——— Shared ——— */
.wrap {
  width: min(100% - 48px, var(--max));
  margin: 0 auto;
}
.section {
  padding: clamp(96px, 13vw, 150px) 0;
}
/* These sections open with a small label + content, so the full section
   top-padding read as a dead gap — trimmed to roughly half. */
.listen,
.tour,
.merch {
  padding-top: clamp(48px, 7vw, 88px);
}
.section-label {
  display: inline-flex;
  align-items: center;
  gap: 12px;
  font-family: ui-monospace, "SF Mono", Menlo, Consolas, monospace;
  font-size: 11px;
  font-weight: 600;
  letter-spacing: 0.18em;
  text-transform: uppercase;
  color: var(--muted);
  margin-bottom: 20px;
}
.section-label::before {
  content: "";
  width: 32px;
  height: 1px;
  background: var(--accent);
  flex-shrink: 0;
}
/* Main section titles are set in Strippy — the album-cover face now carries the
   titles as well as the name. Strippy is a heavy condensed caps face, so it needs
   its own metrics: uppercase, tighter leading, and slightly open tracking rather
   than the -0.03em a grotesque wants. Wider measure too, since condensed caps fit
   far more characters per line. */
.section-title {
  font-family: var(--font-title);
  font-size: clamp(32px, 5vw, 52px);
  font-weight: 400;
  line-height: 0.94;
  letter-spacing: 0.005em;
  text-transform: uppercase;
  max-width: 22ch;
}
.section-lead {
  margin-top: 16px;
  font-size: clamp(16px, 1.6vw, 18px);
  color: var(--muted);
  max-width: 48ch;
  line-height: 1.65;
}
.btn {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  gap: 10px;
  min-height: 52px;
  padding: 0 30px;
  border-radius: var(--radius-pill);
  font-size: 13px;
  font-weight: 700;
  letter-spacing: 0.08em;
  text-transform: uppercase;
  transition: transform 0.3s var(--ease), box-shadow 0.3s var(--ease), background 0.25s, color 0.25s, border-color 0.25s;
}
.btn:hover { transform: translateY(-3px); }
.btn:active { transform: translateY(0); }
/* Primary: filled dark pill on the light canvas */
/* Filled buttons carry Kelly Blue (owner's call, 2026-07-29): the brand colour
   now means "this is an action". White on #0872c0 is 5.02:1 — passes AA at the
   13px uppercase label size. Secondary/ghost buttons stay ink-on-transparent, so
   the hierarchy still reads: filled = do this, outlined = optional. */
.btn-primary {
  background: var(--accent);
  color: #ffffff;
}
.btn-primary:hover {
  background: color-mix(in srgb, var(--accent) 84%, #000000);
}
/* Dark photo/video surfaces get the same blue rather than the old white flip —
   one action colour everywhere. It stays loud against the scrimmed photography. */
.hero .btn-primary,
.live-band .btn-primary,
.cta-card .btn-primary {
  background: var(--accent);
  color: #ffffff;
}
.hero .btn-primary:hover,
.live-band .btn-primary:hover,
.cta-card .btn-primary:hover {
  background: color-mix(in srgb, var(--accent) 84%, #000000);
}
/* Secondary: ghost pill, hairline cream border */
.btn-secondary {
  background: transparent;
  color: var(--fg);
  border: 1px solid color-mix(in srgb, var(--fg) 45%, transparent);
}
.btn-secondary:hover {
  border-color: var(--fg);
  background: color-mix(in srgb, var(--fg) 8%, transparent);
}
.btn-ghost {
  background: transparent;
  color: var(--fg);
  border: var(--border-w) solid color-mix(in srgb, var(--fg) 45%, transparent);
}
.btn-ghost:hover {
  background: var(--surface);
  color: #000000;
  border-color: var(--surface);
  box-shadow: var(--shadow);
}
.btn-link {
  color: var(--accent-text);
  font-weight: 600;
  font-size: 14px;
  letter-spacing: 0.02em;
  display: inline-flex;
  align-items: center;
  gap: 8px;
  min-height: 44px;
}
.btn-link:hover { text-decoration: underline; text-underline-offset: 4px; }
.btn svg { width: 16px; height: 16px; flex-shrink: 0; }

/* Unhurried, opacity-led (the reference sites' shared cadence): long fade,
   short travel, plain ease. Nothing snaps or bounces. */
.reveal {
  opacity: 0;
  transform: translateY(14px);
  transition: opacity 1.2s ease, transform 1.2s ease;
}
.reveal.is-in { opacity: 1; transform: none; }
.reveal-d1 { transition-delay: 0.15s; }
.reveal-d2 { transition-delay: 0.3s; }
.reveal-d3 { transition-delay: 0.45s; }

/* Luxury hover micro-interactions */
.section-title {
  transition: letter-spacing 0.4s var(--ease);
}

/* ——— Hero ——— */
:focus-visible {
  outline: 2px solid var(--accent);
  outline-offset: 3px;
}
.btn:focus-visible,
.nav-cta:focus-visible,
.platform:focus-visible,
.track:focus-visible {
  outline-offset: 3px;
}

/* ——— Hero ——— */
.hero {
  position: relative;
  min-height: 100vh;
  min-height: 100dvh;
  display: grid;
  align-items: end;
  padding: calc(var(--nav-h) + 40px) 0 72px;
  overflow: hidden;
  /* Literal colors, not theme tokens: this text sits on a photograph under a
     dark scrim, so it must stay white in any theme. The old var(--bg) here
     turned the name near-black when the site went dark. */
  background: #101010;
  color: #ffffff;
}
.hero-media {
  position: absolute;
  inset: 0;
  z-index: 0;
}
.hero-media img {
  width: 100%;
  height: 100%;
  object-fit: cover;
  /* Payphone shot: subject center-right, CONFESSIONAL sign across the top.
     60% x keeps him in frame at every width; 35% y keeps the sign readable
     on wide screens where the square source crops top and bottom. */
  object-position: 60% 35%;
  /* No grayscale filter — the image file decides; forcing it here would
     silently desaturate any color photo swapped in. */
  filter: contrast(1.06) brightness(0.94);
  transform: scale(1.05);
  animation: hero-zoom 20s var(--ease) forwards;
}
@keyframes hero-zoom {
  to { transform: scale(1); }
}
.hero-scrim {
  position: absolute;
  inset: 0;
  /* Left-and-bottom weighted: holds the name legible over the dark wood
     without dimming the sign or the subject. The old nav-protection band at
     the top is gone — the pill nav carries its own surface. */
  background:
    linear-gradient(to top, color-mix(in srgb, #000000 72%, transparent) 0%, color-mix(in srgb, #000000 24%, transparent) 36%, transparent 66%),
    linear-gradient(to right, color-mix(in srgb, #000000 35%, transparent) 0%, transparent 46%);
  z-index: 1;
}
.hero-content {
  position: relative;
  z-index: 2;
  width: min(100% - 48px, var(--max));
  margin: 0 auto;
  display: grid;
  gap: 18px;
  max-width: 680px;
  margin-left: max(24px, calc((100% - var(--max)) / 2));
}
/* The name is set in Strippy, stacked in caps, echoing the album cover.
   Everything else in the hero stays small so the name and the photo carry it. */
.hero h1 {
  font-family: var(--font-name);
}
.hero h1 .hero-name {
  display: block;
  /* Bounded so it reads big on desktop but never overflows a phone: the min
     (44px) fits a ~360px screen, the max (104px) sits inside the content box. */
  /* +20% across the whole landing group (owner's call 2026-07-29): 44/10/104
     became 53/12/125. KELLY's flex width is set in em, so it scales with this. */
  font-size: clamp(53px, 12vw, 125px);
  /* Album-cover lockup, per Illustrator spec: Strippy 450pt / 350 leading
     ≈ line-height 0.78 — the two words sit tight, nearly touching. */
  line-height: 0.78;
  text-transform: uppercase;
  /* Strippy ships one alternate: a straight-legged Y (Y.ss01). That's the Y
     used on the Here's The Thing cover — the default Y has a curved tail. */
  font-feature-settings: "ss01" 1;
  font-variant-alternates: styleset(ss01);
}
.hero h1 .hero-name > span {
  display: block;
}
/* STEVE — tracking -3 (≈ -0.003em) */
.hero h1 .hn-l1 { letter-spacing: -0.003em; }
/* KELLY, rebuilt 2026-07-30 from DIRECT MEASUREMENT of the actual album cover
   (assets/img/image-1.png) — stop guessing/re-tuning by feel; this replaces
   every earlier pass. Measured via pixel analysis (blue-text mask, border
   excluded, line split on the row gap between STEVE/KELLY): on the cover,
   STEVE is 442px wide and KELLY is 450px wide — KELLY is not width-matched to
   STEVE at all, it is deliberately ~1.8% WIDER. On the actual cover this
   overhangs symmetrically on both sides (K left, Y right), but the owner
   explicitly chose to deviate from the source art here: K is anchored flush
   with S's left edge instead (margin-left: 0, not the small negative offset
   the cover measurement would imply) because the slight left-overhang read
   as misaligned on screen, not as an intentional design nuance. Y keeps its
   own overhang on the right — only K's anchor changed. Within KELLY, the
   letter-column gaps are K-E = E-L = L-L = 4px each (i.e. the SAME "4px" unit
   the cover's K/Y overhangs used — one consistent tracking value) and
   L-Y = 0px: the L and Y physically TOUCH on the cover. All percentages
   below are that single measured unit (4/442 = 0.905% of STEVE's width)
   expressed against STEVE's 2.479em reference width — see
   [[stevekelly-io-rebuild]] memory for the Python measurement if this ever
   needs re-deriving. */
.hero h1 .hero-name > .hn-l2 {
  display: flex;
  width: 2.5239em; /* STEVE's width x 1.0181 — KELLY really is wider */
  letter-spacing: normal;
  /* No margin-left here (deliberately, see comment above): K sits flush
     under S rather than overhanging its left edge, an explicit owner
     override of the pure cover-measurement model. */
}
/* Owner's tweaks on top of the measured model above:
   1) E and the first L were nudged right TOGETHER by ~1.5% of KELLY's width
      (1% first, +0.5% more after) — hn-e absorbs that shift (which cascades
      to every letter after it, since flex margins are cumulative), and
      hn-l1st is left untouched so its own gap to E doesn't change — E and L1
      move as one rigid pair.
   2) The first L is set to a net +2% of KELLY's width (2.25%, dialed back
      0.25%), the second L to a net +2.75% (settled through several rounds:
      4% → 3.75% → 3.25% → 3% → 2.75%) — both dialed back from an initial
      4%/8% pass that overcorrected.
   3) Y is no longer anchored to the cover-measured overhang: to match K's
      flush-left override (see above), Y is now pinned FLUSH with STEVE's
      own right edge too — zero overhang on both ends of the lockup, an
      explicit deviation from the source art on both sides now, not just
      one. Its margin is derived by measuring the rendered gap directly
      (isolated single DOM measurement, not recomputed by formula alone —
      the formula model had ~0.008em of accumulated rounding drift across
      several edits) rather than by canceling the L1st/L2nd nudges. EVERY
      time hn-l1st OR hn-l2nd changes, hn-y must absorb the exact opposite
      delta for EACH (flex margins cascade left-to-right, so both letters'
      changes reach Y too, independently) — that's how Y has stayed flush
      across every one of these L tweaks. If this is ever tweaked again,
      re-measure Y's rendered right edge against STEVE's right edge
      directly and adjust hn-y to zero the gap; do not assume the old
      "cancel P1+P2" formula still holds. */
.hero h1 .hn-e     { margin-left: 0.0603em; }  /* K -> E: measured unit + 1.5% (moves with L1) */
.hero h1 .hn-l1st  { margin-left: 0.0729em; }  /* E -> L: measured unit + 2% net, minus the 1.5% E+L1 passed through */
.hero h1 .hn-l2nd  { margin-left: 0.0476em; }  /* L -> L: measured unit + 2.5% net, minus the 1.5% E+L1 passed through */
.hero h1 .hn-y     { margin-left: -0.1667em; } /* pins Y flush with STEVE's right edge (measured, see comment above) */
.hero-kicker {
  display: block;
  /* h1 carries Strippy for the name; the kicker sits inside it, so reset to
     the body face explicitly or it inherits the display font too. */
  font-family: var(--font);
  font-weight: 500;
  font-size: 11px;
  letter-spacing: 0.24em;
  text-transform: uppercase;
  margin-bottom: 14px;
  color: color-mix(in srgb, #ffffff 66%, transparent);
}
.hero-sub {
  /* +20% with the rest of the landing group: 15/1.4/17 -> 18/1.7/20. */
  font-size: clamp(18px, 1.7vw, 20px);
  line-height: 1.55;
  color: color-mix(in srgb, #ffffff 78%, transparent);
  max-width: 42ch;
  font-weight: 400;
}
/* Quiet secondary action — a boxed second button competes with the photo */
/* Landing group runs 20% larger than the site default — the button, its label and
   the text link all scale together so the cluster keeps its proportions. */
.hero-actions { gap: 14px; }
.hero-actions .btn {
  min-height: 62px;
  padding: 0 36px;
  font-size: 16px;
}
.hero-actions .btn svg { width: 19px; height: 19px; }
.hero-actions .hero-textlink { font-size: 17px; }

.hero-textlink {
  font-size: 14px;
  font-weight: 600;
  color: color-mix(in srgb, #ffffff 82%, transparent);
  padding-bottom: 2px;
  border-bottom: 1px solid color-mix(in srgb, #ffffff 40%, transparent);
  transition: color 0.2s var(--ease), border-color 0.2s var(--ease);
}
.hero-textlink:hover {
  color: #ffffff;
  border-color: #ffffff;
}
.hero-actions {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: 12px;
  margin-top: 4px;
}
.hero-scroll {
  position: absolute;
  right: max(24px, calc((100% - var(--max)) / 2));
  bottom: 28px;
  z-index: 2;
  display: flex;
  align-items: center;
  gap: 10px;
  font-size: 11px;
  font-weight: 700;
  letter-spacing: 0.16em;
  text-transform: uppercase;
  color: color-mix(in srgb, #ffffff 55%, transparent);
}
.hero-scroll-line {
  width: 1px;
  height: 40px;
  background: linear-gradient(to bottom, color-mix(in srgb, #ffffff 50%, transparent), transparent);
  animation: scroll-line 1.8s ease-in-out infinite;
}
@keyframes scroll-line {
  0%, 100% { opacity: 0.35; transform: scaleY(0.7); transform-origin: top; }
  50% { opacity: 1; transform: scaleY(1); transform-origin: top; }
}

/* ——— Listen ——— */
.listen {
  background: var(--surface);
  /* The generic .section rule gives every section the same top+bottom
     padding, but .listen's TOP is already trimmed to this same "half"
     clamp() a few rules up (.listen, .tour, .merch). Its bottom was left at
     the full clamp(96px,13vw,150px) — nearly double the ~14-24px gap
     .album-preview has above it — so the section read tight above "Preview
     the singles" and had a wall of dead air below it before the ticker.
     Matching the bottom to the same reduced value the top already uses. */
  padding-bottom: clamp(48px, 7vw, 88px);
}
.listen-head {
  display: flex;
  flex-wrap: wrap;
  align-items: end;
  justify-content: space-between;
  gap: 24px;
  margin-bottom: 48px;
}
.track-list {
  border-top: var(--border-w) solid var(--border);
}
.track {
  display: grid;
  grid-template-columns: 48px 1fr auto;
  align-items: center;
  gap: 16px 20px;
  padding: 20px 12px;
  border-bottom: var(--border-w) solid var(--border);
  transition: background 0.25s var(--ease);
  cursor: pointer;
  border-radius: 0;
}
.track:hover, .track.is-active {
  background: var(--surface);
}
.track-num {
  font-size: 13px;
  font-weight: 600;
  color: var(--muted);
  text-align: center;
  font-variant-numeric: tabular-nums;
}
.track.is-active .track-num { color: var(--accent-text); }
.track-play {
  width: 44px;
  height: 44px;
  border-radius: 50%;
  border: 2px solid var(--fg);
  display: grid;
  place-items: center;
  background: var(--bg);
  transition: background 0.25s var(--ease), border-color 0.25s, color 0.25s, transform 0.25s var(--ease);
}
.track:hover .track-play,
.track.is-active .track-play {
  background: var(--fg);
  border-color: var(--fg);
  color: var(--bg);
  transform: scale(1.08);
}
.track-play svg { width: 14px; height: 14px; margin-left: 1px; }
.track-info h3 {
  font-size: 17px;
  font-weight: 600;
  letter-spacing: -0.01em;
}
.track-info p {
  font-size: 13px;
  color: var(--muted);
  margin-top: 2px;
}
.track-meta {
  display: flex;
  align-items: center;
  gap: 16px;
  font-size: 13px;
  color: var(--muted);
  font-variant-numeric: tabular-nums;
}
.eq {
  display: none;
  align-items: end;
  gap: 2px;
  height: 14px;
}
.track.is-active .eq { display: flex; }
.eq span {
  width: 3px;
  background: var(--accent);
  border-radius: 1px;
  animation: eq 0.9s ease-in-out infinite alternate;
}
.eq span:nth-child(1) { height: 40%; animation-delay: 0s; }
.eq span:nth-child(2) { height: 90%; animation-delay: 0.15s; }
.eq span:nth-child(3) { height: 55%; animation-delay: 0.3s; }
.eq span:nth-child(4) { height: 75%; animation-delay: 0.1s; }
@keyframes eq {
  from { transform: scaleY(0.4); }
  to { transform: scaleY(1); }
}
.platforms {
  display: flex;
  flex-wrap: wrap;
  gap: 10px;
  margin-top: 40px;
}
.platform {
  min-height: 44px;
  padding: 0 18px;
  border: var(--border-w) solid var(--border);
  border-radius: var(--radius);
  font-size: 13px;
  font-weight: 600;
  display: inline-flex;
  align-items: center;
  background: var(--bg);
  transition: border-color 0.2s, background 0.2s, transform 0.2s var(--ease);
}
.platform:hover {
  border-color: var(--fg);
  background: var(--surface);
  transform: translateY(-1px);
}

/* ——— Album ——— */
/* Album folded into the Listen section: a hairline surface-step between the
   track list and the record, rather than a full section boundary. */
/* The 3-song audio preview, under the album. Matches .listen's own (reduced)
   bottom padding below the ticker, so the section reads evenly balanced top
   and bottom instead of tight-above/heavy-below (was clamp(14px,1.8vw,24px)
   — nowhere near enough breathing room against the section's own padding). */
.album-preview {
  margin-top: clamp(48px, 7vw, 88px);
}
.album-grid {
  display: grid;
  /* Copy column runs wider than the cover so the blurb sets without orphaning
     its last word, and stretches to the cover's full height so the credits and
     the order button can sit flush with the bottom of the artwork. */
  grid-template-columns: 0.86fr 1.3fr;
  gap: clamp(32px, 5vw, 64px);
  align-items: stretch;
}
.album-copy {
  display: flex;
  flex-direction: column;
}
/* Pushes the credits + button block down so it bottoms out with the cover. */
.album-copy .credit-list { margin-top: auto; }
.album-art {
  position: relative;
  aspect-ratio: 1;
  border-radius: var(--radius);
  overflow: hidden;
  box-shadow: var(--shadow);
  background: var(--accent);
}
.album-art img {
  width: 100%;
  height: 100%;
  object-fit: cover;
}
/* The old hover-zoom (transform:scale on :hover img) was superseded by the
   vinyl disc-reveal (enhance.js) and left in place — it was still firing on
   BOTH the cover art and the disc <img> GSAP animates, fighting the JS
   transform on the exact same element and making the album card's reveal
   feel different from the merch cards' (which never had this rule). Removed
   so the album cover uses the identical disc-reveal motion as the merch
   cards, nothing layered on top. */
.album-art-badge {
  position: absolute;
  top: 20px;
  right: 20px;
  background: var(--accent);
  color: #ffffff;
  font-size: 10px;
  font-weight: 700;
  letter-spacing: 0.14em;
  text-transform: uppercase;
  padding: 10px 14px;
  border-radius: var(--radius);
  box-shadow: var(--shadow);
}
.album-copy .section-title { max-width: none; }
.album-copy p {
  margin-top: 20px;
  color: var(--muted);
  font-size: 16px;
  line-height: 1.7;
  /* Wider measure than the 48ch body default: the blurb then fits its closing
     words on the previous line instead of orphaning one. `pretty` is the
     belt-and-braces — it pulls a lone trailing word up where supported. */
  max-width: 64ch;
  text-wrap: pretty;
}
.credit-list {
  margin-top: 32px;
  display: grid;
  gap: 12px;
}
.credit {
  display: grid;
  grid-template-columns: 120px 1fr;
  gap: 12px;
  padding: 14px 0;
  border-top: var(--border-w) solid var(--border);
  font-size: 14px;
}
.credit:last-child { border-bottom: var(--border-w) solid var(--border); }
/* Keeps a short credit label on one line even if the 120px column is tight. */
.credit dt.credit-dt--nowrap { white-space: nowrap; }
.credit dt {
  color: var(--muted);
  font-weight: 500;
  font-size: 12px;
  letter-spacing: 0.08em;
  text-transform: uppercase;
  padding-top: 2px;
}
.credit dd { font-weight: 600; }
.album-actions {
  display: flex;
  flex-wrap: wrap;
  gap: 12px;
  margin-top: 32px;
}

/* ——— Gallery ——— */
.gallery-grid {
  display: grid;
  grid-template-columns: 1.2fr 1fr 1fr;
  grid-template-rows: auto auto;
  gap: 16px;
  margin-top: 48px;
}
.gallery-item {
  position: relative;
  border-radius: var(--radius);
  overflow: hidden;
  background: var(--surface);
  border: var(--border-w) solid var(--border);
}
.gallery-item:first-child {
  grid-row: 1 / 3;
  min-height: 520px;
}
.gallery-item img {
  width: 100%;
  height: 100%;
  object-fit: cover;
  min-height: 240px;
  transition: transform 0.7s var(--ease);
}
.gallery-item:hover img { transform: scale(1.04); }
.gallery-cap {
  position: absolute;
  left: 16px;
  bottom: 16px;
  padding: 8px 12px;
  background: color-mix(in srgb, #ffffff 92%, transparent);
  backdrop-filter: blur(8px);
  border-radius: var(--radius);
  font-size: 12px;
  font-weight: 600;
  letter-spacing: 0.04em;
  box-shadow: var(--shadow-sm);
}

/* ——— Tour ——— */
.tour { background: var(--surface); border-top: 1px solid var(--border); }
.tour-list {
  margin-top: 48px;
  border-top: var(--border-w) solid var(--border);
}
.show {
  display: grid;
  /* Six columns, each sized to ITS OWN content rather than split evenly.
     Equal 1fr thirds looked wrong with centred text: a track much wider
     than its content leaves big margins, so the visual gaps came out at
     146px between venue and city but 28px between notes and time. Widths
     below are max-glyph + ~40px, measured from the real rendered rows
     (venue 160, city 130, time 78, date 35), so every centred field
     carries a similar ~20px of breathing room on each side and the gaps
     between them land even. Notes takes the remaining space as the one
     flexible track — it's the only field whose length really varies.
     Two constraints that must hold if these are ever retuned:
     1. No `auto` tracks. Each .show is its OWN grid (not a shared
        <table>), so an auto track sizes to THAT row's content and the
        column would land at a different x on every row.
     2. City stays its own column — it used to be a flex pair inside the
        venue cell, which crammed the two together against a big empty
        gap. Don't merge them back. */
  grid-template-columns: 76px 210px 163px 1fr 109px 132px;
  grid-template-areas: "date venue city notes time cta";
  gap: 24px;
  align-items: center;
  padding: 22px 8px;
  border-bottom: var(--border-w) solid var(--border);
  transition: background 0.2s;
}
.show:not(.show--booking):hover { background: color-mix(in srgb, var(--fg) 3%, transparent); }
/* Every cell in a dated row centres its own content (owner's call 2026-08)
   so the row reads as an even table rather than a left-ragged list.
   Scoped off .show--booking, which is a left-aligned CTA block, not a
   table row, and would look broken centred. */
.show:not(.show--booking) > * { text-align: center; }
/* Type scale for the four data fields, deliberately narrow: they were once
   20/20/14/14px at four different weights, which read as four different
   typefaces even though every one is DM Sans. One clear step for the venue
   (the thing you scan for), one shared size for city and notes, and time a
   step below.
     venue 18/700 · city 16/500 · notes 16/500 italic · time 15/600
   ⚠️ Self-hosted DM Sans ships 300/400/600/700/800 and NO italic — there is
   no 500 face and no italic face. Per CSS font matching, `font-weight: 500`
   resolves DOWN to the 400 file (verified: 400 and 500 render at identical
   widths), and `font-style: italic` is SYNTHESISED by the browser as a
   mechanical slant rather than a drawn italic. The 500s are kept because
   they state the intent and would start rendering for real the moment a
   dm-sans-500 file is added to assets/fonts + an @font-face block up top;
   same for a true italic. Don't "fix" the 500s to 400 — and don't assume
   city and notes currently differ in weight, they don't.
   Sizes live only here. Do NOT add font-size back to the per-element
   .show-venue h3 / .show-city / .show-notes / .show-time rules below —
   those come later in the file and would win on source order. */
.show-venue h3 { font-size: 18px; }
.show-city    { font-size: 16px; }
.show-notes   { font-size: 16px; }
.show-time    { font-size: 15px; }
.show-date {
  grid-area: date;
  font-variant-numeric: tabular-nums;
}
.show-date .day {
  font-size: 28px;
  font-weight: 700;
  line-height: 1;
  letter-spacing: -0.03em;
}
.show-date .mon {
  font-size: 12px;
  font-weight: 700;
  letter-spacing: 0.1em;
  text-transform: uppercase;
  color: var(--muted);
  margin-top: 4px;
}
.show-venue { grid-area: venue; }
/* Size lives in the shared .show-venue h3/.show-city/.show-notes/.show-time
   rule above — deliberately not repeated here, or it would win on source
   order and re-break the matched sizing. */
.show-venue h3 {
  font-weight: 700;
  letter-spacing: -0.01em;
}
/* City is its own grid column, sized the same as the venue name (owner's
   call 2026-08) rather than stacked under it as a small caption. Hierarchy
   is carried by weight and color — venue bold ink, city medium muted — so
   the two read as distinct even at matching size. It was briefly a flex
   pair *inside* the venue cell with a "·" separator; that crammed the two
   against each other while the rest of the row sat far right, so it's a
   real column now and the separator is gone with it. */
.show-city {
  grid-area: city;
  font-weight: 500;
  color: var(--muted);
}
/* Booking Inquiries row still uses a plain stacked <p> subtitle under its
   h3 (see .show--booking below) — unrelated to .show-city above. */
.show-venue p {
  font-size: 13px;
  color: var(--muted);
  margin-top: 3px;
}
/* Optional per-show notes/description column (owner's call 2026-08) — left
   empty for rows with nothing to say, which is a valid, blank grid cell,
   not a layout break. */
.show-notes {
  grid-area: notes;
  font-weight: 500;
  /* Italic is the field's real differentiator here — with no 500 face
     shipped, the weight alone renders the same as city's. See the
     type-scale block above. */
  font-style: italic;
  color: var(--muted);
  line-height: 1.4;
  /* Notes is the one track that fills its column (the text wraps to it),
     so without this it would butt right up against the gap while every
     other centred field keeps ~20px of margin — making the two gaps
     either side of notes read tighter than the rest. */
  padding-inline: 20px;
}
/* Emphasis markers from the sheet's Notes column (*bold* / _italic_ —
   see emphasize() in tour.js). 700 because it's a real shipped DM Sans
   face, so it reads as a genuine step up from the base — which is 500,
   and 500 currently resolves to the 400 file (see the type-scale note
   above). */
.show-notes strong { font-weight: 700; }
/* Notes are already italic, so an italic-within-italic would be invisible.
   Flipping to upright is the standard typographic answer and makes the
   emphasis actually read. */
.show-notes em { font-style: normal; }
.show-time {
  grid-area: time;
  font-weight: 600;
  color: var(--muted);
  font-variant-numeric: tabular-nums;
  white-space: nowrap;
}
.show-status {
  grid-area: cta;
  font-size: 12px;
  font-weight: 700;
  letter-spacing: 0.08em;
  text-transform: uppercase;
  color: var(--muted);
  padding: 8px 12px;
  border: var(--border-w) solid var(--border);
  border-radius: var(--radius);
  white-space: nowrap;
}
/* Fixed pill width, centred in a slightly wider track: as a grid item the
   pill would otherwise stretch edge to edge, leaving it no breathing room
   while every other field has ~20px. Fixed (not shrink-to-fit) so the
   "Tickets" and "Played" pills still share an edge across rows.
   Scoped off .show--booking, whose Inquire button is a deliberately
   larger filled CTA and must keep its own size. */
.show:not(.show--booking) .show-status {
  width: 92px;
  justify-self: center;
}
.show-status.open {
  color: var(--accent-text);
  border-color: var(--accent-text);
}
.show-status.open:hover {
  background: var(--accent);
  color: #ffffff;
}
/* Recent past shows (owner's call 2026-08: show a short "where we've been"
   trail, not just upcoming dates) — the whole row recedes a step, and the
   "Tickets" pill becomes a plain "Played" label: no border/hover, since a
   button-shaped box implies an action, and there's no action left to take
   on a show that already happened. */
.show--past { opacity: 0.6; }
.show-status--past {
  border-color: transparent;
  padding: 8px 0;
}
/* Booking Inquiries — a CTA block, not a dated show: no date/time columns,
   softly tinted, with a filled button so it reads apart from the tour rows. */
.show--booking {
  grid-template-columns: 1fr auto;
  grid-template-areas: "venue cta";
  background: color-mix(in srgb, var(--fg) 4%, transparent);
  border-bottom: none;
  border-radius: var(--radius);
  margin-top: 20px;
  padding: 22px 20px;
}
.show--booking .show-venue h3 { font-size: 17px; }
/* Filled, so it follows the same Kelly Blue action rule as .btn-primary.
   (.show-status.open — the Tickets pill — is an outlined button and stays put.) */
.show-status--book {
  color: #ffffff;
  background: var(--accent);
  border-color: var(--accent);
  /* Larger than the base .show-status pill (owner's call 2026-08) — this is
     the one filled action in the tour list, it should read as more important
     than the outlined "Tickets" pills, not the same size. */
  font-size: 13px;
  padding: 14px 22px;
}
.show-status--book:hover {
  background: color-mix(in srgb, var(--accent) 84%, #000000);
  border-color: color-mix(in srgb, var(--accent) 84%, #000000);
}
.tour-note {
  margin-top: 28px;
  font-size: 14px;
  color: var(--muted);
  max-width: 56ch;
}

/* ——— Trust strip ——— */
/* ——— Collaborator ticker: an infinite marquee of the cast. Two identical
   groups sit side by side; translateX(-50%) advances exactly one group for a
   seamless loop, so BOTH groups must always list the same names. ——— */
.ticker-band {
  background: var(--surface);
  border-top: var(--border-w) solid var(--border);
  border-bottom: var(--border-w) solid var(--border);
  padding: 28px 0;
  text-align: center;
  overflow: hidden;
}
.ticker-eyebrow {
  font-size: 10px;
  font-weight: 600;
  letter-spacing: 0.2em;
  text-transform: uppercase;
  color: var(--muted);
  margin-bottom: 14px;
}
.ticker-mask {
  max-width: 900px;
  margin: 0 auto;
  overflow: hidden;
  -webkit-mask-image: linear-gradient(to right, transparent 0, #000 56px, #000 calc(100% - 56px), transparent 100%);
  mask-image: linear-gradient(to right, transparent 0, #000 56px, #000 calc(100% - 56px), transparent 100%);
}
.ticker-track {
  display: flex;
  width: max-content;
  font-size: clamp(13px, 1.4vw, 15px);
  font-weight: 700;
  letter-spacing: 0.04em;
  color: var(--accent-text);
  white-space: nowrap;
  animation: ticker-scroll 34s linear infinite;
}
.ticker-group {
  display: flex;
  gap: 44px;
  padding-right: 44px;
}
@keyframes ticker-scroll {
  from { transform: translateX(0); }
  to { transform: translateX(-50%); }
}
@media (prefers-reduced-motion: reduce) {
  /* No marquee for reduced-motion visitors: show one static, wrapped list. */
  .ticker-track { animation: none; width: 100%; flex-wrap: wrap; justify-content: center; white-space: normal; }
  .ticker-group { flex-wrap: wrap; justify-content: center; padding-right: 0; gap: 12px 44px; }
  .ticker-group[aria-hidden="true"] { display: none; }
}

.trust {
  background: var(--surface);
  color: var(--fg);
  border-top: 1px solid var(--border);
  border-bottom: 1px solid var(--border);
  padding: 24px 0;
}
.trust-row {
  display: grid;
  grid-template-columns: auto 1fr;
  gap: 40px;
  align-items: center;
}
.trust-label {
  font-size: 10px;
  font-weight: 600;
  letter-spacing: 0.2em;
  text-transform: uppercase;
  color: var(--muted);
  white-space: nowrap;
}
.trust-names {
  display: flex;
  flex-wrap: wrap;
  gap: 8px 32px;
  font-size: clamp(13px, 1.4vw, 15px);
  font-weight: 700;
  letter-spacing: 0.04em;
  color: var(--accent-text);
}
.trust-names span {
  position: relative;
}
.trust-names span:not(:last-child)::after {
  content: "";
  position: absolute;
  right: -18px;
  top: 50%;
  width: 4px;
  height: 4px;
  border-radius: 50%;
  background: var(--accent);
  transform: translateY(-50%);
}

/* ——— Bio ——— */
.bio {
  background: var(--surface);
  color: var(--fg);
  /* Shop and Bio are both white in the new section order — a hairline keeps
     them from reading as one block, the site's sanctioned separator. */
  border-top: 1px solid var(--border);
}
.bio .section-label { color: var(--muted); }
.bio .section-label::before { background: var(--accent); }
/* This headline is a full sentence, not a 2-word title, so the 14ch measure it
   used to share with short titles stacked it into a tall ragged column against
   two-thirds empty page. A ~28ch measure sets it in three balanced lines that
   hold the width; `balance` evens the rag. Left-aligned on purpose — every
   other section label, title and lead on the site is left-aligned, and
   centering this one alone would break that spine. */
.bio .section-title {
  max-width: 28ch;
  text-wrap: balance;
}
.bio-grid {
  display: grid;
  grid-template-columns: 0.92fr 1.08fr;
  gap: clamp(32px, 6vw, 80px);
  align-items: start;
  margin-top: 40px;
}
.bio-photo-stack {
  display: grid;
  gap: 16px;
  position: sticky;
  top: 96px;
}
.bio-photo {
  border-radius: var(--radius);
  overflow: hidden;
  aspect-ratio: 4/5;
  background: var(--surface);
  border: var(--border-w) solid var(--border);
}
.bio-photo img {
  width: 100%;
  height: 100%;
  object-fit: cover;
}
.bio-photo-secondary {
  aspect-ratio: 16/10;
}
.bio-stats {
  display: grid;
  grid-template-columns: 1fr 1fr;
  gap: 12px;
}
.bio-stat {
  padding: 16px;
  border: var(--border-w) solid var(--border);
  border-radius: var(--radius);
  background: var(--bg);
}
.bio-stat strong {
  display: block;
  font-size: 13px;
  font-weight: 700;
  letter-spacing: 0.04em;
  text-transform: uppercase;
  margin-bottom: 4px;
}
.bio-stat span {
  font-size: 13px;
  color: var(--muted);
  line-height: 1.4;
}
.bio-lead {
  font-size: clamp(20px, 2.4vw, 26px) !important;
  line-height: 1.45 !important;
  color: var(--fg) !important;
  font-weight: 500;
  letter-spacing: -0.02em;
  margin-bottom: 28px !important;
}
.bio-text p {
  font-size: clamp(15px, 1.5vw, 17px);
  line-height: 1.75;
  color: var(--muted);
  margin-bottom: 18px;
}
.bio-text p strong { color: var(--fg); font-weight: 700; }
.bio-actions {
  display: flex;
  flex-wrap: wrap;
  gap: 12px;
  margin-top: 28px;
}
/* .bio-quote / .bio-quote p removed 2026-07-29 — dead CSS, referenced by no
   markup. The real pull-quote on /bio/ is .pull-quote, which is a blockquote
   rule (a vertical stroke beside a quotation), not a tinted card. */

/* ——— Merch ——— */
.merch { background: var(--surface); border-top: 1px solid var(--border); }
.merch-grid {
  display: grid;
  grid-template-columns: repeat(3, 1fr);
  gap: 20px;
  margin-top: 48px;
}
/* A trailing row with fewer than 3 items sits in its own centered flex row
   instead of inside the grid above, which would pack them flush-left and
   leave an unbalanced empty gap on the right rather than even space on both
   sides. Card width matches the grid's own column formula exactly (see the
   matching breakpoints below), so cards here are pixel-identical in size to
   the ones in the row above. */
.merch-grid-row2 {
  display: flex;
  justify-content: center;
  gap: 20px;
  margin-top: 20px;
}
.merch-grid-row2 .merch-card {
  flex: 0 0 calc((100% - 40px) / 3);
}
.merch-card {
  background: var(--bg);
  border: var(--border-w) solid var(--border);
  border-radius: var(--radius);
  overflow: hidden;
  transition: transform 0.35s var(--ease), box-shadow 0.35s var(--ease);
}
.merch-card:hover {
  transform: translateY(-4px);
  box-shadow: var(--shadow);
}
.merch-img {
  position: relative;
  aspect-ratio: 1;
  background: var(--surface);
  overflow: hidden;
}
.merch-img img {
  width: 100%;
  height: 100%;
  object-fit: cover;
}
/* Merch-card disc reveal is handled by the enhancement layer
   (assets/css/enhance.css + assets/js/enhance.js) using the real record/CD
   images — the earlier generated-disc + cover-slide version was removed. */
.merch-body { padding: 20px; }
.merch-body h3 {
  font-size: 16px;
  font-weight: 700;
  letter-spacing: -0.01em;
}
.merch-body p {
  font-size: 13px;
  color: var(--muted);
  margin-top: 6px;
}
.merch-foot {
  display: flex;
  align-items: center;
  justify-content: space-between;
  margin-top: 16px;
  padding-top: 16px;
  border-top: var(--border-w) solid var(--border);
}
.merch-price {
  font-weight: 700;
  font-size: 15px;
  font-variant-numeric: tabular-nums;
}
.merch-cta {
  font-size: 12px;
  font-weight: 700;
  letter-spacing: 0.08em;
  text-transform: uppercase;
  color: var(--accent-text);
}
.merch-card:hover .merch-cta { color: var(--accent-text); }

/* ——— Press / proof ——— */
.press {
  background: var(--bg);
  border-top: var(--border-w) solid var(--border);
}
.press-grid {
  display: grid;
  grid-template-columns: 1fr 1.4fr;
  gap: clamp(32px, 5vw, 64px);
  align-items: start;
  margin-top: 40px;
}
.press-quote {
  font-size: clamp(22px, 3vw, 32px);
  font-weight: 500;
  line-height: 1.35;
  letter-spacing: -0.025em;
  max-width: 18ch;
}
.press-quote cite {
  display: block;
  margin-top: 20px;
  font-size: 12px;
  font-weight: 700;
  font-style: normal;
  letter-spacing: 0.12em;
  text-transform: uppercase;
  color: var(--muted);
}
.press-points {
  display: grid;
  gap: 0;
  border-top: var(--border-w) solid var(--border);
}
.press-point {
  display: grid;
  grid-template-columns: 48px 1fr;
  gap: 16px;
  padding: 22px 0;
  border-bottom: var(--border-w) solid var(--border);
}
.press-num {
  font-size: 13px;
  font-weight: 700;
  color: var(--accent-text);
  font-variant-numeric: tabular-nums;
  padding-top: 2px;
}
.press-point h3 {
  font-size: 17px;
  font-weight: 700;
  letter-spacing: -0.015em;
  margin-bottom: 6px;
}
.press-point p {
  font-size: 14px;
  color: var(--muted);
  line-height: 1.55;
  max-width: 48ch;
}

/* ——— Contact / CTA ——— */
.cta-band {
  background: var(--surface);
  padding: clamp(80px, 12vw, 140px) 0;
}
.cta-card {
  /* Deliberately dark on the light canvas: the closing "ink moment" that
     bookends the dark hero and echoes the nav pill. Internals are authored
     for dark, so this block must not follow the theme tokens. */
  background: var(--ink);
  color: #f5efe2;
  border: 1px solid color-mix(in srgb, #ffffff 10%, transparent);
  border-radius: var(--radius);
  padding: clamp(48px, 7vw, 80px);
  display: grid;
  grid-template-columns: 1.15fr 1fr;
  gap: 48px;
  align-items: center;
  position: relative;
  overflow: hidden;
  box-shadow: var(--shadow-lg);
}
/* A decorative radial-gradient glow used to sit behind the logo column here
   (right:-10%, top:-30%, width:50%, height:160%) — a leftover from before
   the real Three.js particle mark existed. Its rectangular bounding box
   didn't fully fade to transparent before hitting its own edges, so it read
   as a visible container boundary behind the sphere rather than atmosphere.
   Removed rather than resized: the site's Flat Rule already treats
   decorative gradients as an anti-pattern everywhere else, and the mark
   itself is the visual interest here — it doesn't need help. */
.cta-card .section-label { color: color-mix(in srgb, #ffffff 55%, transparent); }
.cta-card h2 {
  font-size: clamp(28px, 4vw, 44px);
  font-weight: 700;
  letter-spacing: -0.03em;
  line-height: 1.1;
  max-width: 14ch;
  position: relative;
  z-index: 1;
}
.cta-card p {
  margin-top: 16px;
  color: color-mix(in srgb, #ffffff 70%, transparent);
  max-width: 40ch;
  position: relative;
  z-index: 1;
}
.cta-form {
  display: grid;
  gap: 12px;
  position: relative;
  z-index: 1;
}
/* SK mark above the signup, centred over the form column so it reads as a
   stamp closing the page out. enhance.js spins it into place on scroll. */
/* The wrap carries sizing + centring now that the logo has the halo/core sitting
   behind it. .cta-form is a grid, so centring is justify-self — auto margins
   resolve to 0 against the default stretch and would leave the mark flush left. */
.cta-logo-wrap {
  position: relative;
  display: grid;
  place-items: center;
  /* At least 2x the original mark size (owner's call 2026-07-29): 96/11vw/132 -> 200/22vw/264. */
  width: clamp(200px, 22vw, 264px);
  justify-self: center;
  margin-bottom: 20px;
}
.cta-logo {
  position: relative;
  z-index: 2;
  width: 100%;
  height: auto;
  display: block;
}
.cta-form label {
  font-size: 12px;
  font-weight: 700;
  letter-spacing: 0.1em;
  text-transform: uppercase;
  color: color-mix(in srgb, #ffffff 55%, transparent);
}
.cta-form input,
.cta-form textarea {
  width: 100%;
  min-height: 48px;
  padding: 12px 16px;
  border-radius: var(--radius);
  border: var(--border-w) solid color-mix(in srgb, #ffffff 22%, transparent);
  background: color-mix(in srgb, #ffffff 8%, transparent);
  color: var(--bg);
  font-family: inherit;
  font-size: 15px;
  outline: none;
  transition: border-color 0.2s, background 0.2s;
}
.cta-form input::placeholder,
.cta-form textarea::placeholder {
  color: color-mix(in srgb, #ffffff 40%, transparent);
}
.cta-form input:focus,
.cta-form textarea:focus {
  border-color: var(--bg);
  background: color-mix(in srgb, #ffffff 12%, transparent);
}
.cta-form textarea { min-height: 96px; resize: vertical; }
.cta-form .btn-primary { width: 100%; margin-top: 4px; }
.cta-card .form-note {
  font-size: 12px;
  color: color-mix(in srgb, #ffffff 45%, transparent);
  text-align: center;
  /* Two classes here (not just `.form-note`) deliberately outweighs the
     generic `.cta-card p { max-width: 40ch }` rule below in specificity —
     a plain `.form-note` tied on specificity with `.cta-card p` and lost by
     source order, which was capping this to ~240px and left-aligning it in
     the grid column. text-align:center was only ever centering within that
     narrow box, not under the full-width button above it. */
  max-width: none;
}
.form-success {
  display: none;
  padding: 24px;
  border: var(--border-w) solid color-mix(in srgb, #ffffff 22%, transparent);
  border-radius: var(--radius);
  text-align: center;
}
/* The right-hand column of the CTA card: SK mark on top, then either the form
   or (after submit) the confirmation message. Grouping them means the mark
   stays put through the swap and the column keeps its visual anchor, instead
   of the message being left floating alone against the right edge. */
.cta-right {
  display: grid;
  align-content: start;
  gap: 12px;
}
.form-success.is-shown { display: block; }
.form-success h3 { font-size: 20px; margin-bottom: 8px; }
/* max-width/margin-inline are the fix for a real bug, not decoration: .cta-card p
   caps every paragraph in this card at 40ch with no auto margins, so the
   confirmation text rendered as a ~290px block jammed against the left edge of a
   ~410px box. text-align:center then centred the lines INSIDE that block, which
   looks centred line-by-line but is visibly off-centre as a whole. Equal
   specificity (0-1-1) means this rule wins on source order — but only for the
   properties it actually names, so max-width has to be restated here.
   34ch keeps a readable measure rather than letting it run the full box. */
.form-success p {
  color: color-mix(in srgb, #ffffff 70%, transparent);
  font-size: 14px;
  max-width: 34ch;
  margin-inline: auto;
}

/* ——— Footer ——— */
.footer {
  border-top: var(--border-w) solid var(--border);
  padding: 48px 0 32px;
  background: var(--bg);
}
.footer-top {
  display: flex;
  flex-wrap: wrap;
  justify-content: space-between;
  gap: 32px;
  padding-bottom: 40px;
  border-bottom: var(--border-w) solid var(--border);
}
.footer-brand .logo { margin-bottom: 12px; }
.footer-brand p {
  font-size: 14px;
  color: var(--muted);
  max-width: 28ch;
}
.footer-cols {
  display: flex;
  flex-wrap: wrap;
  gap: 48px;
}
.footer-col h4 {
  font-size: 11px;
  font-weight: 700;
  letter-spacing: 0.14em;
  text-transform: uppercase;
  color: var(--muted);
  margin-bottom: 16px;
}
.footer-col a {
  display: block;
  font-size: 14px;
  font-weight: 500;
  padding: 6px 0;
  color: var(--muted);
  transition: color 0.2s;
}
.footer-col a:hover { color: var(--fg); }
.footer-bottom {
  display: flex;
  flex-wrap: wrap;
  justify-content: space-between;
  gap: 16px;
  padding-top: 24px;
  font-size: 13px;
  color: var(--muted);
}
.socials {
  display: flex;
  gap: 8px;
}
.socials a {
  width: 40px;
  height: 40px;
  border: var(--border-w) solid var(--border);
  border-radius: var(--radius);
  display: grid;
  place-items: center;
  transition: border-color 0.2s, background 0.2s, color 0.2s;
}
.socials a:hover {
  border-color: var(--fg);
  background: var(--fg);
  color: var(--bg);
}
.socials svg { width: 16px; height: 16px; }

/* ——— Player dock ——— */
.player {
  position: fixed;
  left: 50%;
  bottom: 24px;
  transform: translateX(-50%) translateY(120%);
  width: min(100% - 32px, 520px);
  background: var(--fg);
  color: var(--bg);
  border-radius: var(--radius);
  padding: 14px 16px;
  display: grid;
  grid-template-columns: auto 1fr auto;
  gap: 14px;
  align-items: center;
  box-shadow: var(--shadow);
  z-index: 90;
  transition: transform 0.45s var(--ease);
}
.player.is-open { transform: translateX(-50%) translateY(0); }
.player-btn {
  width: 44px;
  height: 44px;
  border-radius: 50%;
  background: var(--accent);
  color: var(--bg);
  display: grid;
  place-items: center;
}
.player-btn svg { width: 16px; height: 16px; margin-left: 1px; }
.player-info strong {
  display: block;
  font-size: 14px;
  font-weight: 700;
}
.player-info span {
  font-size: 12px;
  color: color-mix(in srgb, #ffffff 55%, transparent);
}
.player-close {
  width: 36px;
  height: 36px;
  border-radius: var(--radius);
  color: color-mix(in srgb, #ffffff 70%, transparent);
  font-size: 18px;
  line-height: 1;
}
.player-close:hover { color: var(--bg); background: color-mix(in srgb, #ffffff 10%, transparent); }
.player-bar {
  grid-column: 1 / -1;
  height: 3px;
  background: color-mix(in srgb, #ffffff 18%, transparent);
  border-radius: 2px;
  overflow: hidden;
}
.player-bar i {
  display: block;
  height: 100%;
  width: 0%;
  background: var(--accent);
  animation: progress 28s linear infinite;
}
@keyframes progress {
  from { width: 0%; }
  to { width: 100%; }
}

/* ——— Responsive ——— */
/* Tour row, mid widths: notes is the only flexible track, so between the
   mobile breakpoint and the 1200px container cap it absorbs every pixel
   the fixed columns don't use — at ~1000px that left it around 120px and
   the text shredded into a tall narrow stack. Everything else steps down
   a little so notes keeps a workable measure. Must stay above the 960px
   block below, which overrides these for the stacked phone layout. */
@media (max-width: 1200px) {
  .show {
    grid-template-columns: 67px 201px 155px 1fr 101px 124px;
    gap: 18px;
  }
  .show-notes { padding-inline: 12px; }
}
@media (max-width: 960px) {
  .album-grid,
  .bio-grid,
  .cta-card,
  .press-grid,
  .trust-row { grid-template-columns: 1fr; }
  .bio-photo-stack { position: static; }
  .gallery-grid {
    grid-template-columns: 1fr 1fr;
  }
  .gallery-item:first-child {
    grid-column: 1 / -1;
    grid-row: auto;
    min-height: 360px;
  }
  .merch-grid { grid-template-columns: 1fr 1fr; }
  .merch-grid-row2 .merch-card { flex-basis: calc((100% - 20px) / 2); }
  /* Phone: the desktop 6-column row can't hold its width, so the middle
     fields stack vertically in one column beside the date, with the CTA
     pinned right. Rows with no notes simply omit that band. */
  .show {
    grid-template-columns: 56px 1fr auto;
    grid-template-areas: "date venue cta" "date city cta" "date time cta";
    /* Tight row-gap: the desktop 24px is horizontal breathing room between
       columns, but here the same value stacks vertically and blows the row
       height out. */
    gap: 4px 16px;
  }
  /* The desktop side padding exists to keep notes off its column edges;
     in the single stacked column it just eats scarce width. */
  .show-notes { padding-inline: 0; }
  .show:has(.show-notes) {
    grid-template-areas: "date venue cta" "date city cta" "date notes cta" "date time cta";
  }
  /* Same relative scale as desktop, one step down for the narrow column:
     venue still leads, time still sits below notes. */
  .show-venue h3 { font-size: 16px; }
  .show-city    { font-size: 15px; }
  .show-notes   { font-size: 15px; }
  .show-time    { font-size: 14px; }
  .show--booking { grid-template-columns: 1fr auto; grid-template-areas: "venue cta"; }
  .hero-scroll { display: none; }
}
/* Six nav links plus a CTA stop fitting well before 720px — the logo and the
   button both start wrapping around 850px. Hand over to the mobile menu there. */
@media (max-width: 900px) {
  .nav-links, .nav-cta { display: none; }
  .menu-btn { display: flex; }
  .mobile-panel { display: flex; }
}

@media (max-width: 720px) {
  /* Portrait crop of the square source: favor the subject over the sign */
  .hero-media img { object-position: 58% center; }
  .hero-content { margin-left: 24px; margin-right: 24px; width: auto; }
  .merch-grid { grid-template-columns: 1fr; }
  .merch-grid-row2 { flex-direction: column; }
  .merch-grid-row2 .merch-card { flex-basis: auto; }
  .gallery-grid { grid-template-columns: 1fr; }
  .track { grid-template-columns: 40px 1fr; }
  .track-meta { display: none; }
  .credit { grid-template-columns: 1fr; gap: 4px; }
  .footer-cols { gap: 32px; width: 100%; }
  .bio-stats { grid-template-columns: 1fr; }
}

/* ============================================================
   SUBPAGE SUPPORT
   Pages without a full-bleed dark hero (/bio/) need the nav to be
   legible from the top, not only once scrolled.
   ============================================================ */

/* Page header for subpages — sits below the fixed nav */
.page-head {
  padding: calc(var(--nav-h) + 72px) 0 0;
  background: var(--bg);
}
/* Shares its column width with .bio-scene and .prose below (see those rules)
   so the headline/lead, image, and body copy all line up on the same edges
   instead of three independently-centered widths. */
.page-head .wrap {
  max-width: 680px;
}
/* The section right after the page header (image + prose) had the generic
   .section clamp(96px,13vw,150px) top padding on top of the page-head's own
   clearance — nearly 150px of dead air before the image. Tightened here
   instead of touching .section generally (used all over the site). */
.page-head + .section {
  padding-top: 40px;
}
.page-head h1 {
  /* The bio page's main title — moved to Strippy with the other main titles
     2026-07-30 (matches .section-title's treatment: Strippy reads as a heavy
     condensed caps face, so weight/leading/tracking/case all follow suit
     rather than carrying over the Jost-tuned values). */
  font-family: var(--font-title);
  font-size: clamp(38px, 6vw, 64px);
  font-weight: 400;
  line-height: 0.96;
  letter-spacing: 0.005em;
  text-transform: uppercase;
  margin: 12px 0 20px;
}
.page-head .page-lead {
  font-size: clamp(17px, 2vw, 20px);
  line-height: 1.65;
  color: var(--muted);
  max-width: 62ch;
}
.page-back {
  display: inline-flex;
  align-items: center;
  gap: 8px;
  margin-top: 40px;
  font-size: 14px;
  font-weight: 600;
  color: var(--muted);
  transition: gap 0.2s var(--ease), color 0.2s var(--ease);
}
.page-back:hover {
  color: var(--accent-text);
  gap: 12px;
}

/* Long-form prose column — centered on the page (margin: auto), not flush
   left, since there's no second column left to balance it against the
   wrap's full 1200px width; text itself stays left-aligned for readability,
   only the block is centered. Shares its 680px width with .page-head .wrap
   and .bio-scene above so the headline, image, and body copy all share the
   same left/right edges (was 68ch — read narrower than the other two and
   didn't line up). */
.prose {
  max-width: 680px;
  margin: 0 auto;
}
.prose p {
  font-size: 17px;
  line-height: 1.75;
  color: var(--fg);
  margin-bottom: 26px;
}
.prose p:last-child {
  margin-bottom: 0;
}
/* Mid-page section breaks inside the bio prose ("The Name Drop..", "But
   Seriously") reuse the site's normal .section-label eyebrow rather than
   the full .page-head component — .page-head carries ~144px of top padding
   meant for the very top of the page, which was stacking on top of the
   generic .section padding and reading as a huge dead gap wherever it got
   reused mid-page. The first label right after .bio-scene needs no extra
   push (the image's own 48px bottom margin already does that job); only
   later ones need a break from the paragraph above.
   `.prose .section-label` restates size/color/margin-bottom because
   `.prose p` (class+element, higher specificity than plain `.section-label`)
   was winning and rendering these at 17px ink instead of 11px muted-gray —
   same specificity trap as the .cta-card p / .form-note bug fixed earlier. */
.prose .section-label {
  font-size: 11px;
  color: var(--muted);
  margin-bottom: 20px;
}
.prose .section-label:not(:first-child) {
  margin-top: 22px;
}
.prose strong {
  font-weight: 600;
}
/* The studio scene photo — full width, native aspect, no crop (see the HTML
   comment: the image's own painted edge is part of its art direction, so
   object-fit:cover would clip into it). Same 680px column as .page-head
   .wrap and .prose (was 860px — wider than both, so its edges didn't line
   up with the headline above or the body copy below). No radius, no
   shadow — the Flat Rule, and the image already carries its own irregular
   edge treatment. */
.bio-scene {
  max-width: 680px;
  margin: 0 auto 48px;
}
.bio-scene img {
  width: 100%;
  height: auto;
  display: block;
}
.bio-page-grid {
  display: block;
}

/* Keep the wordmark and CTA on one line at every width */
.logo, .nav-cta { white-space: nowrap; }

/* ============================================================
   SOCIAL ROW
   Named links rather than bare icons — an icon alone makes people
   guess, and these carry different handles per platform anyway.
   ============================================================ */
.social-row {
  margin-top: clamp(40px, 6vw, 64px);
}
.social-row-label {
  font-size: 12px;
  font-weight: 700;
  letter-spacing: 0.14em;
  text-transform: uppercase;
  color: var(--muted);
  margin-bottom: 20px;
}
.social-links {
  display: grid;
  grid-template-columns: repeat(4, 1fr);
  gap: 12px;
}
.social-link {
  display: flex;
  align-items: center;
  gap: 14px;
  min-height: 64px;
  padding: 0 20px;
  background: var(--bg);
  border: var(--border-w) solid var(--border);
  border-radius: var(--radius);
  transition: border-color 0.2s var(--ease), transform 0.2s var(--ease), box-shadow 0.2s var(--ease);
}
.social-link:hover {
  border-color: var(--fg);
  transform: translateY(-2px);
  box-shadow: var(--shadow-sm);
}
.social-link svg {
  width: 22px;
  height: 22px;
  flex: none;
  fill: currentColor;
}
.social-link-text {
  display: grid;
  gap: 1px;
  min-width: 0;
}
.social-link-text strong {
  font-size: 14px;
  font-weight: 600;
  line-height: 1.2;
}
.social-link-text span {
  font-size: 12px;
  color: var(--muted);
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}
@media (max-width: 900px) {
  .social-links { grid-template-columns: repeat(2, 1fr); }
}
@media (max-width: 460px) {
  .social-links { grid-template-columns: 1fr; }
}

/* ============================================================
   LIVE VIDEO BAND
   Full-bleed muted loop above the tour list (Revivalists pattern).
   The video is decoration: it autoplays only while on screen, never
   plays audio, and reduced-motion visitors get the poster frame.
   ============================================================ */
.live-band {
  position: relative;
  min-height: 72vh;
  display: grid;
  align-items: end;
  overflow: hidden;
  background: #101010; /* literal: sits behind video, must stay dark in any theme */
  color: #ffffff;
  isolation: isolate;
}
.live-video {
  position: absolute;
  inset: 0;
  width: 100%;
  height: 100%;
  object-fit: cover;
  /* Crop anchored high rather than centred: the band's heads sit in the upper
     half of frame, so a centred crop trims them. 30% keeps the top of the
     picture and sacrifices floor/monitors instead, which carry nothing. */
  object-position: 50% 30%;
  z-index: 0;
}
.live-scrim {
  position: absolute;
  inset: 0;
  z-index: 1;
  background: linear-gradient(to top,
    color-mix(in srgb, #000000 78%, transparent) 0%,
    color-mix(in srgb, #000000 30%, transparent) 40%,
    color-mix(in srgb, #000000 12%, transparent) 100%);
}
.live-content {
  position: relative;
  z-index: 2;
  padding: clamp(48px, 8vw, 96px) 0 clamp(40px, 6vw, 72px);
}
.live-content .section-label {
  color: color-mix(in srgb, #ffffff 65%, transparent);
}
.live-content .section-title {
  color: #ffffff;
}
.live-content .section-lead {
  color: color-mix(in srgb, #ffffff 78%, transparent);
}
/* The tour list follows directly — pull it tight against the band
   so the two read as one zone. */
.live-band + .tour {
  padding-top: clamp(40px, 6vw, 64px);
}

/* Narrow screens: a 72vh band on a phone is taller than it is wide, so a 16:9
   clip under object-fit:cover loses ~64% of its width to side-cropping. Tying
   the height to the viewport width instead fixes the band at 1:0.64 (≈1.56:1),
   which keeps ~88% of the frame's width visible at every width below 900px and
   never crops vertically at all. Padding tightens to match the shorter band;
   min-height (not height) means the band still grows if the copy needs it. */
@media (max-width: 900px) {
  .live-band { min-height: 64vw; }
  .live-content {
    padding: clamp(32px, 6vw, 56px) 0 clamp(28px, 5vw, 48px);
  }
}

/* ============================================================
   IN-PAGE PLAYER + VISUALIZER STAGE
   The track row expands (grid-rows accordion) into a dark stage
   where the spectrum terrain renders. Row layout gains a fourth
   area: the stage spans the full row width.
   ============================================================ */
.track {
  grid-template-columns: 48px 1fr auto;
  grid-template-areas: "play info meta" "stage stage stage";
  cursor: default;
}
.track-play { grid-area: play; }
.track-info { grid-area: info; }
.track-meta { grid-area: meta; }
.track-meta a {
  font-size: 13px;
  font-weight: 600;
  color: var(--muted);
  transition: color 0.2s var(--ease);
}
.track-meta a:hover { color: var(--accent-text); }

button.track-play {
  width: 48px;
  height: 48px;
  border-radius: 50%;
  border: var(--border-w) solid var(--border);
  display: grid;
  place-items: center;
  background: var(--surface);
  color: var(--fg);
  transition: border-color 0.2s var(--ease), background 0.2s var(--ease), color 0.2s var(--ease);
}
button.track-play:hover { border-color: var(--fg); }
button.track-play svg { width: 18px; height: 18px; }
.track-play .ic-pause { display: none; }
.track.is-playing .track-play .ic-play { display: none; }
.track.is-playing .track-play .ic-pause { display: block; }
.track.is-playing button.track-play {
  background: var(--accent);
  border-color: var(--accent);
  color: #ffffff;
}

/* Accordion: rows animate 0fr -> 1fr; inner wrapper clips */
.track-stage {
  grid-area: stage;
  display: grid;
  grid-template-rows: 0fr;
  transition: grid-template-rows 0.55s var(--ease);
}
.track.is-open .track-stage { grid-template-rows: 1fr; }
.track-stage-inner {
  overflow: hidden;
  min-height: 0;
}
.track.is-open .track-stage-inner {
  margin: 6px 0 2px;
}
/* The visualizer floats on the page itself — no box, no dark stage.
   The canvas is created by player.js, attached to the active row, and
   bleeds upward so the ink dances above the list. pointer-events: none
   keeps every link and button under it clickable. */
.track { position: relative; }
.track-viz {
  position: absolute;
  left: 22%;
  top: -400px;
  /* Explicit width/height, not left+right insets: canvas is a REPLACED
     element, so opposing insets don't size it — it would fall back to its
     buffer attribute, and the render loop's buffer-follows-layout resize
     then feeds back and doubles forever. Explicit CSS dimensions break
     that loop. */
  width: 80%;
  height: calc(100% + 408px);
  z-index: 4;
  pointer-events: none;
}
@media (max-width: 720px) {
  .track-viz { left: 0; width: 100%; top: -280px; height: calc(100% + 288px); }
}
.track-transport {
  display: flex;
  align-items: center;
  gap: 14px;
  padding: 10px 2px 4px;
  font-size: 12px;
  font-variant-numeric: tabular-nums;
  color: var(--muted);
}
.seek {
  flex: 1;
  appearance: none;
  -webkit-appearance: none;
  height: 3px;
  border-radius: 2px;
  background: var(--border);
  cursor: pointer;
}
.seek::-webkit-slider-thumb {
  -webkit-appearance: none;
  width: 14px;
  height: 14px;
  border-radius: 50%;
  background: var(--accent);
  border: none;
}
.seek::-moz-range-thumb {
  width: 14px;
  height: 14px;
  border-radius: 50%;
  background: var(--accent);
  border: none;
}
