/* ─────────────────────────────────────────────────────────────────────────
   babe al3rt — night with soul
   tokens + base + section styles
   ───────────────────────────────────────────────────────────────────────── */

:root {
  /* Palette — set live by tweaks; defaults: warm amber (ember) */
  --ink-0: oklch(0.10 0.022 50);     /* deepest bg */
  --ink-1: oklch(0.14 0.028 50);     /* primary bg */
  --ink-2: oklch(0.19 0.035 50);     /* surface */
  --ink-3: oklch(0.27 0.045 50);     /* hairline */
  --ink-4: oklch(0.42 0.04 55);      /* muted text */
  --paper: oklch(0.96 0.018 75);     /* primary text */
  --paper-mute: oklch(0.78 0.025 70);

  --ember: oklch(0.80 0.17 55);       /* the warm accent */
  --ember-deep: oklch(0.60 0.18 45);
  --ember-glow: oklch(0.85 0.16 65);

  --hair: rgba(255, 240, 210, 0.08);
  --hair-strong: rgba(255, 240, 210, 0.16);

  /* Type */
  --f-display: "Geist", ui-sans-serif, -apple-system, BlinkMacSystemFont, "Helvetica Neue", sans-serif;
  --f-body: "Geist", ui-sans-serif, -apple-system, BlinkMacSystemFont, sans-serif;
  --f-mono: "Geist Mono", ui-monospace, "SF Mono", Menlo, monospace;

  /* Layout */
  --gutter: clamp(20px, 4vw, 56px);
  --maxw: 1240px;
  --section-pad: clamp(64px, 9vh, 128px);

  /* Beat-reactive vars (driven by audio.jsx) — see the @property block below.
     They are REGISTERED custom properties: that is a performance requirement,
     not cosmetic. An UNregistered custom property changing on :root forces
     Blink to recalc style for the whole document subtree every frame (coarse
     dependency tracking). Registered via @property, Blink invalidates only the
     handful of elements that actually reference the variable. */
}

/* Register the beat vars as typed custom properties. inherits:true so the
   scattered reactive elements (hero name, spotlights, atmosphere…) still
   resolve var(--beat-*); the registration is what unlocks fine-grained,
   cheap style invalidation when audio.jsx writes them every frame. */
@property --beat-low    { syntax: "<number>"; inherits: true; initial-value: 0; }
@property --beat-mid    { syntax: "<number>"; inherits: true; initial-value: 0; }
@property --beat-high   { syntax: "<number>"; inherits: true; initial-value: 0; }
@property --beat-energy { syntax: "<number>"; inherits: true; initial-value: 0; }
@property --live        { syntax: "<number>"; inherits: true; initial-value: 0; }

* { box-sizing: border-box; -webkit-font-smoothing: antialiased; -moz-osx-font-smoothing: grayscale; }

html, body {
  margin: 0; padding: 0;
  background: var(--ink-0);
  color: var(--paper);
  font-family: var(--f-body);
  font-feature-settings: "ss01", "cv11";
  font-size: 16px;
  line-height: 1.5;
  letter-spacing: -0.005em;
  overflow-x: hidden;
}

::selection { background: var(--ember); color: var(--ink-0); }

a { color: inherit; text-decoration: none; }

/* ─── Spotlights ───────────────────────────────────────────────────────
   Club lighting cue. Two beams that sweep across the page when LIVE.
   Opacity is gated on --live so they are completely absent at REST.
   ──────────────────────────────────────────────────────────────────── */
.spotlights {
  position: fixed; inset: 0;
  width: 100vw; height: 100vh;
  pointer-events: none;
  z-index: 1;
  overflow: hidden;
  opacity: var(--live);
  transition: opacity 0.6s ease-out;
  /* screen blend keeps the beams luminous (they brighten, never darken) and
     measured at < 1fps cost here — the framerate problem was never the blend,
     it was a blur filter on the rotating beam (now moved to the static cone
     ::after below, so it rasterizes once). */
  mix-blend-mode: screen;
  contain: layout paint;
}
/* THE BEAM — does the sweep. It carries the rotate/scale keyframe animation
   and NOTHING that needs main-thread painting: no filter, no blur. It is a
   pure compositor transform. The blur lives on the static ::after below. */
.spotlights .beam {
  position: absolute;
  top: -40vh;
  width: 14vh;
  height: 200vh;
  transform-origin: top center;
  will-change: transform;
}
/* THE CONE — the visible, blurred light shaft. filter: blur() with a CONSTANT
   radius on a STATIC element: it rasterizes ONCE into its own layer texture.
   When the parent .beam sweeps, the compositor just moves that cached blurred
   texture — no re-rasterization. The beat lift is OPACITY ONLY (a compositor
   property — it does not re-raster the blur). This is the core of the fix:
   the old code put the blur on the rotating beam, so the blurred surface had
   to be re-rendered every frame of the sweep. */
.spotlights .beam::after {
  content: "";
  position: absolute; inset: 0;
  clip-path: polygon(46% 0%, 54% 0%, 100% 100%, 0% 100%);
  filter: blur(44px) saturate(1.0);
  opacity: calc(0.5 + var(--beat-low) * 0.3 + var(--beat-energy) * 0.16);
  transition: opacity 0.07s linear;
}
.spotlights .beam-1 {
  left: 20%;
  animation: sweep1 9s ease-in-out infinite alternate;
}
.spotlights .beam-1::after {
  background: linear-gradient(180deg,
    color-mix(in oklch, var(--ember-glow) 70%, transparent) 0%,
    color-mix(in oklch, var(--ember) 32%, transparent) 28%,
    color-mix(in oklch, var(--ember-deep) 12%, transparent) 60%,
    transparent 95%);
}
.spotlights .beam-2 {
  left: 68%;
  animation: sweep2 11s ease-in-out infinite alternate;
}
.spotlights .beam-2::after {
  background: linear-gradient(180deg,
    color-mix(in oklch, var(--ember) 60%, transparent) 0%,
    color-mix(in oklch, var(--ember-deep) 28%, transparent) 35%,
    transparent 90%);
}
.spotlights .beam-3 {
  left: 44%;
  animation: sweep3 13s ease-in-out infinite alternate;
}
.spotlights .beam-3::after {
  background: linear-gradient(180deg,
    color-mix(in oklch, var(--ember-glow) 55%, transparent) 0%,
    color-mix(in oklch, var(--ember) 18%, transparent) 40%,
    transparent 88%);
}
/* Hotspots — bright source dots where the beams emanate. Constant blur
   radius + static transform → rasterizes ONCE. The bass pump is OPACITY
   ONLY: scaling a blurred element would re-render its blur every frame, so
   the hotspot stays a fixed size and just brightens. */
.spotlights .beam::before {
  content: "";
  position: absolute;
  top: -4vh; left: 50%;
  width: 8vh; height: 8vh;
  transform: translate(-50%, 0);
  border-radius: 100%;
  background: radial-gradient(circle,
    color-mix(in oklch, var(--ember-glow) 90%, transparent) 0%,
    color-mix(in oklch, var(--ember) 40%, transparent) 30%,
    transparent 70%);
  filter: blur(20px);
  opacity: calc(0.45 + var(--beat-low) * 0.3 + var(--beat-energy) * 0.1);
  transition: opacity 0.06s linear;
}

@keyframes sweep1 {
  0%   { transform: rotate(-28deg) scaleX(0.9); }
  50%  { transform: rotate(8deg)   scaleX(1.1); }
  100% { transform: rotate(-14deg) scaleX(1.0); }
}
@keyframes sweep2 {
  0%   { transform: rotate(22deg)  scaleX(1.0); }
  50%  { transform: rotate(-10deg) scaleX(0.9); }
  100% { transform: rotate(14deg)  scaleX(1.1); }
}
@keyframes sweep3 {
  0%   { transform: rotate(-6deg)  scaleX(0.85); }
  100% { transform: rotate(12deg)  scaleX(1.0); }
}

