/* ---------- fonts (self-hosted) ----------
   Both families are OFL, so they ship with the site. The third-party stylesheet
   they used to arrive through sat in <head> and was a single point of failure
   for first paint on exactly the mobile traffic this site is built for: a DNS
   lookup, a TLS handshake and a CSS round trip to fonts.googleapis.com before
   the first byte of a font could even be requested from fonts.gstatic.com.

   These are the same binaries Google serves (v25 / v17), with Google's own
   descriptors and unicode-ranges kept byte-for-byte, so rendering and subset
   selection are unchanged. Archivo is a two-axis variable font — fvar declares
   wght 100..900 and wdth 62..125 with gvar/HVAR/avar intact — and the whole
   file ships untouched, so `font-stretch: 118%` on the hero and the 800/900
   weights everywhere else still interpolate. font-display: swap is preserved.
   Only the latin subsets are preloaded in <head>; the rest are fetched on
   demand by unicode-range as before. */
@font-face {
  font-family: 'Archivo';
  font-style: normal;
  font-weight: 100 900;
  font-stretch: 62% 125%;
  font-display: swap;
  src: url(fonts/archivo-vf-latin.woff2) format('woff2');
  unicode-range: U+0000-00FF, U+0131, U+0152-0153, U+02BB-02BC, U+02C6, U+02DA, U+02DC, U+0304, U+0308, U+0329, U+2000-206F, U+20AC, U+2122, U+2191, U+2193, U+2212, U+2215, U+FEFF, U+FFFD;
}
@font-face {
  font-family: 'Archivo';
  font-style: normal;
  font-weight: 100 900;
  font-stretch: 62% 125%;
  font-display: swap;
  src: url(fonts/archivo-vf-latin-ext.woff2) format('woff2');
  unicode-range: U+0100-02BA, U+02BD-02C5, U+02C7-02CC, U+02CE-02D7, U+02DD-02FF, U+0304, U+0308, U+0329, U+1D00-1DBF, U+1E00-1E9F, U+1EF2-1EFF, U+2020, U+20A0-20AB, U+20AD-20C0, U+2113, U+2C60-2C7F, U+A720-A7FF;
}
@font-face {
  font-family: 'Archivo';
  font-style: normal;
  font-weight: 100 900;
  font-stretch: 62% 125%;
  font-display: swap;
  src: url(fonts/archivo-vf-vietnamese.woff2) format('woff2');
  unicode-range: U+0102-0103, U+0110-0111, U+0128-0129, U+0168-0169, U+01A0-01A1, U+01AF-01B0, U+0300-0301, U+0303-0304, U+0308-0309, U+0323, U+0329, U+1EA0-1EF9, U+20AB;
}
@font-face {
  font-family: 'Space Mono';
  font-style: normal;
  font-weight: 400;
  font-display: swap;
  src: url(fonts/spacemono-400-latin.woff2) format('woff2');
  unicode-range: U+0000-00FF, U+0131, U+0152-0153, U+02BB-02BC, U+02C6, U+02DA, U+02DC, U+0304, U+0308, U+0329, U+2000-206F, U+20AC, U+2122, U+2191, U+2193, U+2212, U+2215, U+FEFF, U+FFFD;
}
@font-face {
  font-family: 'Space Mono';
  font-style: normal;
  font-weight: 400;
  font-display: swap;
  src: url(fonts/spacemono-400-latin-ext.woff2) format('woff2');
  unicode-range: U+0100-02BA, U+02BD-02C5, U+02C7-02CC, U+02CE-02D7, U+02DD-02FF, U+0304, U+0308, U+0329, U+1D00-1DBF, U+1E00-1E9F, U+1EF2-1EFF, U+2020, U+20A0-20AB, U+20AD-20C0, U+2113, U+2C60-2C7F, U+A720-A7FF;
}
@font-face {
  font-family: 'Space Mono';
  font-style: normal;
  font-weight: 400;
  font-display: swap;
  src: url(fonts/spacemono-400-vietnamese.woff2) format('woff2');
  unicode-range: U+0102-0103, U+0110-0111, U+0128-0129, U+0168-0169, U+01A0-01A1, U+01AF-01B0, U+0300-0301, U+0303-0304, U+0308-0309, U+0323, U+0329, U+1EA0-1EF9, U+20AB;
}
@font-face {
  font-family: 'Space Mono';
  font-style: normal;
  font-weight: 700;
  font-display: swap;
  src: url(fonts/spacemono-700-latin.woff2) format('woff2');
  unicode-range: U+0000-00FF, U+0131, U+0152-0153, U+02BB-02BC, U+02C6, U+02DA, U+02DC, U+0304, U+0308, U+0329, U+2000-206F, U+20AC, U+2122, U+2191, U+2193, U+2212, U+2215, U+FEFF, U+FFFD;
}
@font-face {
  font-family: 'Space Mono';
  font-style: normal;
  font-weight: 700;
  font-display: swap;
  src: url(fonts/spacemono-700-latin-ext.woff2) format('woff2');
  unicode-range: U+0100-02BA, U+02BD-02C5, U+02C7-02CC, U+02CE-02D7, U+02DD-02FF, U+0304, U+0308, U+0329, U+1D00-1DBF, U+1E00-1E9F, U+1EF2-1EFF, U+2020, U+20A0-20AB, U+20AD-20C0, U+2113, U+2C60-2C7F, U+A720-A7FF;
}
@font-face {
  font-family: 'Space Mono';
  font-style: normal;
  font-weight: 700;
  font-display: swap;
  src: url(fonts/spacemono-700-vietnamese.woff2) format('woff2');
  unicode-range: U+0102-0103, U+0110-0111, U+0128-0129, U+0168-0169, U+01A0-01A1, U+01AF-01B0, U+0300-0301, U+0303-0304, U+0308-0309, U+0323, U+0329, U+1EA0-1EF9, U+20AB;
}

/* Taz Angullia — tokens */
:root {
  --ink: #0a0a0a;
  --paper: #f2f2f0;
  --rule: #262624;
  --rule-soft: #1c1c1b;
  --muted: #b8b8b3;
  --dim: #8a8a84;
  --dimmer: #6f6f6b;
  --dimmest: #5a5a56;
  --panel: #0d0d0c;

  --pad: clamp(20px, 4vw, 56px);
  --section-y: clamp(44px, 6vw, 80px);
  --sans: 'Archivo', 'Helvetica Neue', Helvetica, Arial, sans-serif;
  --mono: 'Space Mono', ui-monospace, SFMono-Regular, Menlo, monospace;
  /* The source photography is club-dark (mean luminance ~34-75/255). Against a
     #0a0a0a ground a straight grayscale pass reads as an empty box, so the grade
     lifts midtones and holds contrast to keep the subject off the page black. */
  --grade: grayscale(1) contrast(1.16) brightness(1.22);
  --grade-hover: grayscale(.5) contrast(1.12) brightness(1.16);
  --ease: cubic-bezier(.2, .7, .3, 1);
}

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

/* Several components set an explicit `display`, which silently beats the UA rule
   for [hidden]. Without this the "enquiry sent" panel renders beside the form. */
[hidden] { display: none !important; }

/* Backstop only — the real overflow was the player stage's height floor
   transferring back through its aspect-ratio, and that is fixed at source.
   Containment lives on the root and ONLY on the root: `clip` does not create a
   scroll container, so the sticky topbar and the scroll-progress rail are
   untouched. It must not be paired with `overflow-x: hidden` on <body> —
   `hidden` computes `overflow-y: auto`, which turns <body> into a scroll
   container and silently kills `position: sticky` on .topbar sitewide. Safari
   15 and older ignore `clip`; see the note on <body> for that fallback. */
html { scroll-behavior: smooth; scroll-padding-top: 84px; overflow-x: clip; }

/* Deliberately NO overflow-x here. On Safari <16 `clip` above is dropped, so a
   stray overflowing child could pan horizontally — but every such source is
   fixed at origin, and a sticky header that never sticks is a far worse defect
   than a hypothetical pan on a three-year-old browser. Do not re-add it. */
body {
  margin: 0;
  background: var(--ink);
  color: var(--paper);
  font-family: var(--sans);
  -webkit-font-smoothing: antialiased;
  text-rendering: optimizeLegibility;
}

img, video, iframe { display: block; max-width: 100%; }

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

:focus-visible {
  outline: 2px solid var(--paper);
  outline-offset: 3px;
}

.sr-only {
  position: absolute; width: 1px; height: 1px; padding: 0; margin: -1px;
  overflow: hidden; clip: rect(0,0,0,0); white-space: nowrap; border: 0;
}

.skip {
  position: absolute; left: -9999px; top: 0; z-index: 100;
  background: var(--paper); color: var(--ink);
  font-family: var(--mono); font-size: 12px; padding: 12px 18px;
}
.skip:focus { left: 0; }

/* ---------- shared type ---------- */
.mono { font-family: var(--mono); letter-spacing: .16em; }

/* .label always carries text — form labels, panel keys, contact keys — at 10px.
   The base used to be --dimmer (3.92:1, fails AA) and every call site patched it
   back up in a scoped rule, which is a trap waiting for the next .label added.
   The floor now passes on its own at --dim (5.70:1); the scoped rules below that
   lift specific groups further (--muted, --paper) still win and are unchanged.
   --dimmer keeps all its non-text jobs (hairlines, decorative marks) untouched. */
.label {
  margin: 0;
  font-family: var(--mono); font-size: 10px; letter-spacing: .2em;
  text-transform: uppercase; color: var(--dim);
}

.eyebrow {
  margin: 0 0 28px;
  font-family: var(--mono); font-size: 11px; letter-spacing: .24em;
  text-transform: uppercase; color: var(--paper); opacity: .5;
}

.display {
  margin: 0;
  font-size: clamp(40px, 6vw, 82px);
  font-weight: 900; font-stretch: 118%;
  letter-spacing: -.03em; line-height: 1;
}

.link-underline {
  font-size: 11px; letter-spacing: .2em; text-transform: uppercase;
  border-bottom: 1px solid var(--dimmer); padding-bottom: 3px;
  transition: color .25s var(--ease), border-color .25s var(--ease);
}
.link-underline:hover { color: var(--paper); border-color: var(--paper); }

.ta-r { text-align: right; }

/* ---------- topbar ---------- */
.topbar {
  position: sticky; top: 0; z-index: 40;
  display: flex; justify-content: space-between; align-items: center;
  gap: 20px; padding: 18px var(--pad);
  background: color-mix(in srgb, var(--ink) 88%, transparent);
  backdrop-filter: blur(14px);
  border-bottom: 1px solid var(--rule);
}

.brand { display: flex; align-items: center; gap: 14px; min-width: 0; }
.brand img {
  height: 34px; width: auto;
  transition: transform .35s var(--ease), opacity .35s var(--ease);
}
.brand:hover img { transform: scale(1.06); }
.brand__word {
  font-size: clamp(13px, 1.5vw, 20px); font-weight: 900; font-stretch: 118%;
  letter-spacing: .02em; line-height: 1; white-space: nowrap;
}

.nav {
  display: flex; align-items: center; gap: clamp(16px, 2.4vw, 36px);
  font-family: var(--mono); font-size: 12px;
  letter-spacing: .16em; text-transform: uppercase;
}
.nav a { transition: color .25s var(--ease); }

/* `display: contents` keeps the desktop bar identical to before — the four
   links stay direct flex children of .nav. The wrapper only becomes a box at
   the mobile breakpoint, where it is the disclosure panel. */