@media (prefers-reduced-motion: reduce) {
  .spotlights .beam { animation: none; }
}

/* ─── Page atmosphere ─── */
.atmos {
  position: fixed; inset: 0; pointer-events: none; z-index: 0;
  background:
    radial-gradient(120% 80% at 20% 0%, color-mix(in oklch, var(--ember-deep) 22%, transparent) 0%, transparent 55%),
    radial-gradient(80% 60% at 100% 30%, color-mix(in oklch, var(--ember) 10%, transparent) 0%, transparent 60%),
    radial-gradient(140% 100% at 50% 110%, color-mix(in oklch, var(--ember-deep) 26%, transparent) 0%, transparent 60%),
    linear-gradient(180deg, var(--ink-0) 0%, var(--ink-1) 40%, var(--ink-0) 100%);
}
.atmos::after {
  /* film grain */
  content: ""; position: absolute; inset: 0;
  background-image: url("data:image/svg+xml;utf8,<svg xmlns='http://www.w3.org/2000/svg' width='180' height='180'><filter id='n'><feTurbulence type='fractalNoise' baseFrequency='0.9' numOctaves='2' stitchTiles='stitch'/><feColorMatrix values='0 0 0 0 1 0 0 0 0 0.86 0 0 0 0 0.7 0 0 0 0.55 0'/></filter><rect width='180' height='180' filter='url(%23n)' opacity='0.55'/></svg>");
  mix-blend-mode: overlay; opacity: 0.18;
}

/* ─── Atmospheric drift ─────────────────────────────────────────────────
   The room breathing. Visible at REST, intensified by --beat-* in LIVE.

   ONE blurred warm-field layer. filter: blur(60px) with a CONSTANT radius;
   the blur radius is never animated. The slow ambient drift is a single
   cheap transform animation (translate + scale only — no rotate, because
   rotating a blur()-filtered layer forces Blink to re-rasterize it). The
   beat lift rides on opacity only, since the transform channel is taken by
   the drift keyframe. At REST --beat-* = 0 → it just slow-drifts.
   ──────────────────────────────────────────────────────────────────── */
.atmos-drift {
  position: fixed; inset: -20%; pointer-events: none; z-index: 0;
  background:
    /* Big warm orb — top-left */
    radial-gradient(38% 30% at 28% 36%,
      color-mix(in oklch, var(--ember) 30%, transparent) 0%,
      color-mix(in oklch, var(--ember) 8%, transparent) 40%,
      transparent 70%),
    /* Deep maroon glow — bottom-right */
    radial-gradient(42% 36% at 74% 64%,
      color-mix(in oklch, var(--ember-deep) 38%, transparent) 0%,
      color-mix(in oklch, var(--ember-deep) 12%, transparent) 45%,
      transparent 70%),
    /* Bright halo highlight */
    radial-gradient(22% 18% at 60% 22%,
      color-mix(in oklch, var(--ember-glow) 26%, transparent) 0%,
      transparent 65%),
    /* Low under-glow */
    radial-gradient(50% 40% at 40% 88%,
      color-mix(in oklch, var(--ember-deep) 32%, transparent) 0%,
      transparent 70%);
  filter: blur(60px);          /* constant radius — rasterizes once */
  opacity: calc(0.95 + var(--beat-low) * 0.6 + var(--beat-energy) * 0.15);
  /* The drift keyframe owns transform; the beat lift is opacity-only. */
  animation: atmosDrift 60s ease-in-out infinite alternate;
  transition: opacity 0.08s linear;
  will-change: transform, opacity;
}
/* Slow ambient orbit — translate + scale only (no rotate on a blurred
   layer). The static blurred texture is just slid + scaled by the
   compositor; it is not re-rasterized. */
@keyframes atmosDrift {
  0%   { transform: translate3d(-2.5%, -2%, 0) scale(1.04); }
  50%  { transform: translate3d(3%, 2.5%, 0)   scale(1.10); }
  100% { transform: translate3d(-1.5%, 3%, 0)  scale(1.05); }
}

/* (The --blob-* registered properties are gone — the atmosphere drift no
   longer animates gradient positions; it transforms two static blurred
   layers instead. See the .atmos-drift block above.) */

/* ─── Layout helpers ─── */
.wrap {
  position: relative; z-index: 2;
  max-width: var(--maxw);
  margin: 0 auto;
  padding: 0 var(--gutter);
}

main { position: relative; z-index: 1; }

section { position: relative; padding: var(--section-pad) 0; }

/* ─── Header ─── */
.hdr {
  position: fixed; top: 0; left: 0; right: 0; z-index: 50;
  padding: 18px var(--gutter);
  display: flex; align-items: center; justify-content: space-between;
  /* No backdrop-filter: with the atmosphere / beams / particles moving behind
     a fixed header, backdrop-filter re-rasterizes every frame on mobile. A
     denser translucent dark gradient reads the same — the wordmark still sits
     on a clean falloff — at zero per-frame cost. */
  background: linear-gradient(180deg,
    color-mix(in oklch, var(--ink-0) 92%, transparent) 0%,
    color-mix(in oklch, var(--ink-0) 64%, transparent) 55%,
    transparent 100%);
}
.hdr-mark {
  font-family: var(--f-display);
  font-weight: 500;
  font-size: 18px;
  letter-spacing: -0.02em;
  position: relative;
}
.hdr-mark .ghost { display: inline-block; transform: translateY(-1px); margin-left: 6px; opacity: 0.85; }
/* slow ambient shimmer on wordmark — 22s */
.hdr-mark::before {
  content: ""; position: absolute; inset: -4px -8px;
  background: radial-gradient(60% 100% at 50% 50%, color-mix(in oklch, var(--ember) 26%, transparent) 0%, transparent 70%);
  filter: blur(10px); opacity: 0.5; z-index: -1;
  animation: markGlow 22s ease-in-out infinite alternate;
}
@keyframes markGlow {
  0%   { opacity: 0.35; transform: translateX(-4%) scale(0.98); }
  100% { opacity: 0.7;  transform: translateX(4%)  scale(1.05); }
}

.hamburger {
  appearance: none; background: transparent; border: 0;
  color: var(--paper); cursor: pointer; padding: 8px;
  display: flex; flex-direction: column; gap: 5px; align-items: flex-end;
}
.hamburger span {
  display: block; height: 1px; background: var(--paper-mute);
  transition: width 0.4s cubic-bezier(.2,.7,.2,1), background 0.3s;
}
.hamburger span:nth-child(1) { width: 22px; }
.hamburger span:nth-child(2) { width: 14px; }
.hamburger span:nth-child(3) { width: 18px; }
.hamburger:hover span { background: var(--paper); width: 22px; }

/* Menu overlay */
.menu {
  position: fixed; inset: 0; z-index: 60;
  /* Near-opaque deep wash instead of backdrop-filter blur — the overlay
     covers the whole viewport, so a heavy backdrop-filter only buys constant
     re-rasterization. The solid wash reads identically. */
  background: color-mix(in oklch, var(--ink-0) 98%, transparent);
  display: flex; align-items: center; justify-content: center;
  opacity: 0; pointer-events: none;
  transition: opacity 0.4s cubic-bezier(.2,.7,.2,1);
}
.menu.open { opacity: 1; pointer-events: auto; }
.menu-list { display: flex; flex-direction: column; gap: 18px; align-items: center; }
.menu-list a {
  font-family: var(--f-display);
  font-size: clamp(38px, 7vw, 76px);
  font-weight: 300; letter-spacing: -0.04em;
  color: var(--paper-mute);
  transition: color 0.3s, transform 0.4s cubic-bezier(.2,.7,.2,1);
  transform: translateY(20px); opacity: 0;
}
.menu.open .menu-list a {
  transform: translateY(0); opacity: 1;
  transition-delay: calc(var(--i, 0) * 60ms + 100ms);
}
.menu-list a:hover { color: var(--ember-glow); }
.menu-close {
  position: fixed; top: 18px; right: var(--gutter); z-index: 61;
  appearance: none; background: transparent; border: 0; color: var(--paper);
  cursor: pointer; padding: 8px;
  font-family: var(--f-mono); font-size: 12px; letter-spacing: 0.04em;
}

/* ─── Hero ─── */
.hero {
  min-height: 100vh;
  padding-top: 110px;
  padding-bottom: 56px;
  display: flex; flex-direction: column;
  position: relative;
}
.hero > .wrap {
  flex: 1;
  display: flex;
  flex-direction: column;
  justify-content: center;
}
.hero-grid {
  display: flex;
  flex-direction: column;
  gap: clamp(36px, 6vh, 72px);
}
.hero-top {
  display: flex;
  flex-direction: column;
  gap: clamp(20px, 3vh, 36px);
}
.hero-eyebrow {
  font-family: var(--f-mono); font-size: 11px; letter-spacing: 0.18em;
  text-transform: uppercase; color: var(--paper-mute);
  display: flex; align-items: center; gap: 14px;
}
.hero-eyebrow::before {
  content: ""; display: block; width: 32px; height: 1px;
  background: var(--ember);
  /* Static ember glow — constant radius, constant colour. Not beat-reactive
     (a minor element; the drama lives in the big atmosphere/headline moves). */
  box-shadow: 0 0 10px color-mix(in oklch, var(--ember) 70%, transparent);
}
.hero-name {
  font-family: var(--f-display);
  font-size: clamp(54px, 10.5vw, 156px);
  font-weight: 200; letter-spacing: -0.055em; line-height: 0.9;
  color: var(--paper);
  margin: 0;
  white-space: nowrap;
  /* No font-variation-settings animation off the beat — re-shaping a
     variable font every frame forces a full text re-raster. The LIVE kick
     is expressed purely via transform: scale() (see the LIVE section). */
}
.hero-name .num3 {
  font-style: normal;
  color: var(--ember);
  font-weight: 300;
  display: inline-block;
  transform: translateY(-0.02em);
}
.hero-headline {
  font-family: var(--f-display);
  font-size: clamp(36px, 5.5vw, 72px);
  font-weight: 300; letter-spacing: -0.035em; line-height: 1.02;
  margin: 0;
  max-width: 14ch;
  color: var(--paper);
}
.hero-headline em { font-style: normal; color: var(--ember-glow); }
.hero-support {
  font-size: clamp(15px, 1.2vw, 18px);
  line-height: 1.55;
  color: var(--paper-mute);
  max-width: 44ch;
  margin: 0;
  font-weight: 400;
}
.hero-bottom {
  display: flex; align-items: flex-end; justify-content: space-between;
  gap: 40px; flex-wrap: wrap;
}

/* ─── Soundwave button ─── */
.sw-wrap {
  display: inline-flex; align-items: center; gap: 18px;
  cursor: pointer; user-select: none;
  -webkit-tap-highlight-color: transparent;
  position: relative;
}
/* Beat-pumped glow halo behind the soundwave button. A single static-blur
   radial layer — it breathes via transform: scale() + opacity off the beat
   (NOT an animated box-shadow radius, which re-rasterizes the button every
   frame). The soundwave button is one of the deliberate LIVE-reactive areas. */
.sw-wrap::before {
  content: "";
  position: absolute;
  left: 0; top: 50%;
  width: clamp(180px, 22vw, 280px); height: 56px;
  border-radius: 100px;
  transform: translateY(-50%) scale(calc(1 + var(--beat-energy) * 0.6));
  background: radial-gradient(60% 120% at 30% 50%,
    color-mix(in oklch, var(--ember) 55%, transparent) 0%,
    transparent 75%);
  filter: blur(22px);              /* constant radius — rasterizes once */
  opacity: calc(var(--live) * (0.35 + var(--beat-energy) * 0.65));
  pointer-events: none;
  z-index: 0;
  transition: transform 0.06s linear, opacity 0.08s linear;
}
.sw-canvas-host {
  position: relative;
  z-index: 1;                      /* above the beat-pumped glow halo */
  width: clamp(180px, 22vw, 280px);
  height: 56px;
  border-radius: 100px;
  background: linear-gradient(180deg,
    color-mix(in oklch, var(--ink-2) 90%, transparent) 0%,
    color-mix(in oklch, var(--ink-1) 90%, transparent) 100%);
  border: 1px solid var(--hair-strong);
  box-shadow:
    inset 0 1px 0 rgba(255, 240, 210, 0.06),
    0 8px 32px rgba(0,0,0,0.4);
  transition: box-shadow 0.4s, border-color 0.3s;
  overflow: hidden;
}
.sw-wrap:hover .sw-canvas-host {
  border-color: color-mix(in oklch, var(--ember) 35%, var(--hair-strong));
  box-shadow:
    inset 0 1px 0 rgba(255, 240, 210, 0.08),
    0 12px 40px rgba(0,0,0,0.5),
    0 0 36px color-mix(in oklch, var(--ember) 22%, transparent);
}
.sw-canvas-host.live {
  border-color: color-mix(in oklch, var(--ember) 45%, var(--hair-strong));
  /* Constant-radius lit glow — the "on" state. The per-beat pump is carried
     by the .sw-wrap::before halo (transform/opacity) and the canvas itself. */
  box-shadow:
    inset 0 1px 0 rgba(255, 240, 210, 0.1),
    0 12px 40px rgba(0,0,0,0.5),
    0 0 44px color-mix(in oklch, var(--ember) 45%, transparent);
}
.sw-canvas { position: absolute; inset: 0; width: 100%; height: 100%; }
.sw-label {
  font-family: var(--f-mono);
  font-size: 11px; letter-spacing: 0.15em;
  text-transform: lowercase;
  color: var(--paper-mute);
  transition: color 0.3s;
}
.sw-wrap:hover .sw-label { color: var(--paper); }
.sw-canvas-host.live + .sw-label { color: var(--ember-glow); }
.sw-label .dot {
  display: inline-block; width: 6px; height: 6px;
  border-radius: 100px; margin-right: 8px;
  background: var(--paper-mute);
  transition: background 0.3s, box-shadow 0.3s;
  vertical-align: middle;
}
.sw-canvas-host.live ~ .sw-label .dot {
  background: var(--ember);
  box-shadow: 0 0 12px var(--ember);
}

/* ─── Pointer (particle field is rendered separately; this is the micro-label) ─── */
.pointer-section {
  padding: clamp(36px, 6vh, 72px) 0;
  text-align: center;
  position: relative;
  min-height: 0;
}
.pointer-label {
  font-family: var(--f-mono);
  font-size: 11px; letter-spacing: 0.22em; text-transform: uppercase;
  color: var(--paper-mute);
  opacity: 0.7;
}

/* ─── Section heading patterns ─── */
.sec-h {
  display: flex; align-items: baseline; gap: 18px;
  margin-bottom: clamp(36px, 6vh, 64px);
}
.sec-h .num {
  font-family: var(--f-mono);
  font-size: 11px; letter-spacing: 0.18em; text-transform: uppercase;
  color: var(--ember);
  min-width: 32px;
}
.sec-h h2 {
  font-family: var(--f-display);
  font-size: clamp(32px, 4.4vw, 60px);
  font-weight: 300; letter-spacing: -0.035em; line-height: 1.02;
  margin: 0;
  color: var(--paper);
  max-width: 18ch;
}
.sec-intro {
  font-size: clamp(17px, 1.35vw, 21px);
  line-height: 1.5;
  color: var(--paper-mute);
  max-width: 56ch;
  margin: 0 0 clamp(40px, 6vh, 72px);
  font-weight: 400;
}