.nav__links { display: contents; }

/* Hairline that wipes in from the left — reads as a cue-point, not a fade. */
.nav__links a { position: relative; padding-block: 5px; color: var(--muted); }
.nav__links a::after {
  content: ''; position: absolute; left: 0; right: 0; bottom: 0; height: 1px;
  background: var(--paper);
  transform: scaleX(0); transform-origin: 0 50%;
  transition: transform .32s var(--ease);
}
.nav__links a:hover,
.nav__links a:focus-visible { color: var(--paper); }
.nav__links a:hover::after,
.nav__links a:focus-visible::after { transform: scaleX(1); }

/* Disclosure controls. Two anchors rather than one, so aria-expanded is
   literally true of whichever one is rendered: only the "open" control exists
   while the panel is closed, only the "close" control while it is open. Both
   are out of the box (and out of the a11y tree) above 760px. */
.nav__toggle { display: none; }

.nav__cta {
  color: var(--paper);
  border: 1px solid var(--paper); padding: 9px 18px;
  white-space: nowrap;
  transition: background .25s var(--ease), color .25s var(--ease), box-shadow .25s var(--ease);
}
.nav__cta:hover, .nav__cta:focus-visible {
  background: var(--paper); color: var(--ink);
  box-shadow: 0 0 0 4px rgba(242, 242, 240, .12);
}

/* Scroll position read as a hairline on the bar's own rule. Progressive: the
   whole element stays out of the box unless scroll timelines are supported. */
.topbar__rail { display: none; }
@supports (animation-timeline: scroll()) {
  @media (prefers-reduced-motion: no-preference) {
    .topbar__rail {
      display: block; position: absolute; left: 0; right: 0; bottom: -1px;
      height: 1px; overflow: hidden; pointer-events: none;
    }
    .topbar__fill {
      display: block; height: 100%; background: var(--paper);
      transform: scaleX(0); transform-origin: 0 50%;
      animation: topbar-progress linear both;
      animation-timeline: scroll(root block);
    }
  }
}
@keyframes topbar-progress { to { transform: scaleX(1); } }

@media (max-width: 760px) {
  /* 11px block padding around 44px controls keeps the bar at ~67px, which is
     what html { scroll-padding-top } below already assumes — no layout shift. */
  .topbar { padding: 11px var(--pad); gap: 12px; }
  /* The bar now carries brand + menu + CTA at 390px. The brand is the only
     part allowed to give ground, and it truncates rather than overflowing. */
  .brand { flex: 0 1 auto; overflow: hidden; }
  .brand__word { overflow: hidden; text-overflow: ellipsis; min-width: 0; }
  .nav { flex: 0 0 auto; gap: 8px; }

  /* Book Taz is never inside the menu — it stays the persistent primary CTA. */
  .nav__cta {
    display: inline-flex; align-items: center;
    background: var(--paper); color: var(--ink);
    padding: 0 15px; min-height: 44px;
  }

  .nav__toggle {
    display: inline-flex; align-items: center; justify-content: center;
    width: 44px; min-height: 44px;
    border: 1px solid var(--dimmer);
    transition: border-color .25s var(--ease), background .25s var(--ease);
  }
  /* Closed is the default state, so only the open control is in the box — and
     therefore only aria-expanded="false" is exposed. The pair swaps on :target. */
  .nav__toggle--close { display: none; }
  .nav__toggle:hover, .nav__toggle:focus-visible {
    border-color: var(--paper); background: rgba(242, 242, 240, .07);
  }
  /* Three bars / an X drawn from borders — no icon font, no extra request. */
  .nav__bars, .nav__x { display: block; width: 18px; height: 12px; position: relative; }
  .nav__bars::before, .nav__bars::after,
  .nav__x::before, .nav__x::after {
    content: ''; position: absolute; left: 0; right: 0; height: 1.5px;
    background: var(--paper);
  }
  .nav__bars { border-top: 1.5px solid var(--paper); border-bottom: 1.5px solid var(--paper); }
  .nav__bars::before { display: none; }
  .nav__bars::after { top: 50%; margin-top: -.75px; }
  .nav__x { height: 16px; }
  .nav__x::before { top: 50%; margin-top: -.75px; transform: rotate(45deg); }
  .nav__x::after  { top: 50%; margin-top: -.75px; transform: rotate(-45deg); }

  /* The panel. Absolutely positioned inside the sticky bar, so opening it
     costs no reflow and it covers nothing while closed. */
  .nav__links {
    display: none;
    position: absolute; z-index: 1;
    left: 0; right: 0; top: 100%;
    flex-direction: column;
    padding: 4px var(--pad) 12px;
    background: color-mix(in srgb, var(--ink) 96%, transparent);
    backdrop-filter: blur(14px);
    border-bottom: 1px solid var(--rule);
  }
  .nav__links:target { display: flex; }
  .nav__links:target ~ .nav__toggle--open { display: none; }
  .nav__links:target ~ .nav__toggle--close { display: inline-flex; }

  .nav__links a {
    display: flex; align-items: center;
    min-height: 48px; padding-block: 0;
    color: var(--paper);
    border-bottom: 1px solid var(--rule-soft);
  }
  .nav__links a:last-child { border-bottom: 0; }
  /* The wipe hairline is a desktop hover cue; the rows carry their own rules. */
  .nav__links a::after { display: none; }
}

/* ---------- hero ---------- */
.hero { padding: clamp(44px, 6vw, 84px) var(--pad) 0; }

/* The name is sized against its own column, not the viewport, so the split
   layout below can take width away from it without the type ever overflowing.
   The vw pair is the fallback for engines without container queries. */
.hero__type { container-type: inline-size; min-width: 0; }

.hero__eyebrow {
  display: flex; align-items: center; gap: 12px;
  margin-bottom: clamp(18px, 2.2vw, 26px);
  opacity: 1; color: var(--muted);
}

/* Four-band level meter — the pulse cue. Transform-only, so it costs nothing. */
.vu { display: inline-flex; align-items: flex-end; gap: 2px; height: 12px; flex: none; }
.vu i {
  display: block; width: 2px; height: 100%;
  background: var(--paper); transform-origin: 50% 100%;
}
.vu i:nth-child(1) { transform: scaleY(.35); }
.vu i:nth-child(2) { transform: scaleY(.75); }
.vu i:nth-child(3) { transform: scaleY(.45); }
.vu i:nth-child(4) { transform: scaleY(.9); }
.vu i:nth-child(5) { transform: scaleY(.3); }

@media (prefers-reduced-motion: no-preference) {
  .vu i { animation: vu-beat 1.1s var(--ease) infinite alternate; }
  .vu i:nth-child(1) { animation-duration: .86s; animation-delay: -.42s; }
  .vu i:nth-child(2) { animation-duration: 1.24s; animation-delay: -.18s; }
  .vu i:nth-child(3) { animation-duration: .68s; animation-delay: -.55s; }
  .vu i:nth-child(4) { animation-duration: 1.02s; animation-delay: -.31s; }
  .vu i:nth-child(5) { animation-duration: .78s; animation-delay: -.06s; }
}
@keyframes vu-beat { from { transform: scaleY(.18); } to { transform: scaleY(1); } }

.hero__name { margin: 0; }
.hero__solid, .hero__outline { display: block; font-weight: 900; font-stretch: 118%; }
.hero__solid {
  font-size: clamp(84px, 17vw, 230px);
  font-size: clamp(84px, 30cqw, 250px);
  line-height: .78; letter-spacing: -.04em;
}
/* "ANGULLIA" measures 6.29x the font-size at this weight/stretch/tracking, so
   the ratio — not the eye — sets the cap: 15.5cqw x 6.29 = 0.975 of the column,
   which holds at every width. The 44px floor is what keeps 320-370px viewports
   inside their column too (52px x 6.29 = 327px needed 327px of a 280px column). */
.hero__outline {
  font-size: clamp(44px, 9vw, 148px);
  font-size: clamp(44px, 15.5cqw, 148px);
  line-height: .88; letter-spacing: -.035em;
  color: transparent;
  -webkit-text-stroke: clamp(1.5px, .18vw, 2px) var(--paper);
}


/* Book is always the filled one; listen sits beside it, press kit trails. */
.hero__actions {
  display: flex; flex-wrap: wrap; align-items: center;
  gap: 12px 18px; margin-top: clamp(22px, 2.6vw, 32px);
}
.hero__cta {
  display: inline-flex; align-items: center; justify-content: center; gap: 10px;
  padding: 15px 24px; border: 1px solid var(--paper);
  font-family: var(--mono); font-size: 12px; font-weight: 700;
  letter-spacing: .2em; text-transform: uppercase; white-space: nowrap;
  transition: background .28s var(--ease), color .28s var(--ease),
              border-color .28s var(--ease), transform .18s var(--ease);
}
.hero__cta--primary { background: var(--paper); color: var(--ink); }
.hero__cta--primary:hover, .hero__cta--primary:focus-visible {
  background: transparent; color: var(--paper);
}
/* --dimmer clears 3:1 against the ink ground, so the ghost button's boundary
   still meets WCAG 1.4.11 without competing with the filled primary. */
.hero__cta--ghost { border-color: var(--dimmer); color: var(--paper); }
.hero__cta--ghost:hover, .hero__cta--ghost:focus-visible {
  border-color: var(--paper); background: rgba(242, 242, 240, .07);
}
.hero__cta:active { transform: translateY(1px); }
.hero__cta-arrow { transition: transform .28s var(--ease); }
.hero__cta:hover .hero__cta-arrow,
.hero__cta:focus-visible .hero__cta-arrow { transform: translateX(5px); }

/* It sits beside two 51px CTAs and reads as a third button, so it has to be a
   real target: 12px block padding plus a 44px floor takes it from 21px to 44px
   and clears WCAG 2.2 SC 2.5.8. Centring keeps the rule close under the text. */
.hero__kit {
  display: flex; align-items: center;
  font-size: 11px; letter-spacing: .18em; text-transform: uppercase;
  color: var(--dim);
  border-bottom: 1px solid var(--dimmer);
  padding: 12px 0; min-height: 44px;
  transition: color .25s var(--ease), border-color .25s var(--ease);
}
.hero__kit:hover, .hero__kit:focus-visible { color: var(--paper); border-color: var(--paper); }

.hero__meta {
  display: flex; justify-content: space-between; align-items: flex-end;
  flex-wrap: wrap; gap: 10px 40px;
  margin-top: clamp(26px, 3vw, 38px); padding-bottom: 26px;
  border-bottom: 1px solid var(--rule);
}
.hero__meta p {
  margin: 0; font-size: 11px; text-transform: uppercase; color: var(--dim);
}
/* The territory list is the international-credibility signal — keep it on
   mobile, where the shared responsive block would otherwise drop it. */
.hero .hero__route { display: block; opacity: 1; color: var(--muted); }
.hero__route-label { color: var(--dim); margin-right: 8px; }

/* Full-bleed on phones; a bled-right column on desktop. The ratio is declared
   on the plate (not left to the image) so the box is reserved before load. */
.hero__plate {
  position: relative; overflow: hidden;
  background: var(--panel);
  aspect-ratio: 4 / 5;
  max-height: 62vh; /* keeps a 4:5 crop from running away on wide phones/tablets */
  margin: clamp(26px, 4.5vw, 40px) calc(var(--pad) * -1) 0;
}
.hero__img {
  width: 100%; height: 100%; object-fit: cover; object-position: 50% 40%;
  filter: var(--grade);
  transition: filter .6s var(--ease), transform .9s var(--ease);
}
.hero__plate:hover .hero__img { filter: var(--grade-hover); transform: scale(1.03); }
/* Sinks the plate's bottom edge into the ink so it meets the ticker rule. */
.hero__plate::after {
  content: ''; position: absolute; inset: 0; pointer-events: none;
  background: linear-gradient(to top, rgba(10, 10, 10, .62), rgba(10, 10, 10, 0) 46%);
}