/* ─── Nights ─── */
.nights {
  display: grid;
  grid-template-columns: 1fr;
  gap: 1px;
  background: var(--hair);
}
.night {
  background: var(--ink-1);
  padding: clamp(36px, 5vh, 64px) 0;
  display: grid;
  grid-template-columns: 1fr;
  gap: 24px;
  transition: background 0.4s;
}
.night:hover { background: var(--ink-2); }
@media (min-width: 800px) {
  .night {
    grid-template-columns: 1.1fr 1.4fr 1.2fr;
    align-items: center;
    gap: 48px;
  }
}
.night-name {
  font-family: var(--f-display);
  font-size: clamp(28px, 3.2vw, 42px);
  font-weight: 300; letter-spacing: -0.03em; line-height: 1.05;
  color: var(--paper);
  margin: 0;
}
.night-name .sup {
  font-family: var(--f-mono);
  font-size: 11px; letter-spacing: 0.2em; text-transform: uppercase;
  color: var(--ember);
  display: block;
  margin-bottom: 14px;
  font-weight: 500;
}
.night-body {
  font-size: clamp(15px, 1.15vw, 18px);
  line-height: 1.55;
  color: var(--paper-mute);
  margin: 0;
  max-width: 36ch;
}

/* Photo placeholder */
.photo {
  position: relative;
  background: linear-gradient(135deg, var(--ink-2) 0%, var(--ink-1) 100%);
  border: 1px solid var(--hair);
  border-radius: 4px;
  overflow: hidden;
  aspect-ratio: 16 / 10;
}
.photo::before {
  content: "";
  position: absolute; inset: 0;
  background:
    repeating-linear-gradient(45deg,
      transparent 0px, transparent 18px,
      color-mix(in oklch, var(--ember-deep) 8%, transparent) 18px,
      color-mix(in oklch, var(--ember-deep) 8%, transparent) 19px);
}
.photo::after {
  content: "";
  position: absolute; inset: 0;
  background: radial-gradient(60% 60% at 50% 30%, color-mix(in oklch, var(--ember) 12%, transparent) 0%, transparent 70%);
}
.photo-label {
  position: absolute;
  inset: auto 0 0 0;
  padding: 14px;
  display: flex; flex-direction: column; gap: 4px;
  background: linear-gradient(180deg, transparent 0%, color-mix(in oklch, var(--ink-0) 88%, transparent) 100%);
}
.photo-label .tag {
  font-family: var(--f-mono);
  font-size: 9px; letter-spacing: 0.22em; text-transform: uppercase;
  color: var(--ember);
}
.photo-label .name {
  font-size: 12px; color: var(--paper-mute);
  letter-spacing: -0.005em;
}

/* ─── Sounds ─── */
.sounds {
  display: grid;
  grid-template-columns: 1fr;
  gap: clamp(40px, 5vh, 64px);
}
@media (min-width: 800px) {
  .sounds { grid-template-columns: repeat(3, 1fr); gap: 0; }
}
.sound {
  padding: clamp(32px, 5vh, 56px) 0;
  position: relative;
}
@media (min-width: 800px) {
  .sound { padding: clamp(40px, 6vh, 72px) 28px; }
  .sound + .sound { border-left: 1px solid var(--hair); }
}
.sound-num {
  font-family: var(--f-mono);
  font-size: 11px; letter-spacing: 0.2em; text-transform: uppercase;
  color: var(--ember);
  margin-bottom: 24px;
}
.sound-name {
  font-family: var(--f-display);
  font-size: clamp(24px, 2.6vw, 34px);
  font-weight: 300; letter-spacing: -0.03em; line-height: 1.08;
  color: var(--paper);
  margin: 0 0 18px;
}
.sound-body {
  font-size: clamp(15px, 1.1vw, 17px);
  line-height: 1.5;
  color: var(--paper-mute);
  margin: 0;
}
.sounds-closer {
  margin-top: clamp(32px, 5vh, 64px);
  font-family: var(--f-display);
  font-size: clamp(20px, 2vw, 28px);
  font-weight: 300; letter-spacing: -0.025em; line-height: 1.3;
  color: var(--paper);
  max-width: 36ch;
}

/* ─── Rooms ─── */
.rooms {
  display: grid;
  grid-template-columns: 1fr;
  gap: 0;
  font-family: var(--f-display);
  font-weight: 300;
  font-size: clamp(28px, 4.4vw, 64px);
  letter-spacing: -0.035em;
  line-height: 1.05;
  color: var(--paper);
}
.room {
  display: flex;
  align-items: baseline;
  gap: 24px;
  padding: clamp(18px, 2.4vh, 30px) 0;
  border-bottom: 1px solid var(--hair);
  color: color-mix(in oklch, var(--paper-mute) 70%, var(--paper));
  transition: color 0.3s, padding-left 0.3s;
  cursor: default;
}
.room::before {
  content: "";
  display: inline-block;
  width: 6px; height: 6px;
  border-radius: 100px;
  background: var(--ember);
  margin: 0;
  box-shadow: 0 0 8px color-mix(in oklch, var(--ember) 60%, transparent);
  flex-shrink: 0;
  transform: translateY(-0.18em);
  transition: box-shadow 0.3s, transform 0.3s, background 0.3s;
}
.room:hover { color: var(--paper); padding-left: 8px; }
.room:hover::before {
  box-shadow: 0 0 16px var(--ember);
  transform: translateY(-0.18em) scale(1.6);
}
.rooms-footer {
  font-family: var(--f-mono);
  font-size: 11px; letter-spacing: 0.2em; text-transform: uppercase;
  color: var(--paper-mute);
  margin-top: clamp(24px, 3vh, 40px);
}

/* ─── About ─── */
.about {
  display: grid;
  grid-template-columns: 1fr;
  gap: clamp(40px, 6vh, 80px);
  align-items: center;
}
@media (min-width: 900px) {
  .about { grid-template-columns: 0.85fr 1.15fr; gap: clamp(48px, 6vw, 100px); }
}
.about-portrait { max-width: 380px; }
.about-body {
  font-family: var(--f-display);
  font-size: clamp(22px, 2.4vw, 32px);
  font-weight: 300; letter-spacing: -0.025em; line-height: 1.3;
  color: var(--paper);
  max-width: 28ch;
}
.about-body .holly {
  font-family: var(--f-display);
  font-weight: 400;
  letter-spacing: -0.02em;
  color: var(--ember-glow);
  white-space: nowrap;
}
.about-body .deg { font-size: 0.7em; vertical-align: 0.18em; }

/* ─── Booking ─── */
.booking {
  position: relative;
  padding: var(--section-pad) 0;
}
.booking-recap {
  font-family: var(--f-mono);
  font-size: 11px; letter-spacing: 0.18em; text-transform: uppercase;
  color: var(--paper-mute);
  margin-bottom: 24px;
  display: flex; flex-wrap: wrap; gap: 0 6px;
  align-items: center;
}
.booking-recap span:not(:last-child)::after {
  content: "·"; margin-left: 6px; color: var(--ember); opacity: 0.7;
}
.booking-h {
  font-family: var(--f-display);
  font-size: clamp(40px, 6vw, 80px);
  font-weight: 300; letter-spacing: -0.04em; line-height: 1.0;
  margin: 0 0 24px;
  color: var(--paper);
  max-width: 16ch;
  /* Receives a calm pulse via CSS — gives the eye an anchor (3-6s cycle) */
  animation: ctaBreath 5.4s ease-in-out infinite alternate;
}
@keyframes ctaBreath {
  0%   { text-shadow: 0 0 0 transparent; }
  100% { text-shadow: 0 8px 60px color-mix(in oklch, var(--ember) 28%, transparent); }
}
.booking-h em {
  font-style: normal;
  background: linear-gradient(120deg, var(--paper) 0%, var(--ember-glow) 50%, var(--paper) 100%);
  background-size: 200% 100%;
  -webkit-background-clip: text;
  background-clip: text;
  color: transparent;
  animation: ctaShine 6s ease-in-out infinite;
}
@keyframes ctaShine {
  0%, 100% { background-position: 100% 0; }
  50%      { background-position: 0% 0; }
}
.booking-sub {
  font-size: clamp(16px, 1.3vw, 19px);
  line-height: 1.5;
  color: var(--paper-mute);
  margin: 0 0 clamp(32px, 5vh, 56px);
  max-width: 46ch;
}

/* Form */
.form {
  display: grid;
  grid-template-columns: 1fr;
  gap: 0;
  border-top: 1px solid var(--hair);
  /* In LIVE state, motion amplitude is capped low around the form — beat must not disrupt typing */
  position: relative; z-index: 3;
}
@media (min-width: 700px) {
  .form { grid-template-columns: 1fr 1fr; }
  .form .full { grid-column: 1 / -1; }
}
.field {
  border-bottom: 1px solid var(--hair);
  padding: 22px 0;
  position: relative;
}
@media (min-width: 700px) {
  .field { padding: 26px 0; }
  .field:nth-child(odd):not(.full) { padding-right: 32px; border-right: 1px solid var(--hair); }
  .field:nth-child(even):not(.full) { padding-left: 32px; }
}
.field label {
  display: block;
  font-family: var(--f-mono);
  font-size: 10px; letter-spacing: 0.2em; text-transform: uppercase;
  color: var(--paper-mute);
  margin-bottom: 10px;
  transition: color 0.25s;
}
.field input, .field textarea {
  appearance: none;
  background: transparent;
  border: 0;
  outline: 0;
  color: var(--paper);
  font-family: var(--f-display);
  font-size: clamp(18px, 1.5vw, 22px);
  font-weight: 300;
  letter-spacing: -0.015em;
  width: 100%;
  padding: 0;
  resize: none;
}
.field textarea { min-height: 90px; line-height: 1.4; }
.field input::placeholder, .field textarea::placeholder {
  color: var(--ink-4);
  font-weight: 300;
}
.field:focus-within label { color: var(--ember-glow); }

.submit-row {
  margin-top: 36px;
  display: flex; align-items: center; justify-content: space-between; gap: 24px;
  flex-wrap: wrap;
}
.submit {
  appearance: none; cursor: pointer;
  background: var(--ember);
  color: var(--ink-0);
  border: 0;
  border-radius: 100px;
  padding: 18px 32px;
  font-family: var(--f-display);
  font-weight: 500;
  font-size: 16px;
  letter-spacing: -0.005em;
  display: inline-flex; align-items: center; gap: 14px;
  transition: transform 0.15s cubic-bezier(.2,.7,.2,1), box-shadow 0.25s, background 0.25s;
  box-shadow: 0 8px 30px color-mix(in oklch, var(--ember) 40%, transparent),
              inset 0 1px 0 color-mix(in oklch, white 30%, transparent);
}
.submit:hover {
  transform: translateY(-1px);
  background: var(--ember-glow);
  box-shadow: 0 12px 40px color-mix(in oklch, var(--ember) 60%, transparent),
              inset 0 1px 0 color-mix(in oklch, white 30%, transparent);
}
.submit:active { transform: translateY(0); }
.submit svg { transition: transform 0.25s cubic-bezier(.2,.7,.2,1); }
.submit:hover svg { transform: translateX(4px); }

.booking-meta {
  font-family: var(--f-mono);
  font-size: 11px; letter-spacing: 0.16em; text-transform: uppercase;
  color: var(--paper-mute);
}
.booking-meta .placeholder {
  color: var(--ember);
  border: 1px dashed color-mix(in oklch, var(--ember) 40%, transparent);
  padding: 3px 8px; border-radius: 4px;
  margin-left: 8px;
}

.thanks {
  font-family: var(--f-display);
  font-size: clamp(28px, 3.2vw, 44px);
  font-weight: 300; letter-spacing: -0.03em; line-height: 1.2;
  color: var(--paper);
  max-width: 26ch;
  padding: 60px 0;
  position: relative;
}
.thanks .check {
  display: inline-flex; align-items: center; gap: 12px;
  color: var(--ember-glow);
}
.thanks .check::before {
  content: ""; display: inline-block;
  width: 12px; height: 12px; border-radius: 100px;
  background: var(--ember);
  box-shadow: 0 0 20px var(--ember);
}

/* ─── Footer ─── */
.ftr {
  padding: 80px 0 56px;
  border-top: 1px solid var(--hair);
  display: flex; justify-content: space-between; flex-wrap: wrap;
  gap: 28px;
  font-family: var(--f-mono);
  font-size: 11px; letter-spacing: 0.16em; text-transform: uppercase;
  color: var(--paper-mute);
}
.ftr a:hover { color: var(--ember-glow); }
.ftr .ghost { font-size: 14px; vertical-align: -1px; }

/* ─── Particles canvas (drifts hero → CTA) ─── */
.particles {
  position: fixed; inset: 0;
  width: 100vw; height: 100vh;
  pointer-events: none;
  z-index: 1;
}

/* Reduced motion */
@media (prefers-reduced-motion: reduce) {
  *, *::before, *::after {
    animation-duration: 0.01ms !important;
    animation-iteration-count: 1 !important;
    transition-duration: 0.01ms !important;
  }
  /* Atmosphere drift + spotlight beams settle to a still pose. */
  .atmos-drift { animation: none; }
  .spotlights .beam { animation: none; }
}

/* Print/clean hide of helper UI when hidden */
.hidden { display: none !important; }

/* ─── Header mini-waveform (comes alive in LIVE only) ─── */
.hdr-wave-wrap {
  position: absolute;
  inset: 0;
  display: flex; align-items: center; justify-content: center;
  pointer-events: none;
  z-index: -1;
  /* Centered, narrow band — does not compete with wordmark/hamburger */
}
.hdr-wave-canvas {
  width: min(600px, 50vw);
  height: 28px;
  opacity: var(--live);
  transition: opacity 0.5s;
}

/* ─────────────────────────────────────────────────────────────────────────
   Beat-reactivity scope (performance rework).

   The come-alive used to drive ~50 elements off --beat-*, many of them
   animating blur / shadow / filter RADII — each forces a full per-frame
   re-rasterization. That is what tanked mobile framerate.

   It is now a deliberate handful of BIG, CHEAP moves:
     • the atmosphere-drift glow      (opacity + transform: scale)
     • the spotlight beams + hotspots (opacity + transform: scale)
     • the hero headline + the "3"    (transform + opacity + color)
     • the soundwave button area      (canvas-driven, see soundwave.jsx)
   Off --beat-*, these animate ONLY transform / opacity / color.

   Minor elements (nav, menu, hamburger, footer, section labels/numbers,
   night names, room names, sound numbers, photos, hairlines, etc.) keep
   their static ember styling but no longer react to the beat — the drama
   comes from a few large gestures, not 50 tiny expensive ones.
   ───────────────────────────────────────────────────────────────────────── */

/* Section heading — static ember accents. The left-edge bar is a quiet
   static hairline (no per-frame blur/opacity animation). */
.sec-h h2 {
  position: relative;
}
.sec-h h2::before {
  content: ""; position: absolute; left: -18px; top: 12%; bottom: 12%;
  width: 1px;
  background: linear-gradient(180deg, transparent 0%, var(--ember) 50%, transparent 100%);
  opacity: 0.3;
}

/* Booking section — motion-capped: the surrounding atmosphere does not bloom here */
.booking { isolation: isolate; }
.booking::before {
  content: ""; position: absolute; inset: 0; pointer-events: none;
  background: linear-gradient(180deg,
    color-mix(in oklch, var(--ink-0) 0%, transparent) 0%,
    color-mix(in oklch, var(--ink-0) 14%, transparent) 50%,
    color-mix(in oklch, var(--ink-0) 0%, transparent) 100%);
  opacity: calc(var(--live) * 0.7);
  z-index: 0;
}
.booking .wrap { position: relative; z-index: 1; }