@media (min-width: 800px) {
  .hero {
    display: grid;
    grid-template-columns: minmax(0, 1.9fr) minmax(0, 1fr);
    gap: clamp(28px, 3.4vw, 56px);
    align-items: end;
  }
  /* The type column sets the row height; the portrait fills it edge to edge and
     bleeds off the right, so the fold carries the face as well as the name. */
  .hero__plate {
    aspect-ratio: auto;
    max-height: none;
    align-self: stretch;
    min-height: clamp(340px, 40vw, 600px);
    margin: 0 calc(var(--pad) * -1) 0 0;
  }
}

@media (max-width: 520px) {
  .hero__eyebrow { font-size: 10px; letter-spacing: .18em; gap: 10px; }
  /* Full-width stacked targets — 390px is the primary case. */
  .hero__actions { gap: 10px; }
  .hero__cta { flex: 1 1 100%; justify-content: space-between; padding: 16px 20px; }
  .hero__kit { flex: 1 1 100%; margin-top: 4px; }
  .hero__meta { gap: 8px; }
}

@media (prefers-reduced-motion: no-preference) {
  .hero__type > * { animation: hero-rise .58s var(--ease) both; }
  .hero__eyebrow { animation-delay: .02s; }
  .hero__name    { animation-delay: .08s; }
  .hero__actions { animation-delay: .16s; }
  .hero__meta    { animation-delay: .22s; }
}
@keyframes hero-rise {
  from { opacity: 0; transform: translateY(14px); }
  to   { opacity: 1; transform: none; }
}

/* ---------- ticker ---------- */
.ticker {
  display: grid; grid-template-columns: repeat(3, 1fr);
  gap: 1px; background: var(--rule);
  border-block: 1px solid var(--rule);
}
.ticker__cell {
  position: relative;
  background: var(--ink); padding: 26px calc(var(--pad) + 26px) 26px var(--pad);
  display: flex; flex-direction: column; gap: 8px; min-width: 0;
  transition: background .3s var(--ease);
}
.ticker__cell:hover, .ticker__cell:focus-within { background: #131312; }

/* NEXT / LISTEN / BOOKINGS name the three above-the-fold routes off this page.
   The shared .label token is --dimmer (3.92:1 on ink) and fails AA at 10px, so
   these three run at --dim (5.70:1). Scoped here; the token is untouched. */
.ticker .label { color: var(--dim); }

.ticker__lead {
  font-size: clamp(17px, 1.8vw, 22px); font-weight: 700; font-stretch: 105%;
  transition: color .25s var(--ease);
  overflow-wrap: anywhere;
}
/* Stretch the link over the whole cell so the tap target is the cell, not the
   line of text — the cells stack full-width on phones. */
.ticker__lead::after { content: ''; position: absolute; inset: 0; }
.ticker__sub { margin: 0; font-size: 10px; color: var(--dim); }

/* Deliberately left on --dimmer. The glyph is aria-hidden and carries no
   information the cell's own link text does not — it is a graphical affordance,
   so 1.4.11's 3:1 applies, not 1.4.3's 4.5:1, and 3.92:1 clears it. Lifting it
   would put a decorative mark at the same weight as the label beside it. */
.ticker__arrow {
  position: absolute; top: 24px; right: var(--pad);
  font-size: 15px; line-height: 1; color: var(--dimmer);
  transition: transform .3s var(--ease), color .3s var(--ease);
}
.ticker__cell:hover .ticker__arrow,
.ticker__cell:focus-within .ticker__arrow { color: var(--paper); transform: translateX(4px); }
.ticker__cell--book:hover .ticker__arrow,
.ticker__cell--book:focus-within .ticker__arrow { transform: translate(3px, -3px); }

/* ---------- about ---------- */
/* Two full-bleed columns with the 1px rule showing through: press portrait on
   the left, the story on the right. The text column is the wider of the two —
   this section is read, not just looked at. */
.about {
  display: grid; grid-template-columns: minmax(0, .82fr) minmax(0, 1.18fr);
  gap: 1px; background: var(--rule);
}

/* --dimmer is ~3.9:1 on ink and fails AA for text this small. Labels in the
   bio and the .KRATE panel carry real information, so they run at --dim (5.7:1). */
.about .label, .krate .label { color: var(--dim); }

/* A real <img> rather than a CSS background: lazy-loading, EXIF orientation and
   object-position all stay under the browser's control. The plate owns the box
   so nothing shifts while the file is in flight. */
.about__plate {
  position: relative; margin: 0; overflow: hidden;
  min-height: clamp(520px, 60vw, 720px);
  background: var(--panel);
}
.about__img {
  position: absolute; inset: 0;
  width: 100%; height: 100%; object-fit: cover; object-position: 50% 24%;
  filter: var(--grade);
  transition: filter .6s var(--ease), transform 1.2s var(--ease);
}
.about__plate:hover .about__img { filter: var(--grade-hover); transform: scale(1.035); }

.about__stamp {
  position: absolute; left: 0; right: 0; bottom: 0;
  display: flex; flex-wrap: wrap; align-items: baseline; gap: 2px 12px;
  padding: 56px var(--pad) clamp(18px, 2.2vw, 26px);
  background: linear-gradient(to top, rgba(10, 10, 10, .85), rgba(10, 10, 10, 0));
}
.about__stamp-name {
  font-size: clamp(15px, 1.3vw, 18px); font-weight: 800; font-stretch: 105%;
  letter-spacing: -.01em;
}
.about__stamp-meta { font-size: 10px; text-transform: uppercase; color: var(--muted); }

.about__body {
  background: var(--ink); padding: var(--section-y) var(--pad);
  display: flex; flex-direction: column; gap: clamp(28px, 3.4vw, 44px);
}
.about__head { display: flex; flex-direction: column; gap: 14px; }
.about__lead {
  position: relative; margin: 0; padding-left: clamp(16px, 1.4vw, 22px);
  font-size: clamp(30px, 3.7vw, 52px); line-height: 1.04;
  font-weight: 900; font-stretch: 105%; letter-spacing: -.025em;
  text-wrap: pretty;
}
.about__lead::before {
  content: ""; position: absolute; left: 0; top: .12em; bottom: .12em; width: 3px;
  background: var(--paper); transform-origin: 50% 0;
}
.about__copy { display: flex; flex-direction: column; gap: 22px; }
.about__copy p {
  margin: 0; font-size: clamp(16px, 1.2vw, 18px); line-height: 1.6;
  color: var(--muted); max-width: 54ch; text-wrap: pretty;
}

/* The arc, stated once: where she is based and how far the diary already
   reaches. The country-by-country evidence is the dates map's job, so this
   points at it instead of reprinting the same ten names. */
.arc {
  display: grid; grid-template-columns: auto minmax(40px, 1fr) auto;
  align-items: start; gap: clamp(14px, 2vw, 26px);
  border-top: 1px solid var(--rule); padding-top: clamp(20px, 2.4vw, 28px);
}
.arc .label { margin-bottom: 10px; }
.arc__place {
  margin: 0; display: flex; align-items: center; gap: 10px;
  font-size: clamp(15px, 1.3vw, 19px); font-weight: 800; font-stretch: 104%;
  letter-spacing: -.01em;
}
/* one slow beacon on a single 8px dot — transform and opacity only */
.arc__beacon {
  position: relative; flex: none;
  width: 8px; height: 8px; border-radius: 50%; background: var(--paper);
}
.arc__beacon::after {
  content: ""; position: absolute; inset: -5px; border-radius: 50%;
  border: 1px solid var(--paper); opacity: 0;
  animation: arcBeacon 2.8s var(--ease) infinite;
}
@keyframes arcBeacon {
  0%   { transform: scale(.35); opacity: .6; }
  100% { transform: scale(1.9); opacity: 0; }
}
/* the signal running Singapore → outward: one 22%-wide segment, transform only */
/* The rest state has to be visible, not just the sweep. At --rule (#262624)
   this was 1.31:1 on --ink and read as a ~435px gap between "Base" and "Reach"
   until the animation happened to pass over it — and with reduced-motion the
   animation never runs at all. --dim is 5.70:1, well clear of the 3:1 that
   1.4.11 asks of a graphical object this thin. */
.arc__line {
  position: relative; height: 1px; overflow: hidden;
  background: var(--dim); margin-top: 33px; /* sits on the place-name midline */
}
.arc__line::after {
  content: ""; position: absolute; top: 0; bottom: 0; left: 0; width: 22%;
  background: linear-gradient(90deg, rgba(242, 242, 240, 0), var(--paper));
  animation: arcRun 3.8s var(--ease) infinite;
}
@keyframes arcRun {
  0%   { transform: translateX(-100%); }
  100% { transform: translateX(455%); }
}
.arc__link {
  display: inline-block; margin-top: 10px;
  font-size: 10px; letter-spacing: .18em; text-transform: uppercase;
  color: var(--dim);
  border-bottom: 1px solid var(--rule); padding-bottom: 3px;
  transition: color .25s var(--ease), border-color .25s var(--ease);
}
.arc__link:hover, .arc__link:focus-visible { color: var(--paper); border-color: var(--paper); }

.stats {
  margin: 0; display: grid; grid-template-columns: repeat(3, minmax(0, 1fr));
  border-top: 1px solid var(--rule);
}
.stat {
  position: relative; display: flex; flex-direction: column-reverse; gap: 6px;
  padding: clamp(18px, 2vw, 24px) 16px 0 0;
  border-right: 1px solid var(--rule);
}
.stat:last-child { border-right: 0; padding-right: 0; }
.stat::before {
  content: ""; position: absolute; top: -1px; left: 0; width: 28px; height: 1px;
  background: var(--paper); transform-origin: 0 50%;
}
.stat__value { font-size: clamp(26px, 2.6vw, 34px); font-weight: 900; font-stretch: 110%; margin: 0; }
.stat__label {
  font-family: var(--mono); font-size: 10px; letter-spacing: .16em;
  text-transform: uppercase; color: var(--dim);
}

/* The bio must hand off, never dead-end: book first, kit second. */
.about__actions { display: flex; flex-wrap: wrap; align-items: center; gap: 12px 20px; }
.about__cta {
  display: inline-flex; align-items: center; justify-content: center; gap: 10px;
  min-height: 48px; padding: 14px 24px;
  background: var(--paper); color: var(--ink); border: 1px solid var(--paper);
  font-family: var(--mono); font-size: 12px; font-weight: 700;
  letter-spacing: .2em; text-transform: uppercase; white-space: nowrap;
  transition: background .28s var(--ease), color .28s var(--ease),
              transform .18s var(--ease);
}
.about__cta:hover, .about__cta:focus-visible { background: transparent; color: var(--paper); }
.about__cta:active { transform: translateY(1px); }
.about__cta-arrow { transition: transform .28s var(--ease); }
.about__cta:hover .about__cta-arrow,
.about__cta:focus-visible .about__cta-arrow { transform: translateX(5px); }
.about__kit {
  font-size: 11px; letter-spacing: .18em; text-transform: uppercase; color: var(--dim);
  border-bottom: 1px solid var(--dimmer); padding-bottom: 3px;
  transition: color .25s var(--ease), border-color .25s var(--ease);
}
.about__kit:hover, .about__kit:focus-visible { color: var(--paper); border-color: var(--paper); }

/* Reveal choreography. app.js stamps [data-reveal] only when the observer
   exists and reduced motion is off, so these hidden states can never strand
   content — and the stagger is the section's pulse, sequenced not decorative. */
.about__body[data-reveal] .about__lead::before { transform: scaleY(0); }
.about__body[data-reveal].is-in .about__lead::before {
  transform: scaleY(1); transition: transform .8s var(--ease) .1s;
}
.about__body[data-reveal] .arc__line { transform: scaleX(0); transform-origin: 0 50%; }
.about__body[data-reveal].is-in .arc__line {
  transform: scaleX(1); transition: transform .9s var(--ease) .2s;
}
.about__body[data-reveal] .stat::before { transform: scaleX(0); }
.about__body[data-reveal].is-in .stat::before {
  transform: scaleX(1);
  transition: transform .6s var(--ease);
  transition-delay: calc(var(--i, 0) * 110ms + .3s);
}

/* ---------- film ---------- */
.film {
  position: relative; margin: 0; isolation: isolate;
  height: clamp(360px, 46vw, 560px);
  overflow: hidden; background: var(--panel);
  border-top: 1px solid var(--rule);
}
.film video {
  width: 100%; height: 100%; object-fit: cover;
  filter: var(--grade); background: var(--panel);
}
/* Two scrims, not one: the top holds the sound pill on mobile (where it moves up
   out of the caption's way), the bottom holds the title over any frame. */
.film::before {
  content: ''; position: absolute; inset: 0 0 auto 0; height: 26%; z-index: 1;
  background: linear-gradient(to bottom, rgba(10,10,10,.55), transparent);
  pointer-events: none;
}
/* Weighted for the worst case, not the average frame: the grade lifts midtones,
   so a blown-out crowd shot can sit right under the year line. At .6 alpha
   there, paper-on-scrim still clears AA. */
.film::after {
  content: ''; position: absolute; inset: auto 0 0 0; height: 62%; z-index: 1;
  background: linear-gradient(to top, rgba(10,10,10,.96), rgba(10,10,10,.6) 50%, transparent);
  pointer-events: none;
}
.film__caption {
  z-index: 2;
  position: absolute; left: var(--pad); bottom: 40px;
  display: flex; flex-direction: column; gap: 10px;
  text-shadow: 0 2px 24px rgba(0,0,0,.55);
  pointer-events: none;
}
.film__year {
  font-size: 11px; letter-spacing: .22em; text-transform: uppercase;
  color: var(--paper);
}
.film__title {
  font-size: clamp(30px, 4.6vw, 64px); font-weight: 900; font-stretch: 112%;
  letter-spacing: -.03em; line-height: .95; max-width: 14ch;
}
/* Desktop: control sits on the caption's baseline, right-aligned to the same
   optical margin. It fits — the title is capped at 14ch. */
.film > .sound { right: var(--pad); bottom: 40px; top: auto; }

/* ---------- clip state: loading / blocked / error ---------- */
/* One honest status line per clip. Empty in the happy path, so it collapses. */
.clip-note {
  font-size: 10px; letter-spacing: .18em; text-transform: uppercase;
  color: var(--paper);
}
.clip-note:empty { display: none; }

/* Full-bleed play affordance. Only rendered when playback is genuinely not
   running — reduced motion, data saver, or a refused play() — so the small
   sound pill never has to double as a hidden play button. */
.clip-play {
  position: absolute; inset: 0; z-index: 4;
  display: none; flex-direction: column; align-items: center; justify-content: center;
  gap: 14px; padding: 0; margin: 0;
  background: rgba(10,10,10,.34); color: var(--paper);
  border: 0; cursor: pointer; font: inherit;
  -webkit-backdrop-filter: blur(1px); backdrop-filter: blur(1px);
}
[data-vstate="poster"] > .clip-play,
[data-vstate="blocked"] > .clip-play { display: flex; }
.clip-play__glyph {
  width: 56px; height: 56px; flex: none;
  display: grid; place-items: center;
  border: 1px solid rgba(242,242,240,.7);
  transition: background .3s var(--ease), color .3s var(--ease), transform .3s var(--ease);
}
.clip-play__glyph::before {
  content: ''; width: 0; height: 0; margin-left: 4px;
  border-left: 13px solid currentColor;
  border-top: 8px solid transparent; border-bottom: 8px solid transparent;
}
.clip-play:hover .clip-play__glyph,
.clip-play:focus-visible .clip-play__glyph {
  background: var(--paper); color: var(--ink); transform: scale(1.07);
}
.clip-play__text { font-size: 10px; letter-spacing: .2em; text-transform: uppercase; }

/* Hairline transport. Doubles as the buffering indicator, so "something is
   happening" and "here's where you are" never need two competing widgets. */
.clip-progress {
  position: absolute; inset: auto 0 0 0; height: 2px; z-index: 3;
  overflow: hidden; background: rgba(242,242,240,.15);
  opacity: 0; transition: opacity .45s var(--ease);
  pointer-events: none;
}
[data-vstate="playing"] > .clip-progress,
[data-vstate="loading"] > .clip-progress { opacity: 1; }
[data-vstate="error"] > .clip-progress { display: none; }
.clip-progress i {
  display: block; height: 100%; width: 100%;
  background: var(--paper); transform-origin: 0 50%; transform: scaleX(0);
}
.clip-progress::after {
  content: ''; position: absolute; inset: 0 auto 0 0; width: 26%;
  background: var(--paper); opacity: 0;
}
[data-vstate="loading"] > .clip-progress::after {
  opacity: .85; animation: clip-buffer 1.25s var(--ease) infinite;
}
@keyframes clip-buffer {
  from { transform: translateX(-110%); }
  to   { transform: translateX(400%); }
}

/* sound button */
.sound {
  position: absolute; right: 14px; top: 14px; z-index: 5;
  display: inline-flex; align-items: center; gap: 10px;
  /* 44px was the target and the pill measured 43 — sub-pixel rounding eating the
     one pixel that mattered. Real vertical padding now sets the height instead of
     a min-height nothing pushes against, so it clears 44 with room to spare. */
  min-height: 48px; padding: 13px 18px;
  background: rgba(10,10,10,.7); color: var(--paper);
  border: 1px solid rgba(242,242,240,.4);
  font-family: var(--mono); font-size: 10px; letter-spacing: .16em;
  text-transform: uppercase; cursor: pointer;
  -webkit-backdrop-filter: blur(8px); backdrop-filter: blur(8px);
  transition: background .28s var(--ease), color .28s var(--ease),
              border-color .28s var(--ease), transform .18s var(--ease);
}
.sound:hover { background: var(--paper); color: var(--ink); border-color: var(--paper); }
.sound:active { transform: translateY(1px); }
.sound[aria-pressed="true"] { background: var(--paper); color: var(--ink); border-color: var(--paper); }
.sound:focus-visible { outline: 2px solid var(--paper); outline-offset: 3px; }
.sound:disabled,
.sound:disabled:hover {
  background: rgba(10,10,10,.7); color: var(--muted);
  border-color: rgba(242,242,240,.22); cursor: default; transform: none;
}
/* Three bars that move only while audio is actually on — the control's state is
   legible from across a room, and never animates for nothing. */
.sound__eq { display: flex; align-items: flex-end; gap: 2px; height: 11px; width: 12px; flex: none; }
.sound__eq i { flex: 1; background: currentColor; height: 38%; transform-origin: 50% 100%; }
.sound[aria-pressed="true"] .sound__eq i {
  height: 100%; animation: sound-eq 620ms var(--ease) infinite alternate;
}
.sound[aria-pressed="true"] .sound__eq i:nth-child(2) { animation-duration: 460ms; animation-delay: 110ms; }
.sound[aria-pressed="true"] .sound__eq i:nth-child(3) { animation-duration: 730ms; animation-delay: 60ms; }
@keyframes sound-eq { from { transform: scaleY(.28); } to { transform: scaleY(1); } }

@media (max-width: 760px) {
  /* Not enough width for a title and a pill on one line — send it back up top,
     where .film::before is waiting to hold it. */
  .film > .sound { right: 14px; top: 14px; bottom: auto; }
  .film__title { font-size: clamp(28px, 8.4vw, 40px); max-width: 13ch; }
}

/* ---------- sections ---------- */
.section-head {
  display: flex; justify-content: space-between; align-items: baseline;
  flex-wrap: wrap; gap: 12px 24px;
  padding: var(--section-y) var(--pad) 26px;
}
/* This carries real text ("Selected — 2023 / 2026") at 11px with .2em tracking.
   --dimmer is 3.92:1 on --ink and fails AA 1.4.3; --dim is 5.70:1. */
.section-head__note {
  margin: 0; font-size: 11px; letter-spacing: .2em;
  text-transform: uppercase; color: var(--dim);
}

/* ---------- gallery ---------- */
/* Six columns so cells can be cut 4+2, 2+2+2, 3+3 — an edited rhythm rather
   than a contact sheet. Row heights come from aspect-ratio, so nothing shifts. */
.grid {
  display: grid; grid-template-columns: repeat(6, 1fr);
  gap: 1px; background: var(--rule);
  border-block: 1px solid var(--rule);
}
.tile {
  position: relative; overflow: hidden; margin: 0; isolation: isolate;
  grid-column: span 2; aspect-ratio: 1 / 1.15;
  background-color: var(--panel);
}
.tile--w3 { grid-column: span 3; }
.tile--w4 { grid-column: span 4; }
.tile--ar-wide { aspect-ratio: 16 / 9; }
.tile--ar-land { aspect-ratio: 3 / 2; }
.tile--ar-sq   { aspect-ratio: 1 / 1; }

/* The photo used to live on a pseudo-element as background-image: var(--img).
   That is not lazy — a background on a *rendered* element is fetched at first
   paint however far below the fold it sits, so nine tiles cost ~1.2MB before the
   visitor scrolled a pixel. It is now a real <img loading="lazy"> in the markup.
   Absolutely positioned inside the aspect-ratio'd cell, it contributes nothing to
   layout, so it can arrive whenever it likes and CLS stays 0; overflow:hidden on
   .tile still clips the hover zoom off the 1px grid rules. */
.tile__img {
  position: absolute; inset: 0; z-index: 0;
  width: 100%; height: 100%;
  object-fit: cover; object-position: var(--pos, 50% 50%);
  filter: var(--grade);
  transition: transform .8s var(--ease), filter .55s var(--ease);
}
.tile:hover .tile__img { filter: var(--grade-hover); transform: scale(1.045); }

/* Both clips are 720x1280 phone video, so their cells are cut portrait — a
   16/9 cell would throw away most of the frame. */
/* A clip whose poster has not been promoted yet has nothing to paint, and
   var(--panel) (#0d0d0c) on var(--ink) (#0a0a0a) is not a difference an eye can
   find — the third cell read as a hole in the grid with a button floating in it.
   The cell now rests on a lit plate: a tone that is visibly a surface, so the
   cell looks like it is waiting rather than broken. It costs no request, and the
   poster paints straight over it the moment it arrives — deferral is untouched. */
.tile--film {
  aspect-ratio: 4 / 5;
  background:
    radial-gradient(120% 80% at 50% 8%, rgba(242,242,240,.05), transparent 62%),
    linear-gradient(168deg, #1b1b19, #121211 52%, var(--panel));
}
.tile--film video {
  width: 100%; height: 100%; object-fit: cover;
  object-position: 50% 38%; filter: var(--grade);
  /* transparent, not --panel: an opaque video box would repaint the resting
     plate above back to flat black while the poster is still deferred. */
  background: transparent;
}
/* Captions sit over live video whose brightness we don't control — scrim, not
   shadow. ::before is now the film tile's alone (still tiles carry a real <img>),
   so it stands on its own rather than overriding the old photo pseudo-element. */
.tile--film::before {
  content: ''; position: absolute;
  inset: auto 0 0 0; height: 52%; z-index: 1;
  background: linear-gradient(to top, rgba(10,10,10,.96), rgba(10,10,10,.5) 48%, transparent);
  pointer-events: none;
}

.tile__caption {
  position: absolute; left: 14px; bottom: 14px; right: 14px; z-index: 2;
  margin: 0; display: flex; flex-direction: column; gap: 7px;
  text-shadow: 0 1px 14px rgba(0,0,0,.7);
  pointer-events: none;
}
.tile__name { font-size: 10px; letter-spacing: .14em; text-transform: uppercase; }

/* Hong Kong is the first international target market and this is the only
   genuine HK footage — it gets the loudest cell in the grid, nothing more. */
.tile--feature .tile__name { font-size: 12px; letter-spacing: .12em; }
.tile--feature { outline: 1px solid rgba(242,242,240,.16); outline-offset: -1px; }

/* Scroll-driven, so it costs no JS and no main-thread work. Behind both a
   feature query and a motion query: when either fails the tiles are simply
   there, never stranded at opacity 0. */
@keyframes tile-in {
  from { opacity: 0; transform: translateY(24px) scale(.985); }
  to   { opacity: 1; transform: none; }
}
@supports (animation-timeline: view()) {
  @media (prefers-reduced-motion: no-preference) {
    .grid > .tile {
      animation: tile-in linear both;
      animation-timeline: view();
      animation-range: entry 0% entry 45%;
    }
  }
}

/* Mobile at 390px is the primary case. Two columns throughout — one column
   makes 1/1.15 tiles absurdly tall — and every cell is explicitly either full
   or half so auto-placement never leaves a hole. */
@media (max-width: 1000px) {
  .grid > .tile { grid-column: span 1; aspect-ratio: 1 / 1.15; }
  .grid > .tile--m-full { grid-column: span 2; aspect-ratio: 16 / 10; }
  .grid > .tile--film { grid-column: span 2; aspect-ratio: 4 / 5; }
  .tile--film video { object-position: 50% 32%; }
  .tile--feature .tile__name { font-size: 11px; }
}

@media (max-width: 520px) {
  .tile__caption { left: 12px; right: 12px; bottom: 12px; }
  .tile--film > .sound { right: 10px; top: 10px; }
}

/* ---------- dates ---------- */
/* A promoter scanning this section asks two things in order: is she working,
   and how do I book her. So the list is grouped — upcoming first, with a count —
   and the booking rail sits between the two groups instead of at the far end.
   The Status and Line-up columns are gone: grouping already encodes status, and
   the line-up read "—" on five of seven rows, so it now hangs off the event name
   only when there is one. */
.gigs { position: relative; }
.gigs > * + * { margin-top: clamp(26px, 3.4vw, 40px); }

.gigs__head {
  display: flex; align-items: baseline; justify-content: space-between; gap: 16px;
  padding: 0 var(--pad) 10px;
  border-bottom: 1px solid var(--rule);
}
.gigs__label {
  margin: 0; font-family: var(--mono); font-size: 11px; font-weight: 400;
  letter-spacing: .22em; text-transform: uppercase; color: var(--muted);
}
.gigs__count {
  font-family: var(--mono); font-size: 11px; letter-spacing: .16em;
  color: var(--dim); font-variant-numeric: tabular-nums;
}
.gigs__list { list-style: none; margin: 0; padding: 0; }

.gig {
  position: relative;
  display: grid;
  grid-template-columns: minmax(86px, .7fr) 1.9fr 1.4fr;
  column-gap: clamp(16px, 2.4vw, 40px);
  align-items: center;
  padding: 20px var(--pad);
  border-bottom: 1px solid var(--rule-soft);
  font-size: clamp(15px, 1.6vw, 23px); font-weight: 700; font-stretch: 105%;
  color: var(--paper);
  transition: background .3s var(--ease);
}
.gig:hover { background: #101010; }
/* Accent rail wipes in on hover and stays lit on the next show. Transform only,
   so it composites and costs nothing on scroll. */
.gig::before {
  content: ''; position: absolute; left: 0; top: -1px; bottom: -1px; width: 2px;
  background: var(--paper);
  transform: scaleY(0); transform-origin: 50% 0;
  transition: transform .34s var(--ease);
}
.gig:hover::before { transform: scaleY(1); }
.gig--next { background: #0e0e0d; }
.gig--next::before { transform: scaleY(1); }
/* --dim on --ink is 5.7:1; --dimmer/--dimmest would not clear AA for this text. */
.gig--played { color: var(--dim); }

.gig__date { display: flex; flex-direction: column; gap: 4px; min-width: 0; }
.gig__date time { display: flex; flex-direction: column; gap: 4px; }
.gig__dd {
  font-size: clamp(26px, 2.8vw, 40px); font-weight: 900; font-stretch: 112%;
  letter-spacing: -.02em; line-height: .86; font-variant-numeric: tabular-nums;
}
.gig__mon {
  font-family: var(--mono); font-size: 10px; font-weight: 400;
  letter-spacing: .18em; text-transform: uppercase; color: var(--dim);
}
.gig--played .gig__dd { font-weight: 800; color: var(--dim); }
.gig__tbc {
  font-family: var(--mono); font-size: 12px; font-weight: 400;
  letter-spacing: .16em; text-transform: uppercase; color: var(--dim);
}

.gig__event, .gig__venue { min-width: 0; }
.gig__event { display: flex; flex-direction: column; gap: 7px; }
.gig__title { text-wrap: balance; }
.gig__lineup {
  font-family: var(--mono); font-size: 11px; font-weight: 400; line-height: 1.6;
  letter-spacing: .1em; text-transform: uppercase; color: var(--dim);
}
.gig__venue {
  display: flex; flex-direction: column; gap: 5px;
  align-items: flex-end; text-align: right;
}
.gig__venue-name { font-size: clamp(13px, .8em, 19px); font-weight: 700; }
.gig__city {
  font-family: var(--mono); font-size: 11px; font-weight: 400;
  letter-spacing: .14em; text-transform: uppercase; color: var(--dim);
}

.gig__flag {
  display: inline-flex; align-items: center; gap: 9px; align-self: flex-start;
  font-family: var(--mono); font-size: 10px; font-weight: 400;
  letter-spacing: .2em; text-transform: uppercase; color: var(--paper);
}
.gig__flag i {
  position: relative; flex: none; width: 6px; height: 6px;
  border-radius: 50%; background: var(--paper);
}
/* One pulse on one dot — the section's only loop. Opacity + transform, and it
   is opted into rather than out of, so reduced motion never even declares it. */
@media (prefers-reduced-motion: no-preference) {
  .gig__flag i::after {
    content: ''; position: absolute; inset: 0; border-radius: 50%;
    background: var(--paper);
    animation: gig-pulse 2.6s var(--ease) infinite;
  }
}
@keyframes gig-pulse {
  0%   { transform: scale(1);   opacity: .5; }
  70%  { transform: scale(3.4); opacity: 0; }
  100% { transform: scale(3.4); opacity: 0; }
}

/* The booking rail follows the upcoming group, so BOOK is never more than one
   action away from the thing a promoter came here to read. */
.dates__cta {
  display: flex; flex-wrap: wrap; align-items: center; justify-content: space-between;
  gap: 16px 28px;
  padding: 22px var(--pad);
  background: var(--panel);
  border-block: 1px solid var(--rule);
}
.dates__cta-copy {
  margin: 0; max-width: 46ch;
  font-family: var(--mono); font-size: 12px; line-height: 1.8;
  letter-spacing: .08em; text-transform: uppercase; color: var(--muted);
}
/* .btn is authored for <button>; these two are links, so they need the box. */
.dates .btn { display: inline-flex; align-items: center; justify-content: center; }
.dates__cta .btn { padding: 15px 30px; }

/* Every announced date is inside three weeks of this build, so "no upcoming
   dates" is a state this section will really reach. It routes to the form
   instead of leaving a promoter looking at a list of things already over. */
.gigs__empty {
  display: flex; flex-direction: column; align-items: flex-start; gap: 14px;
  padding: clamp(28px, 4vw, 44px) var(--pad);
  border-bottom: 1px solid var(--rule-soft);
  background: repeating-linear-gradient(-45deg,
    rgba(242,242,240,.03) 0 2px, transparent 2px 10px);
}
.gigs__empty-title {
  margin: 0; max-width: 20ch;
  font-size: clamp(21px, 2.6vw, 32px); font-weight: 800; font-stretch: 108%;
  letter-spacing: -.01em; color: var(--paper); text-wrap: balance;
}
.gigs__empty-body {
  margin: 0; max-width: 48ch;
  font-family: var(--mono); font-size: 12px; line-height: 1.9;
  letter-spacing: .08em; text-transform: uppercase; color: var(--dim);
}
.gigs__empty .btn { margin-top: 6px; }

.gigs__skeleton span {
  display: block; height: 78px; margin: 0 var(--pad);
  border-bottom: 1px solid var(--rule-soft); opacity: .45;
}
.gigs__skeleton span::before {
  content: ''; display: block; margin-top: 28px;
  height: 18px; width: min(46%, 320px); background: #171716;
}
@media (prefers-reduced-motion: no-preference) {
  .gigs__skeleton span { animation: gigs-shim 1.3s var(--ease) infinite alternate; }
  .gigs__skeleton span:nth-child(2) { animation-delay: .12s; }
  .gigs__skeleton span:nth-child(3) { animation-delay: .24s; }
}
@keyframes gigs-shim { from { opacity: .28; } to { opacity: .6; } }

.gigs__note {
  margin: 14px 0 0; padding: 0 var(--pad);
  font-size: 11px; line-height: 1.8; letter-spacing: .12em;
  text-transform: uppercase; color: var(--dim);
}
.gigs__note[data-kind="error"] { color: #ff8a7a; }

.history {
  padding: clamp(30px, 4vw, 44px) var(--pad) var(--section-y);
  display: flex; flex-direction: column; gap: 14px;
}
/* The shared .label token is --dimmer (3.9:1) — fine as a hairline, not as 10px
   type, so this section overrides it. */
.history .label { color: var(--dim); }
.history__label { margin-top: 6px; }
.history__body {
  margin: 0; font-size: clamp(13px, 1.1vw, 15px); line-height: 2;
  letter-spacing: .06em; text-transform: uppercase; color: var(--dim);
  text-wrap: pretty;
}
.history__map {
  list-style: none; margin: 0; padding: 0;
  display: flex; flex-wrap: wrap; gap: 8px;
}
.history__map .chip {
  font-family: var(--mono); font-size: 11px; letter-spacing: .14em;
  text-transform: uppercase; color: var(--dim);
  border: 1px solid var(--rule); padding: 7px 12px;
  transition: color .25s var(--ease), border-color .25s var(--ease), background .25s var(--ease);
}
.history__map .chip:hover { color: var(--paper); border-color: #3a3a37; }
.history__map .chip--home { color: var(--muted); }
.history__map .chip--hk { color: var(--paper); border-color: rgba(242,242,240,.34); padding: 0; }
.history__map .chip--hk a { display: flex; align-items: center; gap: 8px; padding: 7px 12px; }
.history__map .chip--hk a::before {
  content: ''; flex: none; width: 5px; height: 5px;
  border-radius: 50%; background: var(--paper);
}
.history__map .chip--hk:hover { border-color: var(--paper); background: rgba(242,242,240,.07); }

/* ---------- krate ---------- */
.krate {
  display: grid; grid-template-columns: 1fr 1.1fr;
  gap: 1px; background: var(--rule); border-top: 1px solid var(--rule);
}
.krate__body {
  background: var(--ink); padding: var(--section-y) var(--pad);
  display: flex; flex-direction: column; justify-content: space-between; gap: 32px;
}
.krate__logos { display: flex; align-items: center; gap: 16px; }
.krate__logos img { filter: invert(1); width: auto; }
.krate__logos img[data-logo]:first-of-type { height: 64px; }
.krate__logos img[data-logo]:last-of-type { height: 24px; }
.krate__wordmark {
  font-size: 34px; font-weight: 900; font-stretch: 118%; letter-spacing: -.02em;
}
.krate__wordmark[hidden] { display: none; }
.krate__title {
  margin: 0; font-size: clamp(28px, 3.1vw, 44px); line-height: 1.05;
  font-weight: 900; font-stretch: 105%; letter-spacing: -.02em; text-wrap: pretty;
}
.krate__copy {
  margin: 0; font-size: clamp(16px, 1.2vw, 18px); line-height: 1.6;
  color: var(--muted); max-width: 48ch; text-wrap: pretty;
}
.krate__body .link-underline { align-self: flex-start; }

/* Same treatment as the bio plate: a real, lazily-loaded <img> in a box that is
   reserved up front, so the panel never reflows under the section below it. */
.krate__plate {
  position: relative; margin: 0; overflow: hidden;
  min-height: clamp(420px, 40vw, 560px);
  background: var(--panel);
}
.krate__img {
  position: absolute; inset: 0;
  width: 100%; height: 100%; object-fit: cover; object-position: 50% 50%;
  filter: var(--grade);
  transition: filter .6s var(--ease), transform 1.2s var(--ease);
}
.krate__plate:hover .krate__img { filter: var(--grade-hover); transform: scale(1.035); }
.krate__stamp {
  position: absolute; left: 0; right: 0; bottom: 0;
  padding: 56px var(--pad) clamp(18px, 2.2vw, 26px);
  font-size: 10px; text-transform: uppercase; color: var(--muted);
  background: linear-gradient(to top, rgba(10, 10, 10, .85), rgba(10, 10, 10, 0));
}

/* ---------- listen ---------- */
.listen { border-top: 1px solid var(--rule); }
.listen .section-head { padding-bottom: 26px; }
.player {
  margin: 0 var(--pad) var(--section-y);
  border: 1px solid var(--rule); background: var(--panel);
}
/* Two text rows on the left, the eq column pinned right and spanning both. */
.player__head {
  padding: 22px clamp(20px, 3vw, 32px) 18px;
  display: grid; grid-template-columns: minmax(0, 1fr) auto;
  align-items: center; column-gap: clamp(14px, 3vw, 30px); row-gap: 7px;
  border-bottom: 1px solid var(--rule);
}
.player__title { margin: 0; font-size: clamp(22px, 2.2vw, 28px); font-weight: 900; font-stretch: 110%; }
.player__head .label,
.player__title { grid-column: 1; }

/* The eq is decorative meter, not a control — five hairlines on the baseline. */
.player__eq {
  grid-column: 2; grid-row: 1 / 3;
  display: flex; align-items: flex-end; gap: 4px;
  height: 26px;
}
.player__eq i {
  display: block; width: 3px; height: 100%;
  background: var(--dim); transform-origin: 50% 100%;
  transform: scaleY(.3);
}
@media (prefers-reduced-motion: no-preference) {
  .player__eq i { animation: playerEq 1.1s var(--ease) infinite alternate; }
  .player__eq i:nth-child(1) { animation-duration: 1.02s; animation-delay: -.74s; }
  .player__eq i:nth-child(2) { animation-duration: .78s;  animation-delay: -.31s; }
  .player__eq i:nth-child(3) { animation-duration: 1.34s; animation-delay: -.95s; }
  .player__eq i:nth-child(4) { animation-duration: .89s;  animation-delay: -.12s; }
  .player__eq i:nth-child(5) { animation-duration: 1.17s; animation-delay: -.58s; }
}
@keyframes playerEq {
  from { transform: scaleY(.18); }
  to   { transform: scaleY(1); }
}

/* The stage reserves its box before anything loads: aspect-ratio plus a floor and
   a ceiling, so the facade -> iframe swap moves nothing. Every child is absolute
   and fills it, including the injected iframe. */
/* The min-height floor must stay under (available width x ratio-height/ratio-width)
   at every viewport. A fixed 276px floor did not: at 390px the column is ~348px,
   the floor beat 34vw, and the ratio transferred 276 x 16/7 = 631px back to the
   inline axis — a 262px page-wide horizontal pan. The floor is now viewport-relative
   and low enough that the width always wins, with max-width as a hard backstop. */
.player__stage {
  position: relative; overflow: hidden;
  aspect-ratio: 16 / 9;
  min-height: clamp(180px, 22vw, 420px);
  max-height: 480px;
  max-width: 100%;
  background: var(--ink);
}
.player .player__stage iframe {
  position: absolute; inset: 0; z-index: 1;
  width: 100%; height: 100%; border: 0;
  max-width: 100%;
}

/* ---- facade ---- */
.facade {
  position: absolute; inset: 0;
  display: block; width: 100%; height: 100%;
  padding: 0; margin: 0; border: 0; background: none;
  color: var(--paper); font: inherit; text-align: left; cursor: pointer;
  -webkit-appearance: none; appearance: none;
}
/* The stage clips, so the global 3px outset ring would be invisible here. */
.facade:focus-visible { outline: 2px solid var(--paper); outline-offset: -6px; }
.facade__img {
  position: absolute; inset: 0;
  width: 100%; height: 100%; object-fit: cover; object-position: 50% 42%;
  filter: var(--grade);
  transition: filter .5s var(--ease), transform .6s var(--ease);
}
.facade__scrim {
  position: absolute; inset: 0;
  background:
    linear-gradient(to top, rgba(6, 6, 6, .92) 0%, rgba(6, 6, 6, .62) 34%, rgba(6, 6, 6, .18) 68%, rgba(6, 6, 6, .32) 100%);
}
.facade__cue {
  position: absolute; left: 0; right: 0; bottom: 0;
  display: flex; align-items: center; gap: clamp(14px, 2vw, 20px);
  padding: clamp(18px, 2.6vw, 28px) clamp(18px, 3vw, 32px);
}
.facade__disc {
  flex: 0 0 auto;
  display: grid; place-items: center;
  width: 62px; height: 62px; border-radius: 50%;
  background: var(--paper); color: var(--ink);
  transition: transform .3s var(--ease), background .3s var(--ease), color .3s var(--ease);
}
.facade__disc svg { transform: translateX(2px); }
.facade__text { display: flex; flex-direction: column; gap: 6px; min-width: 0; }
.facade__kicker {
  font-family: var(--mono); font-size: 13px; font-weight: 700;
  letter-spacing: .3em; text-transform: uppercase; color: var(--paper);
}
.facade__sub {
  font-size: 10px; text-transform: uppercase; color: var(--muted);
  line-height: 1.5;
}
.facade:hover .facade__img,
.facade:focus-visible .facade__img { filter: var(--grade-hover); transform: scale(1.035); }
.facade:hover .facade__disc,
.facade:focus-visible .facade__disc { transform: scale(1.09); background: #fff; }
.facade:active .facade__disc { transform: scale(.97); }

/* ---- loading / error states ---- */
/* The iframe is appended after these in the DOM, so the states need an explicit
   layer or a half-painted embed would show through the loading panel. */
.player__state {
  position: absolute; inset: 0; z-index: 2;
  display: flex; flex-direction: column; align-items: center; justify-content: center;
  gap: 18px; padding: clamp(24px, 4vw, 40px); text-align: center;
  background: var(--panel);
}
/* --dimmer is ~3.8:1 on --panel, which fails AA at this size. Micro-labels inside
   the player are bumped to --dim (~5.5:1); the error headline goes full paper. */
.player__head .label,
.player__state--loading .label { color: var(--dim); }
.player__state--error .label { color: var(--paper); }

.player__bar {
  position: relative; overflow: hidden;
  width: min(300px, 66%); height: 2px; background: var(--rule);
}
.player__bar::after {
  content: ''; position: absolute; top: 0; bottom: 0; left: 0;
  width: 38%; background: var(--paper); transform: translateX(-110%);
}
@media (prefers-reduced-motion: no-preference) {
  .player__bar::after { animation: playerBar 1.5s var(--ease) infinite; }
}
@keyframes playerBar {
  from { transform: translateX(-110%); }
  to   { transform: translateX(300%); }
}
.player__errcopy {
  margin: 0; max-width: 46ch;
  font-size: 15px; line-height: 1.55; color: var(--muted); text-wrap: pretty;
}

/* ---- cta: job #2 of the whole site, so it reads as a filled button ---- */
.player__cta {
  display: inline-flex; align-items: center; justify-content: center; gap: 10px;
  min-height: 48px; padding: 14px 24px;
  background: var(--paper); color: var(--ink); border: 1px solid var(--paper);
  font-family: var(--mono); font-size: 12px; font-weight: 700;
  letter-spacing: .2em; text-transform: uppercase; white-space: nowrap;
  transition: background .28s var(--ease), color .28s var(--ease),
              transform .18s var(--ease);
}
.player__cta:hover,
.player__cta:focus-visible { background: transparent; color: var(--paper); }
.player__cta:active { transform: translateY(1px); }
.player__cta-mark { transition: transform .28s var(--ease); }
.player__cta:hover .player__cta-mark,
.player__cta:focus-visible .player__cta-mark { transform: translate(3px, -3px); }

.player__foot {
  display: flex; flex-wrap: wrap; align-items: center;
  gap: 16px clamp(20px, 3vw, 32px);
  padding: clamp(18px, 2.4vw, 26px) clamp(20px, 3vw, 32px);
  border-top: 1px solid var(--rule);
}
.player__note {
  margin: 0; flex: 1 1 220px;
  font-family: var(--mono); font-size: 10px; letter-spacing: .16em;
  text-transform: uppercase; color: var(--dim); line-height: 1.6;
}

@media (max-width: 620px) {
  .player__head { grid-template-columns: minmax(0, 1fr) auto; }
  .player__eq { height: 20px; }
  /* A 16/9 stage in a 348px column is only 196px tall — too short to play in.
     Phones get a taller box from the ratio itself, never from a height floor,
     so nothing can transfer back to the inline axis. */
  .player__stage { aspect-ratio: 4 / 3; min-height: 0; }
  .facade__cue { gap: 14px; padding: 20px; }
  .facade__disc { width: 54px; height: 54px; }
  .facade__kicker { font-size: 12px; letter-spacing: .26em; }
  .facade__sub { font-size: 9px; letter-spacing: .12em; }
  .player__foot { padding: 20px; }
  .player__cta { width: 100%; }
  .player__note { flex: 1 1 100%; }
}

/* ---------- book ---------- */
.book {
  border-top: 1px solid var(--rule);
  padding: clamp(52px, 7vw, 96px) var(--pad);
  display: flex; flex-direction: column; gap: 32px;
}
.book__head { display: flex; flex-direction: column; gap: 18px; }
.book__lead {
  margin: 0; font-size: clamp(17px, 1.6vw, 22px); line-height: 1.5;
  color: var(--muted); max-width: 60ch; text-wrap: pretty;
}
.book__meta {
  margin: 0; display: flex; flex-wrap: wrap; gap: 28px;
  font-size: 12px; text-transform: uppercase; color: var(--dim);
}

/* --dimmer (#6f6f6b on #0a0a0a) is ~3.8:1 and fails AA, so every piece of
   supporting copy in this section is lifted to --muted / --dim. */
.book .label { color: var(--muted); }
.book .field ::placeholder { color: var(--dim); }

/* Kept after the book facts row was removed: the FOOTER availability line still
   uses .book__dot. Transform/opacity only, so the reduced-motion block stops it. */
.book__dot {
  flex: none; width: 7px; height: 7px; border-radius: 50%;
  background: var(--paper); transform-origin: center;
  animation: book-pulse 2.4s var(--ease) infinite;
}
@keyframes book-pulse {
  0%, 100% { opacity: 1; transform: scale(1); }
  50% { opacity: .35; transform: scale(.72); }
}


/* ---- EPK: the asset a promoter forwards internally, so it is a target, not a link ---- */
.epk {
  display: flex; align-items: center; gap: clamp(14px, 2.5vw, 28px);
  border: 1px solid var(--rule); background: var(--panel);
  padding: clamp(18px, 2.4vw, 26px) clamp(16px, 2.5vw, 28px);
  min-height: 44px;
  transition: background .3s var(--ease), border-color .3s var(--ease), color .3s var(--ease);
}
.epk:hover { background: var(--paper); color: var(--ink); border-color: var(--paper); }
.epk__tag {
  flex: none; font-size: 11px; font-weight: 700;
  border: 1px solid currentColor; padding: 7px 10px; opacity: .75;
}
.epk__body { display: flex; flex-direction: column; gap: 5px; min-width: 0; }
.epk__title { font-size: clamp(19px, 2.2vw, 26px); font-weight: 900; letter-spacing: -.02em; }
.epk__copy { font-size: 14px; line-height: 1.45; color: var(--muted); }
.epk:hover .epk__copy { color: inherit; opacity: .7; }
.epk__go {
  margin-left: auto; flex: none; font-size: 11px; text-transform: uppercase;
  color: var(--muted); display: inline-flex; align-items: center; gap: 8px;
}
.epk:hover .epk__go { color: inherit; }
.epk__arrow { display: inline-block; transition: transform .3s var(--ease); }
.epk:hover .epk__arrow { transform: translateY(3px); }

.form {
  /* An input boundary answers to WCAG 1.4.11 (3:1), not to the decorative
     hairline token: --rule (#262624) is 1.31:1 on --ink, --edge is 3.43:1. */
  --edge: #666660;
  display: flex; flex-direction: column; gap: 1px;
  background: var(--edge); border: 1px solid var(--edge);
}
/* Rows auto-fit, so a row can wrap and leave an empty cell. Painting the rules on
   the fields instead of showing the row background keeps that gap invisible. */
.form__row { display: grid; background: var(--ink); }
/* Two fields can never leave an empty auto-fit cell (1 column or 2, both full),
   so this row carries a real 1px gap instead: a divider that occupies space and
   cannot be painted over by the next field's own background. */
.form__row--2 {
  grid-template-columns: repeat(auto-fit, minmax(240px, 1fr));
  gap: 1px; background: var(--edge);
}
.form__row--3 { grid-template-columns: repeat(auto-fit, minmax(240px, 1fr)); }
.form__row--4 { grid-template-columns: repeat(auto-fit, minmax(200px, 1fr)); }
.form__row--1 { grid-template-columns: 1fr; }

/* ---- progressive disclosure: the four required fields first, the rest on request ---- */
.form__more {
  display: flex; flex-direction: column; gap: 1px; background: var(--edge);
}
.form__summary {
  background: var(--ink); cursor: pointer; list-style: none;
  padding: 18px clamp(16px, 2.5vw, 28px); min-height: 44px;
  display: flex; align-items: center; gap: 14px; flex-wrap: wrap;
  transition: background .25s var(--ease);
}
.form__summary::-webkit-details-marker { display: none; }
.form__summary:hover { background: #101010; }
.form__summary:focus-visible { outline: 2px solid var(--paper); outline-offset: -4px; }
.form__summary-t { font-size: 16px; font-weight: 800; font-stretch: 104%; }
.form__summary-s {
  font-size: 10px; text-transform: uppercase; color: var(--dim);
  letter-spacing: .16em; line-height: 1.5;
}
.form__summary-i {
  margin-left: auto; flex: none; position: relative; width: 13px; height: 13px;
}
.form__summary-i::before, .form__summary-i::after {
  content: ''; position: absolute; inset: 50% 0 auto 0; height: 1px;
  background: var(--muted); transition: transform .3s var(--ease);
}
.form__summary-i::after { transform: rotate(90deg); }
.form__more[open] .form__summary-i::after { transform: rotate(0deg); }
.form__more[open] .form__summary { background: #101010; }

.field {
  background: var(--ink); padding: 16px clamp(16px, 2.5vw, 28px);
  display: flex; flex-direction: column; gap: 7px;
  box-shadow: 1px 0 0 0 var(--edge), 0 1px 0 0 var(--edge);
  transition: background .25s var(--ease);
}
/* The controls suppress the global focus ring (outline:none below), so the
   visible focus state has to live here — and a 1.03:1 background nudge is not
   one. The outline is inset, so it costs no layout and no shift. */
.field:focus-within {
  background: #1b1b19;
  outline: 2px solid var(--paper); outline-offset: -2px;
}
/* The control itself is the tap target, not the box that looks like one:
   padding lifts every input from 18px to >=44px tall (WCAG 2.5.8 wants 24). */
.field input, .field select, .field textarea {
  background: none; border: 0; outline: none; color: var(--paper);
  font-family: inherit; font-size: 18px; font-weight: 700; font-stretch: 105%;
  width: 100%; padding: 14px 0; color-scheme: dark;
}
.field textarea { font-weight: 400; line-height: 1.5; resize: vertical; }
.field select { cursor: pointer; background: var(--ink); }
/* --dimmest (#5a5a56) is 2.86:1 and fails AA — and before typing, the
   placeholder is the only content in the field. --dim (#8a8a84) is 5.70:1. */
.field ::placeholder { color: var(--dim); }
.field input:user-invalid, .field textarea:user-invalid { color: #ff8a7a; }
.field label { cursor: pointer; }
.field__req { color: var(--paper); opacity: .6; }
/* The error is owned by its field and wired with aria-describedby / aria-invalid,
   not just announced once in the shared status line. */
.field__err {
  margin: 0; font-family: var(--mono); font-size: 11px; line-height: 1.45;
  letter-spacing: .06em; color: #ff9c8e;
}
.field:has([aria-invalid="true"]) { background: #150e0d; box-shadow: 1px 0 0 0 var(--edge), 0 1px 0 0 var(--edge), inset 2px 0 0 0 #ff8a7a; }

.honey { position: absolute; left: -9999px; width: 1px; height: 1px; opacity: 0; }

.form__foot {
  background: var(--ink); padding: 24px clamp(16px, 2.5vw, 28px);
  display: flex; flex-wrap: wrap; gap: 20px;
  align-items: center; justify-content: space-between;
}
.form__foot-copy { display: flex; flex-direction: column; gap: 8px; min-width: 0; flex: 1 1 260px; }
.form__acts { display: flex; flex-wrap: wrap; gap: 10px; align-items: center; }
.form__hint {
  margin: 0; font-size: 11px; letter-spacing: .14em; line-height: 1.6;
  text-transform: uppercase; color: var(--muted); max-width: 52ch;
}
/* Not display:none — an aria-live region must already be in the accessibility
   tree when its text arrives, or the status is never announced. */
.form__hint:empty { height: 0; margin: 0; }
.form__hint[data-state="error"] { color: #ff9c8e; }
.form__hint[data-state="busy"], .form__hint[data-state="ok"] { color: var(--paper); }
/* The most valuable action on the site must never mislead about what it does,
   so the mail-client handoff is spelled out before the button is pressed. */
.form__note {
  margin: 0; font-size: 11px; letter-spacing: .1em; line-height: 1.65;
  color: var(--dim); max-width: 58ch; text-transform: none;
}
.form__raw {
  background: var(--ink); padding: 20px clamp(16px, 2.5vw, 28px);
  display: flex; flex-direction: column; gap: 10px;
}
.form__raw textarea {
  background: var(--panel); border: 1px solid var(--rule); color: var(--paper);
  font-family: var(--mono); font-size: 12px; line-height: 1.6;
  padding: 14px; width: 100%; resize: vertical;
}

.btn {
  background: var(--paper); color: var(--ink); border: 0;
  font-family: var(--mono); font-size: 12px; letter-spacing: .2em;
  text-transform: uppercase; font-weight: 700;
  padding: 16px 34px; cursor: pointer;
  transition: background .25s var(--ease), transform .25s var(--ease);
}
.btn:hover { background: var(--muted); }
.btn:active { transform: translateY(1px); }
.btn[disabled] { opacity: .55; cursor: default; }
.btn--ghost {
  background: none; color: var(--paper);
  border: 1px solid #3a3a37; padding: 13px 20px; font-size: 11px;
  min-height: 44px; display: inline-flex; align-items: center; justify-content: center;
}
.btn--ghost:hover { background: var(--paper); color: var(--ink); }
.btn--dark { background: var(--ink); color: var(--paper); padding: 9px 14px; font-size: 10px; }
.btn--dark:hover { background: #3a3a37; }
/* The one action the whole section exists for: a sheen on hover, a live bar
   while it works. Both are compositor-only. */
.btn--go { position: relative; overflow: hidden; min-height: 52px; }
.btn--go::before {
  content: ''; position: absolute; inset: 0 auto 0 -40%; width: 40%;
  background: linear-gradient(100deg, transparent, rgba(255,255,255,.65), transparent);
  transform: translateX(0); pointer-events: none;
}
.btn--go:hover::before { animation: btn-sheen .7s var(--ease) 1; }
@keyframes btn-sheen { to { transform: translateX(360%); } }
.btn--go[data-state="busy"]::after {
  content: ''; position: absolute; left: 0; bottom: 0; height: 2px; width: 40%;
  background: var(--ink); animation: btn-bar 1.1s var(--ease) infinite;
}
@keyframes btn-bar { 0% { transform: translateX(-100%); } 100% { transform: translateX(350%); } }

.sent {
  border: 1px solid var(--rule); background: var(--panel);
  padding: clamp(32px, 5vw, 56px);
  display: flex; flex-direction: column; gap: 14px; align-items: flex-start;
}
.sent__title {
  margin: 0; font-size: clamp(24px, 3vw, 38px);
  font-weight: 900; font-stretch: 110%; letter-spacing: -.02em;
}
.sent__copy { margin: 0; font-size: 16px; line-height: 1.5; color: var(--muted); max-width: 52ch; }
.sent:focus-visible { outline: 2px solid var(--paper); outline-offset: 3px; }
/* The panel is a .book child, so the reveal observer tags it — but it starts
   hidden, never intersects, and would appear at opacity 0. Opt it out. */
.sent[data-reveal] { opacity: 1; transform: none; }
.sent__acts { display: flex; flex-wrap: wrap; gap: 10px; margin-top: 4px; }
/* Handoff is not success. A mail client opening is a step, not a delivery, and
   the panel says so — with the enquiry still recoverable from the form below. */
.sent[data-mode="handoff"] { border-color: #3a3a37; }
.sent[data-mode="handoff"] .sent__kicker { color: var(--paper); }
.sent[data-mode="ok"] .sent__kicker { color: var(--paper); }

/* ---- follow: job #2 of the site lives here, so the real profiles get real weight ---- */
.follow { display: flex; flex-direction: column; gap: 16px; }
.follow__k { letter-spacing: .24em; }
.contact {
  display: grid; grid-template-columns: repeat(auto-fit, minmax(250px, 1fr)); gap: 1px;
  background: var(--rule); border: 1px solid var(--rule);
}
.contact__card {
  background: var(--ink); padding: clamp(24px, 3vw, 36px) clamp(16px, 3vw, 34px);
  display: flex; flex-direction: column; gap: 10px; min-width: 0;
  transition: background .3s var(--ease), color .3s var(--ease);
}
a.contact__card:hover, a.contact__card:focus-visible { background: var(--paper); color: var(--ink); }
/* On hover the card inverts, so this key composites --ink over --paper. At .55
   that landed on #727271 = 4.30:1 and missed AA at 10px; .6 gives 5.05:1 and is
   visually the same weight. */
a.contact__card:hover .label, a.contact__card:focus-visible .label { color: inherit; opacity: .6; }
.contact__value {
  font-size: clamp(18px, 2.1vw, 30px); font-weight: 900;
  letter-spacing: -.02em; overflow-wrap: anywhere; line-height: 1.05;
}
.contact__go {
  margin-top: auto; padding-top: 12px; font-size: 11px; text-transform: uppercase;
  color: var(--muted); display: inline-flex; align-items: center; gap: 8px;
}
a.contact__card:hover .contact__go { color: inherit; }
.contact__arrow { display: inline-block; transition: transform .3s var(--ease); }
a.contact__card:hover .contact__arrow { transform: translate(3px, -3px); }
/* The email cell holds two separate actions, so it never inverts as one target. */
.contact__mail {
  border-bottom: 1px solid var(--rule); padding-bottom: 6px; width: fit-content; max-width: 100%;
  transition: border-color .25s var(--ease);
}
.contact__mail:hover { border-color: var(--paper); }
.contact__copy {
  margin-top: auto; align-self: flex-start; min-height: 44px;
  background: none; color: var(--paper); border: 1px solid #3a3a37;
  font-size: 11px; letter-spacing: .18em; text-transform: uppercase;
  padding: 12px 18px; cursor: pointer;
  transition: background .25s var(--ease), color .25s var(--ease);
}
.contact__copy:hover { background: var(--paper); color: var(--ink); }
.contact__copy[data-state="ok"] { background: var(--paper); color: var(--ink); }

/* ---------- footer ---------- */
/* The last thing on the page is the two things the site is for: book her, follow her. */
.footer {
  border-top: 1px solid var(--rule);
  display: flex; flex-direction: column;
  font-size: 11px; color: var(--dim);
}
.footer__cta {
  padding: clamp(28px, 4vw, 46px) var(--pad);
  display: flex; flex-wrap: wrap; align-items: center;
  justify-content: space-between; gap: 24px 40px;
}
.footer__book {
  display: flex; flex-direction: column; gap: 10px; min-height: 44px;
}
.footer__book-k {
  font-size: 10px; letter-spacing: .22em; text-transform: uppercase; color: var(--muted);
  display: inline-flex; align-items: center; gap: 9px;
}
.footer__book-t {
  font-family: var(--sans); font-size: clamp(30px, 5vw, 56px); font-weight: 900;
  font-stretch: 112%; letter-spacing: -.03em; line-height: 1; color: var(--paper);
  display: inline-flex; align-items: center; gap: 14px;
}
.footer__book-arrow { display: inline-block; transition: transform .3s var(--ease); }
.footer__book:hover .footer__book-arrow { transform: translateX(8px); }
.footer__links { display: flex; flex-wrap: wrap; gap: 8px 10px; }
.footer__link {
  display: inline-flex; align-items: center; gap: 7px; min-height: 44px;
  padding: 12px 16px; border: 1px solid var(--rule);
  font-size: 11px; letter-spacing: .16em; text-transform: uppercase; color: var(--muted);
  transition: background .25s var(--ease), color .25s var(--ease), border-color .25s var(--ease);
}
.footer__link:hover { background: var(--paper); color: var(--ink); border-color: var(--paper); }
.footer__base {
  border-top: 1px solid var(--rule); padding: 22px var(--pad);
  display: flex; justify-content: space-between; flex-wrap: wrap; gap: 8px 20px;
  font-size: 11px; text-transform: uppercase; color: var(--dim);
}
.footer__krate {
  border-bottom: 1px solid var(--rule); padding-bottom: 2px;
  transition: color .25s var(--ease), border-color .25s var(--ease);
}
.footer__krate:hover { color: var(--paper); border-color: var(--paper); }

/* ---------- now playing ---------- */
.nowplaying {
  position: fixed; right: clamp(16px, 3vw, 32px); bottom: clamp(16px, 3vw, 32px);
  z-index: 90; display: flex; align-items: center; gap: 14px;
  background: var(--paper); color: var(--ink);
  padding: 12px 14px 12px 18px;
  box-shadow: 0 10px 40px rgba(0,0,0,.45);
}
.nowplaying[hidden] { display: none; }
.nowplaying__text {
  font-size: 10px; text-transform: uppercase; white-space: nowrap;
  overflow: hidden; text-overflow: ellipsis; max-width: min(46vw, 260px);
}
.eq { display: flex; align-items: flex-end; gap: 2px; height: 13px; }
.eq i { width: 2px; background: var(--ink); animation: eq 620ms ease-in-out infinite alternate; height: 100%; }
.eq i:nth-child(2) { animation-duration: 480ms; animation-delay: 120ms; height: 60%; }
.eq i:nth-child(3) { animation-duration: 720ms; animation-delay: 60ms; height: 85%; }
@keyframes eq { from { transform: scaleY(.35); } to { transform: scaleY(1); } }

/* ---------- reveal ---------- */
[data-reveal] {
  opacity: 0; transform: translateY(18px);
  transition: opacity .7s var(--ease), transform .7s var(--ease);
}
[data-reveal].is-in { opacity: 1; transform: none; }

/* ---------- responsive ---------- */
@media (max-width: 1000px) {
  .about { grid-template-columns: 1fr; }
  /* The portrait used to vanish below 1000px, which left every phone visitor
     reading a bio with no face attached to it. It stays — as a banded crop. */
  .about__plate { min-height: 0; aspect-ratio: 3 / 2; }
  .about__img { object-position: 50% 18%; }
  .krate { grid-template-columns: 1fr; }
  .krate__plate { min-height: 0; aspect-ratio: 16 / 10; }
  .ticker { grid-template-columns: 1fr; }
  .grid { grid-template-columns: repeat(2, 1fr); }
  .tile { grid-column: span 1; }
  .tile--wide { grid-column: span 2; }
}

@media (max-width: 760px) {
  html { scroll-padding-top: 68px; }
  /* The nav links are no longer dropped here — they move into the .nav__links
     disclosure panel defined in the topbar block above. */
  .brand img { height: 28px; }
  .hero { padding-top: 40px; }
  .hero__meta { margin-top: 22px; padding-bottom: 22px; }
  .hero__route { display: none; }
  .hero__plate { display: block; }
  .about__plate { aspect-ratio: 1 / 1; }
  .about__img { object-position: 50% 20%; }
  .about__body { gap: 26px; }
  .about__lead { font-size: 31px; padding-left: 14px; }
  .arc { grid-template-columns: 1fr; gap: 18px; }
  .arc__line { margin-top: 0; }
  .stat { padding-right: 10px; }
  .about__actions { gap: 14px 18px; }
  .about__cta { flex: 1 1 100%; }

  /* 390px is the case that matters: each date becomes a card with the day
     number as a left rail and event over venue beside it — no wrapped columns,
     no header row to hide. */
  .gigs__head { padding-bottom: 8px; }
  .gig {
    grid-template-columns: auto 1fr;
    column-gap: 16px; row-gap: 8px;
    align-items: start;
    padding: 18px var(--pad); font-size: 20px;
  }
  .gig__date { grid-column: 1; grid-row: 1 / span 2; }
  .gig__dd { font-size: 30px; }
  .gig__event { grid-column: 2; grid-row: 1; }
  .gig__venue {
    grid-column: 2; grid-row: 2;
    flex-direction: row; flex-wrap: wrap; align-items: baseline;
    gap: 4px 10px; text-align: left;
  }
  .gig__venue-name { font-size: 15px; }
  .dates__cta { flex-direction: column; align-items: stretch; }
  .dates__cta .btn { text-align: center; }

  /* 390px is the primary judged case for the booking form: one field per row,
     the four required ones first, everything else one tap away. */
  .book { gap: 26px; }
  .epk { flex-wrap: wrap; gap: 14px; padding: 20px 16px; }
  .epk__go { margin-left: 0; width: 100%; }
  /* Single column, so no auto-fit row can leave an empty cell: the divider
     between stacked fields is a real 1px gap, not a box-shadow the next
     field's background paints over. */
  .form__row--2, .form__row--3, .form__row--4 {
    grid-template-columns: 1fr; gap: 1px; background: var(--edge);
  }
  /* Trimmed to pay for the taller controls: the input's own 14px padding now
     supplies the breathing room the box padding used to. */
  .field { padding: 11px 16px; gap: 6px; }
  /* 16px inputs: anything smaller makes iOS Safari zoom the page on focus. */
  .field input, .field select, .field textarea { font-size: 16px; }
  .form__summary { padding: 16px; }
  .form__summary-s { width: 100%; order: 3; }
  .form__summary-i { margin-left: auto; }
  .form__foot { padding: 20px 16px; flex-direction: column; align-items: stretch; gap: 16px; }
  /* The column flip turns the 260px flex-basis into a height floor, which left
     161px of void above the send button. On the main axis, size to content. */
  .form__foot-copy { flex: 0 0 auto; }
  .form__acts { flex-direction: column; align-items: stretch; }
  .form__acts .btn { width: 100%; }
  .form__raw { padding: 16px; }
  .sent { padding: 24px 18px; }
  .sent__acts { flex-direction: column; align-items: stretch; width: 100%; }
  .contact { grid-template-columns: 1fr; }
  .contact__card { padding: 22px var(--pad); }
  .contact__copy { align-self: stretch; text-align: center; }
  .footer__cta { flex-direction: column; align-items: flex-start; gap: 22px; padding: 30px var(--pad); }
  .footer__links { width: 100%; }
  .footer__link { flex: 1 1 auto; justify-content: center; }
  .footer__base { flex-direction: column; gap: 5px; padding: 18px var(--pad); }
  .film__caption { bottom: 28px; }
}

@media (prefers-reduced-motion: reduce) {
  html { scroll-behavior: auto; }
  *, *::before, *::after {
    animation-duration: .01ms !important; animation-iteration-count: 1 !important;
    transition-duration: .01ms !important;
  }
  [data-reveal] { opacity: 1; transform: none; }
}