/* Pointer label — a quiet lift when the room goes LIVE. The base opacity +
   --live lift live here; the beat-reactive groove (scale + a mid-band opacity
   breath) is added by the .pointer-label rule in the GROOVE block below, which
   overrides this `opacity`. No `transition` — the groove tracks the JS-smoothed
   beat directly (a transition here would smear the per-frame pulse). */
.pointer-label {
  opacity: calc(0.6 + var(--live) * 0.4);
}

/* CTA breath: in LIVE, the cycle subtly tightens (driven by --live, not
   per-frame — animation-duration is not a per-frame paint cost) */
.booking-h {
  animation-duration: calc(5.4s - var(--live) * 1.4s);
}

/* Soundwave label dot — live state inherits via sw-canvas-host.live class */
/* (handled in main file) */

/* Anchors for in-page nav — account for fixed header */
section[id], div[id="top"] { scroll-margin-top: 80px; }

/* ─── Section dividers ─────────────────────────────────────────────────
   A thin hairline at REST. Bends into a real-time waveform during LIVE.
   ──────────────────────────────────────────────────────────────────── */
.section-divider {
  position: relative;
  z-index: 2;
  padding: 0;
  /* When LIVE, give it a touch more breathing room since the wave needs space */
  margin: calc(0px - clamp(8px, 1vh, 16px)) 0;
}
.section-divider canvas {
  display: block;
  width: 100%;
}

/* ════════════════════════════════════════════════════════════════════════
   Click-style booking form (the BF — step-based picker UX).
   ════════════════════════════════════════════════════════════════════════ */
.bf {
  display: block;
  border: 0;
  position: relative;
  padding-top: 8px;
}
.bf-stage {
  min-height: 320px;
  animation: bfFade 0.45s cubic-bezier(.2,.7,.2,1) both;
}
@keyframes bfFade {
  from { opacity: 0; transform: translateY(8px); }
  to   { opacity: 1; transform: translateY(0); }
}

.bf-stephead {
  display: flex; flex-direction: column;
  gap: 14px;
  margin-bottom: clamp(28px, 4vh, 44px);
}
.bf-progress {
  display: flex; align-items: center; gap: 10px;
  font-family: var(--f-mono);
  font-size: 10px; letter-spacing: 0.18em; text-transform: uppercase;
  color: var(--paper-mute);
}
.bf-dot {
  display: inline-block;
  width: 22px; height: 2px;
  background: var(--hair-strong);
  border-radius: 100px;
  transition: background 0.4s, width 0.4s cubic-bezier(.2,.7,.2,1);
}
.bf-dot.done    { background: color-mix(in oklch, var(--ember) 50%, var(--hair-strong)); }
.bf-dot.current { background: var(--ember); width: 44px;
  box-shadow: 0 0 12px color-mix(in oklch, var(--ember) 60%, transparent); }
.bf-step-num {
  margin-left: 6px;
  color: var(--paper-mute);
}
.bf-q {
  font-family: var(--f-display);
  font-size: clamp(28px, 3.6vw, 48px);
  font-weight: 300; letter-spacing: -0.035em; line-height: 1.05;
  color: var(--paper);
  margin: 0;
  max-width: 22ch;
  text-wrap: balance;
}
.bf-sub {
  font-size: clamp(15px, 1.2vw, 18px);
  color: var(--paper-mute);
  margin: 0;
  max-width: 50ch;
  line-height: 1.45;
}

/* Chips */
.chips {
  display: flex; flex-wrap: wrap;
  gap: 10px 10px;
  margin-bottom: 8px;
}
.chip {
  appearance: none; cursor: pointer;
  background: color-mix(in oklch, var(--ink-2) 92%, transparent);
  color: var(--paper);
  border: 1px solid var(--hair-strong);
  border-radius: 100px;
  padding: 14px 22px;
  font-family: var(--f-display);
  font-weight: 400;
  font-size: clamp(15px, 1.15vw, 18px);
  letter-spacing: -0.01em;
  display: inline-flex; align-items: center; gap: 12px;
  transition: background 0.25s, border-color 0.25s, color 0.25s,
              transform 0.18s cubic-bezier(.2,.7,.2,1), box-shadow 0.25s;
  position: relative;
}
.chip:hover {
  border-color: color-mix(in oklch, var(--ember) 40%, var(--hair-strong));
  background: color-mix(in oklch, var(--ink-2) 70%, var(--ember-deep) 30%);
  transform: translateY(-1px);
}
.chip:active { transform: translateY(0); }
.chip.active {
  background: var(--ember);
  color: var(--ink-0);
  border-color: var(--ember);
  box-shadow: 0 6px 28px color-mix(in oklch, var(--ember) 45%, transparent),
              0 0 0 4px color-mix(in oklch, var(--ember) 15%, transparent);
  font-weight: 500;
}
.chip-mark {
  display: inline-block;
  width: 8px; height: 8px;
  border-radius: 100px;
  border: 1.5px solid color-mix(in oklch, var(--paper-mute) 60%, transparent);
  transition: background 0.25s, border-color 0.25s, transform 0.25s;
  flex-shrink: 0;
}
.chip.active .chip-mark {
  background: var(--ink-0);
  border-color: var(--ink-0);
  transform: scale(1.2);
}

/* Multi-select hint */
.bf-multinote {
  font-family: var(--f-mono);
  font-size: 10px; letter-spacing: 0.18em; text-transform: uppercase;
  color: var(--paper-mute);
  margin-top: 12px;
  opacity: 0.7;
}

/* "Or" connector */
.bf-or {
  font-family: var(--f-mono);
  font-size: 10px; letter-spacing: 0.18em; text-transform: uppercase;
  color: var(--paper-mute);
  margin: 24px 0 12px;
  display: flex; align-items: center; gap: 14px;
}
.bf-or::before, .bf-or::after {
  content: ""; flex: 1; height: 1px;
  background: var(--hair);
}

/* Inline text fields */
.bf-fields { display: grid; grid-template-columns: 1fr; gap: 4px; }
@media (min-width: 700px) { .bf-fields { grid-template-columns: 1fr 1fr; } .bf-fields > .bf-field:last-child { grid-column: 1 / -1; } }

.bf-field {
  display: flex; flex-direction: column; gap: 8px;
  padding: 16px 0;
  border-bottom: 1px solid var(--hair);
  transition: border-color 0.3s;
}
.bf-field:focus-within { border-color: color-mix(in oklch, var(--ember) 40%, var(--hair-strong)); }
.bf-field label {
  font-family: var(--f-mono);
  font-size: 10px; letter-spacing: 0.2em; text-transform: uppercase;
  color: var(--paper-mute);
  display: flex; gap: 8px; align-items: baseline;
}
.bf-field label .opt {
  text-transform: none;
  letter-spacing: 0;
  font-size: 11px;
  color: var(--ink-4);
}
.bf-field input, .bf-field textarea {
  appearance: none;
  background: transparent;
  border: 0; outline: 0;
  color: var(--paper);
  font-family: var(--f-display);
  font-size: clamp(18px, 1.4vw, 22px);
  font-weight: 300;
  letter-spacing: -0.015em;
  width: 100%;
  padding: 0;
  resize: none;
  line-height: 1.4;
}
.bf-field input::placeholder, .bf-field textarea::placeholder { color: var(--ink-4); font-weight: 300; }

/* Review */
.bf-review {
  display: grid;
  grid-template-columns: 1fr;
  gap: 0;
  border-top: 1px solid var(--hair);
  margin: 0;
}
@media (min-width: 600px) {
  .bf-review { grid-template-columns: 1fr 1fr; }
  .bf-review > .full { grid-column: 1 / -1; }
}
.bf-review > div {
  display: flex; flex-direction: column; gap: 6px;
  padding: 16px 0;
  border-bottom: 1px solid var(--hair);
}
@media (min-width: 600px) {
  .bf-review > div:nth-child(odd):not(.full) { padding-right: 32px; border-right: 1px solid var(--hair); }
  .bf-review > div:nth-child(even):not(.full) { padding-left: 32px; }
}
.bf-review dt {
  font-family: var(--f-mono);
  font-size: 10px; letter-spacing: 0.2em; text-transform: uppercase;
  color: var(--paper-mute);
}
.bf-review dd {
  font-family: var(--f-display);
  font-weight: 300;
  font-size: clamp(17px, 1.35vw, 21px);
  letter-spacing: -0.015em;
  color: var(--paper);
  margin: 0;
}

/* Controls */
.bf-controls {
  margin-top: clamp(32px, 4vh, 56px);
  padding-top: 24px;
  border-top: 1px solid var(--hair);
  display: flex; align-items: center; justify-content: space-between; gap: 16px;
  flex-wrap: wrap;
}
.bf-back {
  appearance: none; background: transparent;
  border: 1px solid var(--hair-strong);
  color: var(--paper-mute);
  font-family: var(--f-mono);
  font-size: 11px; letter-spacing: 0.18em; text-transform: uppercase;
  padding: 12px 18px;
  border-radius: 100px;
  cursor: pointer;
  display: inline-flex; align-items: center; gap: 10px;
  transition: color 0.2s, border-color 0.2s, transform 0.15s;
}
.bf-back:hover:not(:disabled) {
  color: var(--paper);
  border-color: color-mix(in oklch, var(--ember) 50%, var(--hair-strong));
  transform: translateX(-2px);
}
.bf-back:disabled { opacity: 0.3; cursor: not-allowed; }
.bf-meta {
  font-family: var(--f-mono);
  font-size: 11px; letter-spacing: 0.16em; text-transform: uppercase;
  color: var(--paper-mute);
}
.bf-meta .placeholder {
  color: var(--ember);
  border: 1px dashed color-mix(in oklch, var(--ember) 40%, transparent);
  padding: 3px 8px; border-radius: 4px;
  margin-left: 8px;
}
.bf-next, .bf-send {
  /* inherit .submit base */
}
.bf-next.disabled, .submit:disabled { opacity: 0.35; cursor: not-allowed; pointer-events: none; }

@media (max-width: 600px) {
  .bf-controls { justify-content: space-between; }
  .bf-meta { order: 3; width: 100%; text-align: center; padding-top: 8px; }
}

/* ════════════════════════════════════════════════════════════════════════
   LIVE state — beat-reactive motion (performance rework).

   This used to pulse every ember-touched element by animating shadow/blur
   RADII, letter-spacing, font-variation-settings and gradient positions —
   ~50 elements, each forcing a per-frame re-rasterization. That collapsed
   mobile framerate.

   The come-alive is now a deliberate handful of cheap moves. Off --beat-*,
   the ONLY animated properties here are transform, opacity and color:
     • the hero name + the ember "3"  — scale on the bass kick
     • the hero headline "the night." — scale + opacity glow pulse
   The atmosphere-drift glow and the spotlight beams (above) carry the rest
   of the LIVE energy via transform: scale() + opacity.

   Everything else gets at most a one-time colour settle driven by --live
   (not --beat-*), so the room still reads warmer when it ignites, with zero
   per-frame paint cost. The booking form stays capped (conversion guardrail).
   ════════════════════════════════════════════════════════════════════════ */

/* Hero name — the bass kick scales it. transform only: no drop-shadow radius,
   no letter-spacing animation. transform is GPU-composited, not re-rastered.
   Amplitude raised from 0.022 → 0.04 so the pulse actually reads (the perf
   fix kept hero reactivity but it had become too subtle to perceive).
   NOTE: no CSS `transition` — see the GROOVE block below. audio.jsx already
   smooths the beat bands in JS every frame; a CSS transition on top is both
   redundant and (multiplied across many elements) a real per-frame cost. */
.hero-name {
  transform: scale(calc(1 + var(--beat-low) * 0.07));
  transform-origin: left bottom;
  will-change: transform;
}
/* The ember "3" punches a little harder and warms toward ember-glow. */
.hero-name .num3 {
  transform: translateY(-0.02em) scale(calc(1 + var(--beat-low) * 0.22));
  display: inline-block;
  color: color-mix(in oklch, var(--ember) calc(100% - var(--beat-mid) * 55%), var(--ember-glow));
  will-change: transform;
}

/* Hero headline "the night." — the signature LIVE glow. The em scales and
   brightens on the beat (transform + opacity only). The warm gradient fill
   is STATIC — animating its background-position repainted the clipped text
   every frame. A drop-shadow-free, fixed-position gradient reads the same.
   Scale raised 0.03 → 0.05 so the signature pulse is clearly perceptible. */
.hero-headline em {
  background: linear-gradient(120deg,
    var(--ember-glow) 0%,
    var(--paper) 42%,
    var(--ember) 62%,
    var(--ember-glow) 100%);
  background-size: 160% 100%;
  background-position: 30% 50%;
  -webkit-background-clip: text;
  background-clip: text;
  color: transparent;
  display: inline-block;
  transform: scale(calc(1 + var(--beat-mid) * 0.11));
  transform-origin: left center;
  opacity: calc(0.8 + var(--beat-mid) * 0.2 + var(--beat-energy) * 0.05);
  will-change: transform, opacity;
}
/* The plain words of the hero headline ("I design") ride the bass gently so
   the whole line breathes as one, not just the em. */
.hero-headline {
  transform: scale(calc(1 + var(--beat-low) * 0.045));
  transform-origin: left center;
}

/* Hero eyebrow — one-time warm settle when LIVE ignites (driven by --live). */
.hero-eyebrow {
  color: color-mix(in oklch, var(--paper-mute) calc(100% - var(--live) * 25%), var(--ember-glow));
  transition: color 0.45s ease-out;
}

/* Section headings — one-time warm settle on the number when LIVE ignites. */
.sec-h .num {
  color: color-mix(in oklch, var(--ember) calc(100% - var(--live) * 25%), var(--ember-glow));
  transition: color 0.45s ease-out;
}

/* Booking — the CTA is the headline event. The em keeps its calm shimmer
   (the ctaShine keyframe, which animates background-position on ONE element —
   acceptable) plus a one-time warm settle. No per-frame beat radius work. */
.booking-h em {
  text-shadow: 0 0 calc(var(--live) * 28px) color-mix(in oklch, var(--ember) 40%, transparent);
  transition: text-shadow 0.5s ease-out;
}

/* Form — capped zone. Labels get a one-time warm settle; inputs stay static
   (the user is typing — do not move it). No --beat-* reactivity here. */
.field label {
  color: color-mix(in oklch, var(--paper-mute) calc(100% - var(--live) * 18%), var(--ember));
  transition: color 0.4s ease-out;
}
.field input, .field textarea { transition: none; }

/* Atmosphere drift live boost is handled in the main .atmos-drift rule above
   (opacity + transform: scale() driven by --beat-*, blur radius constant). */

/* The whole-page LIVE lift is carried by the atmosphere-drift, spotlight and
   particle layers themselves — NOT by an animated filter on .atmos. Animating
   brightness()/saturate() on a full-viewport element (plus its blended grain
   ::after) forced a per-frame re-raster of the entire background. */

/* ════════════════════════════════════════════════════════════════════════
   TITLE + SUBTITLE GROOVE — the come-alive, restored perf-safely.

   The original design had the titles, subtitles and named items pulse with
   the music. The performance rework (necessary — mobile was at 5fps) cut
   beat-reactivity down to a handful of elements and the groove left the
   page's text. This block puts it back — and only the safe way.

   ── HARD RULE ─────────────────────────────────────────────────────────────
   Off --beat-*, these rules animate ONLY `transform` (scale), `opacity` and
   `color`. NEVER a blur / shadow / text-shadow / drop-shadow / filter RADIUS,
   never backdrop-filter, letter-spacing, width or font-variation-settings —
   those re-rasterize the element every frame and are exactly what collapsed
   the framerate. transform and opacity are GPU-composited and effectively
   free per frame.

   ── THE ONE PERF DETAIL THAT MATTERS: no CSS `transition` ─────────────────
   These rules carry NO `transition`. That is deliberate and load-bearing.

   audio.jsx writes --beat-* every animation frame (60fps), and it ALREADY
   smooths the FFT bands in JS (the lerp() in its tick loop). So the var
   values arriving at CSS are already smooth. Adding a CSS `transition` on
   top does two bad things: (1) it double-smooths (mushier groove), and
   (2) — measured — it is the real per-frame cost. A `transition` registers
   the element with the CSS animation engine; with the source var changing
   every frame, every transitioned element runs an animation tick every
   frame. Across ~22 grooving elements on a 4× CPU-throttled phone that
   measured at roughly −6 fps. Removing the transitions: the SAME full groove
   (scale + opacity on all ~22 elements) measured framerate-neutral vs. no
   groove at all (within ±1 fps). The groove tracks the JS-smoothed bands
   directly, frame-perfect, for free.

   Because the groove is now genuinely free, it runs in FULL on every
   viewport — no mobile cutdown is needed. (If a future change reintroduces
   a cost, the lever per the brief is to drop reactive elements on phone
   widths — but as built, mobile holds the perf floor with the full groove.)

   ── REST safety ───────────────────────────────────────────────────────────
   Every opacity groove uses the form  calc(1 - var(--live)*D + var(--beat-X)*D).
   At REST (--live = 0, --beat-* = 0) it resolves to exactly 1 — no dimming.
   In LIVE the --live term drops the baseline by D and the --beat term pulses
   it back up, so opacity rides in [1-D, 1], brightening on each hit. Each
   scale() likewise resolves to scale(1) at REST. The page is perfectly still
   at REST; it only grooves once LIVE.

   ── Musicality ────────────────────────────────────────────────────────────
   Different text rides different FFT bands so the page grooves like an
   instrument, not a metronome:
     • big section headings  → --beat-low  (the bass kick — a broad swell)
     • body / sub-copy lines → --beat-mid  (a finer breath)
     • named items           → --beat-mid
     • small mono labels     → --beat-high (the hi-hat — a quick shimmer)
   transform-origin is `left` on every rule: the headings and copy are
   left-aligned, so scaling from centre would visibly shift the left edge.
   ════════════════════════════════════════════════════════════════════════ */

/* Promote every grooving text element to its own GPU compositing layer so
   the per-frame scale() is a cheap texture transform. */
.sec-h h2, .sec-h .num,
.sec-intro, .sounds-closer, .booking-sub, .rooms-footer,
.night-name, .sound-name, .night-body, .sound-body,
.night-name .sup, .sound-num,
.room, .about-body, .about-body .holly,
.booking-h, .booking-recap, .pointer-label {
  will-change: transform, opacity;
}

/* ── Big section headings — ride the bass. A broad swell + brightness breath.
   The ::before hairline rides along with the heading, which is fine. */
.sec-h h2 {
  transform: scale(calc(1 + var(--beat-low) * 0.082));
  transform-origin: left center;
  opacity: calc(1 - var(--live) * 0.16 + var(--beat-low) * 0.16);
}
/* Section heading number — a punch on the bass kick. (Its one-time --live
   colour settle is set by the earlier .sec-h .num rule and still applies.) */
.sec-h .num {
  transform: scale(calc(1 + var(--beat-low) * 0.10));
  transform-origin: left center;
  opacity: calc(1 - var(--live) * 0.22 + var(--beat-low) * 0.22);
}

/* ── Subtitle / intro / sub-copy lines — ride the mids. A finer scale pulse
   with a clear opacity breath so the copy feels like it inhales with the beat. */
.sec-intro,
.sounds-closer,
.booking-sub,
.rooms-footer {
  transform: scale(calc(1 + var(--beat-mid) * 0.052));
  transform-origin: left center;
  opacity: calc(1 - var(--live) * 0.22 + var(--beat-mid) * 0.22);
}

/* ── Named items — the night names, sound names. These are the nouns of the
   page; give them a confident mid-band groove. ──────────────────────────── */
.night-name,
.sound-name {
  transform: scale(calc(1 + var(--beat-mid) * 0.07));
  transform-origin: left center;
  opacity: calc(1 - var(--live) * 0.16 + var(--beat-mid) * 0.16);
}
/* Body copy under each named item — a gentle mid-band breath. */
.night-body,
.sound-body {
  transform: scale(calc(1 + var(--beat-mid) * 0.042));
  transform-origin: left center;
  opacity: calc(1 - var(--live) * 0.22 + var(--beat-mid) * 0.22);
}

/* ── Small mono accent labels — an energy-band shimmer + a warm colour
   lift toward ember-glow. colour is an allowed channel and cheap on these
   tiny labels; the bigger headings deliberately take no beat colour. */
.night-name .sup,
.sound-num {
  transform: scale(calc(1 + var(--beat-energy) * 0.085));
  transform-origin: left center;
  opacity: calc(1 - var(--live) * 0.22 + var(--beat-energy) * 0.22);
  color: color-mix(in oklch, var(--ember) calc(100% - var(--beat-energy) * 40%), var(--ember-glow));
}

/* ── Venue rows — ride the mids. .room is a flexbox row; the transform scales
   the whole row. Its ::before dot keeps its own static styling and hover
   transition, untouched — no beat-driven shadow radius on it. The base .room
   rule's `transition: color 0.3s, padding-left 0.3s` (for hover) still
   applies; it does NOT cover transform/opacity, so the beat channels update
   instantly — which is what we want. */
.room {
  transform: scale(calc(1 + var(--beat-mid) * 0.056));
  transform-origin: left center;
  opacity: calc(1 - var(--live) * 0.24 + var(--beat-mid) * 0.24);
}

/* ── About — the human line. The whole sentence breathes on the mids; the
   "H°LLY H°LLAND" name punches a little harder so it stays the focal word. */
.about-body {
  transform: scale(calc(1 + var(--beat-mid) * 0.046));
  transform-origin: left center;
  opacity: calc(1 - var(--live) * 0.16 + var(--beat-mid) * 0.16);
}
.about-body .holly {
  display: inline-block;
  transform: scale(calc(1 + var(--beat-low) * 0.10));
  transform-origin: left center;
}

/* ── Booking headline — the conversion title. Rides the bass like the other
   big headings. It already carries the `ctaBreath` keyframe (which animates
   text-shadow — a fixed, pre-existing CSS animation, NOT --beat-* driven, so
   it is within the hard rule); `transform` and `opacity` are independent
   channels and do not conflict with that keyframe. ──────────────────────── */
.booking-h {
  transform: scale(calc(1 + var(--beat-low) * 0.072));
  transform-origin: left center;
  opacity: calc(1 - var(--live) * 0.14 + var(--beat-low) * 0.14);
}

/* ── Booking recap chips — the little run of past nights/venues above the
   headline. A faint energy-band shimmer ties them into the groove. ───────────── */
.booking-recap {
  transform: scale(calc(1 + var(--beat-energy) * 0.075));
  transform-origin: left center;
  opacity: calc(1 - var(--live) * 0.20 + var(--beat-energy) * 0.20);
}

/* ── Pointer label — the earlier rule lifts it on --live (0.6 + --live*0.4).
   Keep that --live lift and ADD a mid-band beat breath on top: at REST the
   --beat term is 0 so the earlier rule's value stands; in LIVE it grooves. */
.pointer-label {
  transform: scale(calc(1 + var(--beat-mid) * 0.06));
  transform-origin: left center;
  opacity: calc(0.6 + var(--live) * 0.2 + var(--beat-mid) * 0.3);
}
