/* =========================================================================
   ALL TONGUE, styles
   Light-ground palette: warm paper and daylight (sand / terracotta / slate),
   per the air-and-palette revision. Room color shifts happen by animating
   --room (the current track's color) from JS; tinted surfaces derive from it
   with color-mix. `body.night` flips the page into the night atmosphere that
   pairs with the starfield mode: same structure, inverted tokens.
   ========================================================================= */

:root {
  --ground:     #f2edea;   /* soft off-white with a warm cast, the paper */
  --ground-2:   #e9e0d9;   /* slightly deeper paper, for cards and wells */
  --sand:       #e0b896;
  --accent:     #c87b54;   /* terracotta, carries the energy, used sparingly */
  --accent-ink: #a4552e;   /* terracotta deepened until it reads as text */
  --ink:        #445769;   /* slate, type and depth */
  --ink-deep:   #2f3e4a;
  --text:       var(--ink);
  --room:       #c87b54;   /* set per playing track from JS */
  --room-soft:  color-mix(in oklab, var(--room) 18%, var(--ground));
  --hairline:   color-mix(in oklab, var(--ink) 22%, transparent);

  --font-hero:    "Ojuju", "Unbounded", system-ui, sans-serif;
  --font-display: "Unbounded", system-ui, sans-serif;
  --font-body:    "Newsreader", Georgia, serif;
  --font-mono:    "Space Mono", ui-monospace, monospace;

  --space: clamp(1.25rem, 4vw, 2.5rem);
  --maxw: 68rem;
}

/* night: the alternate atmosphere behind the starfield mode */
body.night {
  --ground:   #1f2a33;
  --ground-2: #29363f;
  --text:     #eee4da;
  --ink:      #c6cfd8;
  --ink-deep: #eee4da;
  --accent-ink: #dda175;
  --hairline: color-mix(in oklab, #eee4da 20%, transparent);
  --room-soft: color-mix(in oklab, var(--room) 26%, #1f2a33);
}

* { margin: 0; padding: 0; box-sizing: border-box; }
html {
  scroll-behavior: smooth;
  /* The document ends exactly at the footer (measured: zero trailing space),
     so anything past it is the browser's own overscroll bounce. Stop the
     bounce at the edges and the scroll ends at the footer. */
  overscroll-behavior-y: none;

  /* DO NOT give html a background. It had one here for exactly one commit and
     it put out the air.

     With no background on html, the browser propagates body's background up to
     the root and then paints body itself with none. Give html its own and that
     propagation stops, so body starts painting an opaque box of its own, and a
     body background paints AFTER negative z-index children in the root stacking
     context. #field sits at z-index -1. So body's ground covered the entire
     WebGL canvas.

     It looked fine, which is the dangerous part: the page has a ground colour
     either way. Measured, the dust went from covering 74.6% of the viewport to
     1%, and that 1% was the arrival copy animating, not dust.

     The bounce is the right colour regardless, because the propagated body
     background paints the whole canvas including the overscroll area. That is
     what propagation is for, and it was already working before this rule was
     added to "help". */
}
@media (prefers-reduced-motion: reduce) { html { scroll-behavior: auto; } }

body {
  background: var(--ground);
  color: var(--text);
  font-family: var(--font-body);
  font-size: 1.0625rem;
  line-height: 1.65;
  font-weight: 400;
  overflow-x: hidden;
  min-height: 100svh;
  /* The palette crossfade. Background, type and the field all move on the
     same 400ms so the page changes as one thing rather than in layers. */
  transition: background 400ms ease, color 400ms ease;
}

::selection { background: color-mix(in oklab, var(--sand) 70%, transparent); color: var(--ink-deep); }

a { color: inherit; }
/* height:auto keeps the width/height attributes working as an aspect ratio
   rather than forcing the file's literal pixel height. */
img { max-width: 100%; height: auto; display: block; }
button { font: inherit; color: inherit; background: none; border: 0; cursor: pointer; }

:focus-visible {
  outline: 2px solid var(--accent-ink);
  outline-offset: 3px;
  border-radius: 2px;
}

.visually-hidden {
  position: absolute; width: 1px; height: 1px; overflow: hidden;
  clip: rect(0 0 0 0); white-space: nowrap;
}

.mono { font-family: var(--font-mono); font-size: 0.72rem; letter-spacing: 0.14em; text-transform: uppercase; }

/* ---- the field ---------------------------------------------------------
   #field-fallback paints the ground (paper + room tint + the light shaft's
   still cousin) and is always visible; the WebGL canvas draws the moving
   air on top with a transparent background. If WebGL is off, the page
   still looks intentional. ---------------------------------------------- */

#field, #field-fallback {
  position: fixed; inset: 0;
  width: 100%; height: 100%;
}
#field { z-index: -1; }
#field-fallback {
  z-index: -2;
  background:
    linear-gradient(112deg, transparent 30%, color-mix(in srgb, var(--sand) 26%, transparent) 46%, color-mix(in srgb, var(--sand) 12%, transparent) 58%, transparent 72%),
    radial-gradient(120% 90% at 50% 112%, var(--room-soft) 0%, transparent 62%),
    var(--ground);
  transition: background 400ms ease;
}
#field { opacity: 0; transition: opacity 1.8s ease; }
body.field-live #field { opacity: 1; }
body.field-swap #field { opacity: 0; transition-duration: 0.8s; }

/* ---- signature mark: one asset, both grounds ---------------------------
   The black signature PNG is the default on paper; night inverts it to
   white with a filter, so both versions come from one file. */
.sig { filter: none; transition: filter 1.2s ease; }
body.night .sig { filter: invert(1); }

/* ---- top bar ------------------------------------------------------------ */

.topbar {
  position: fixed; top: 0; left: 0; right: 0; z-index: 40;
  display: flex; align-items: center; justify-content: space-between;
  gap: 1rem;
  padding: 0.85rem var(--space);
  opacity: 0; pointer-events: none;
  transform: translateY(-8px);
  transition: opacity 0.6s ease, transform 0.6s ease;
  background: linear-gradient(color-mix(in srgb, var(--ground) 85%, transparent), transparent);
}
body.arrived .topbar { opacity: 1; pointer-events: auto; transform: none; }

.topbar__mark { display: inline-flex; align-items: center; text-decoration: none; }
.topbar__mark .sig { height: 22px; width: auto; }
.topbar__fallback { display: none; font-family: var(--font-display); font-weight: 400; font-size: 0.8rem; letter-spacing: 0.08em; }
.sig-missing .sig { display: none; }
.sig-missing .topbar__fallback { display: inline; }

.topbar__nav { display: none; gap: 1.6rem; }
.topbar__nav a { text-decoration: none; font-family: var(--font-mono); font-size: 0.72rem; letter-spacing: 0.14em; text-transform: uppercase; opacity: 0.8; }
.topbar__nav a:hover { opacity: 1; }

.topbar__tools { display: flex; gap: 0.45rem; }
/* ---- the day and night switch --------------------------------------------
   The same control language as the support toggle, one size quieter: a
   terracotta fill that travels between two halves rather than two buttons
   that light up. The sun sets as the moon rises, each rotating out and in
   over its own half rather than crossfading in place, so the change reads as
   a small piece of weather instead of an icon swap.
   -------------------------------------------------------------------------- */

.modesw {
  position: relative; isolation: isolate;
  display: inline-grid; grid-auto-flow: column;
  padding: 0.2rem;
  border-radius: 999px;
  border: 1px solid var(--hairline);
  background: color-mix(in srgb, var(--ground-2) 55%, transparent);
  backdrop-filter: blur(6px);
  transition: border-color .35s ease, background .35s ease;
}
.modesw:hover { border-color: color-mix(in oklab, var(--accent) 55%, transparent); }

/* the travelling fill, terracotta, exactly as the offer control does it */
.modesw__fill {
  position: absolute; top: 0.2rem; left: 0.2rem; z-index: -1;
  border-radius: 999px;
  background: linear-gradient(135deg, var(--accent), color-mix(in srgb, var(--accent-ink) 80%, var(--accent)));
  box-shadow: 0 5px 14px -8px color-mix(in srgb, var(--accent-ink) 70%, transparent);
  transition: transform 380ms cubic-bezier(.34, 1.28, .48, 1),
              width 380ms cubic-bezier(.34, 1.28, .48, 1);
}

.modesw__half {
  display: inline-flex; align-items: center; gap: 0.4rem;
  height: 1.85rem; padding: 0 0.7rem;
  border-radius: 999px;
  color: var(--ink);
  opacity: 0.62;
  transition: opacity 340ms ease, color 340ms ease;
}
/* aria-pressed=true means the page is dark, so the moon is the live half */
.modesw[aria-pressed="true"]  .modesw__half--moon,
.modesw[aria-pressed="false"] .modesw__half--sun { color: #fdf9f6; opacity: 1; }

.modesw__word {
  font-family: var(--font-mono); font-size: 0.6rem;
  letter-spacing: 0.14em; text-transform: uppercase;
}

/* the orbs: one sets as the other rises */
.modesw__orb { display: grid; place-items: center; width: 15px; height: 15px; }
.modesw__orb svg { display: block; overflow: visible; }
.sun-core { fill: currentColor; }
.sun-rays { stroke: currentColor; stroke-width: 1.4; fill: none; }
.moon-body { fill: currentColor; }

.modesw__half .modesw__orb {
  transition: transform 380ms cubic-bezier(.34, 1.28, .48, 1), opacity 320ms ease;
}
.sun-rays { transition: transform 420ms cubic-bezier(.34, 1.28, .48, 1), opacity 300ms ease;
            transform-origin: 10px 10px; }

/* dark: the sun has set (dropped and dimmed, rays pulled in), moon upright */
.modesw[aria-pressed="true"] .modesw__half--sun .modesw__orb {
  transform: translateY(2px) rotate(-40deg); opacity: 0.5;
}
.modesw[aria-pressed="true"] .sun-rays { transform: scale(0.55) rotate(-25deg); opacity: 0; }
.modesw[aria-pressed="true"] .modesw__half--moon .modesw__orb { transform: none; opacity: 1; }

/* light: the moon has set, the sun is up with its rays out */
.modesw[aria-pressed="false"] .modesw__half--moon .modesw__orb {
  transform: translateY(2px) rotate(35deg); opacity: 0.5;
}
.modesw[aria-pressed="false"] .modesw__half--sun .modesw__orb { transform: none; opacity: 1; }
.modesw[aria-pressed="false"] .sun-rays { transform: none; opacity: 1; }

@media (prefers-reduced-motion: reduce) {
  .modesw__fill, .modesw__half .modesw__orb, .sun-rays { transition: none; }
}

/* under 380px the words go and the two orbs carry it alone */
@media (max-width: 380px) {
  .modesw__word { display: none; }
  .modesw__half { padding: 0 0.55rem; }
}

/* Phones: keep the words, just tighten. Only drop them if the bar would
   actually crowd (checked at 320px, the narrowest real phone). */
@media (max-width: 520px) {
  .topbar { gap: 0.5rem; }
  .topbar__mark .sig { height: 18px; }
  .modesw__half { height: 1.75rem; padding: 0 0.6rem; }
  .modesw__word { font-size: 0.56rem; letter-spacing: 0.1em; }
}

@media (min-width: 760px) { .topbar__nav { display: flex; } }

/* ---- sections shared ---------------------------------------------------- */

main { display: block; }

/* Vertical space is measured in svh, not vh. This was not the cause of the
   scroll jump (that was an unsized lazy image, see the portrait in
   index.html), but every section's padding depends on viewport height, and
   svh is the one viewport unit guaranteed not to move when a mobile toolbar
   retracts. Hardening, not a fix. Use svh for anything that contributes to
   document height. */
section {
  max-width: var(--maxw);
  margin: 0 auto;
  padding: clamp(4rem, 12svh, 8rem) var(--space);
}
.section-label { color: var(--accent-ink); margin-bottom: 1rem; }
h2 { font-family: var(--font-display); font-weight: 400; font-size: clamp(1.5rem, 4.5vw, 2.4rem); line-height: 1.2; margin-bottom: 1rem; letter-spacing: 0.01em; color: var(--ink-deep); }
section > p, .drum__text p { max-width: 38rem; }

.btn {
  display: inline-block;
  font-family: var(--font-mono); font-size: 0.75rem; letter-spacing: 0.14em; text-transform: uppercase;
  text-decoration: none; text-align: center;
  padding: 0.9rem 1.6rem;
  border: 1px solid var(--hairline);
  border-radius: 999px;
  transition: border-color .3s, background .3s, color .3s;
}
.btn--gold {
  color: #fdf9f6;
  border-color: color-mix(in oklab, var(--accent) 80%, var(--accent-ink));
  background: linear-gradient(135deg, var(--accent), color-mix(in srgb, var(--accent-ink) 85%, var(--accent)));
}
.btn--gold:hover { background: linear-gradient(135deg, color-mix(in srgb, var(--accent) 85%, white), var(--accent-ink)); }

.quiet-link { font-size: 0.95rem; opacity: 0.75; text-underline-offset: 3px; }
.quiet-link:hover { opacity: 1; }

.img-missing { position: relative; min-height: 12rem; border: 1px dashed var(--hairline); border-radius: 6px; }
.img-missing img { display: none; }
.img-missing::after {
  content: "photo on its way";
  position: absolute; inset: 0; display: grid; place-items: center;
  font-family: var(--font-mono); font-size: 0.7rem; letter-spacing: 0.14em; text-transform: uppercase;
  opacity: 0.5;
}
img.img-missing { display: none; }

/* ---- arrival ------------------------------------------------------------ */

.arrival {
  min-height: 100svh;
  display: flex; flex-direction: column; align-items: center; justify-content: center;
  text-align: center;
  position: relative;
  padding-top: 14svh;
}
.arrival__eyebrow { opacity: 0.6; margin-bottom: 2rem; }
.arrival__title {
  font-family: var(--font-hero);
  font-weight: 700;
  font-size: clamp(3rem, 14vw, 8.5rem);
  line-height: 0.94;
  letter-spacing: 0.005em;
  color: var(--ink-deep);
}
.arrival__artist {
  font-family: var(--font-body); font-style: italic; font-weight: 400;
  font-size: clamp(1.05rem, 2.6vw, 1.4rem);
  opacity: 0.85;
  margin-top: 0.9rem;
}

.big-play {
  margin-top: clamp(2rem, 6svh, 3.5rem);
  position: relative;
  width: 5.25rem; height: 5.25rem;
  border-radius: 50%;
  display: grid; place-items: center;
  color: #fdf9f6;
}
.big-play__ring {
  position: absolute; inset: 0; border-radius: 50%;
  border: 1px solid color-mix(in oklab, var(--accent) 80%, var(--accent-ink));
  background: radial-gradient(circle at 32% 28%, color-mix(in srgb, var(--sand) 70%, var(--accent)), var(--accent) 55%, var(--accent-ink));
  transition: transform .4s ease;
}
.big-play:hover .big-play__ring { transform: scale(1.06); }
.big-play__icon { position: relative; margin-left: 4px; }
body.playing .big-play { display: none; }
.arrival__hint { margin-top: 1rem; opacity: 0.55; }
body.playing .arrival__hint { visibility: hidden; }

@keyframes ringbreathe {
  0%, 100% { box-shadow: 0 0 0 0 color-mix(in srgb, var(--accent) 35%, transparent); }
  50%       { box-shadow: 0 0 0 14px transparent; }
}
@media (prefers-reduced-motion: no-preference) {
  .big-play__ring { animation: ringbreathe 4.5s ease-in-out infinite; }
}

.welcome {
  max-width: 34rem;
  margin-top: 2.5rem;
  font-size: 1rem;
  font-style: italic;
  opacity: 0;
  visibility: hidden;
  transition: opacity 2s ease 1.2s;
}
.welcome p + p { margin-top: 0.9rem; }
body.playing .welcome { opacity: 0.9; visibility: visible; }

.arrival__down {
  position: absolute; bottom: 1.75rem; left: 50%; transform: translateX(-50%);
  opacity: 0.55; padding: 0.5rem;
}
.arrival__down:hover { opacity: 0.9; }

/* ---- the player ---------------------------------------------------------
   Typography and layout here are working, and per the revision brief they stay
   exactly as built. Only color tokens changed. ---------------------------- */

.listen { display: grid; gap: 3rem; }

.now { text-align: center; }
.now__label { opacity: 0.6; }
.now__title {
  font-family: var(--font-display); font-weight: 400;
  font-size: clamp(1.4rem, 5vw, 2.2rem);
  margin: 0.5rem 0 1.75rem;
  color: var(--ink-deep);
}
.now__note {
  font-size: 0.9rem; font-style: italic;
  color: var(--accent-ink);
  margin: -0.9rem auto 1.4rem; max-width: 26rem;
}
.now__note[hidden] { display: none; }

.now__ring {
  position: relative;
  width: min(58vw, 15rem); aspect-ratio: 1;
  margin: 0 auto;
  border-radius: 50%;
  touch-action: none;
}
.now__ring svg { width: 100%; height: 100%; transform: rotate(-90deg); }
.ring-track { fill: none; stroke: var(--hairline); stroke-width: 2; }
.ring-progress {
  fill: none;
  stroke: var(--accent);
  stroke-width: 3; stroke-linecap: round;
  stroke-dasharray: 553; stroke-dashoffset: 553; /* 2πr, r=88 */
}
.now__times {
  position: absolute; inset: 0;
  display: flex; align-items: center; justify-content: center; gap: 0.5rem;
  font-size: 0.8rem; opacity: 0.85;
}
.now__sep { opacity: 0.4; }

.now__controls {
  display: flex; align-items: center; justify-content: center; gap: 1.5rem;
  margin-top: 1.75rem;
}
.ctrl {
  width: 2.9rem; height: 2.9rem; border-radius: 50%;
  display: grid; place-items: center;
  opacity: 0.85;
}
.ctrl:hover { opacity: 1; }

.ctrl--main {
  width: 4rem; height: 4rem;
  color: #fdf9f6;
  border: 1px solid color-mix(in oklab, var(--accent) 80%, var(--accent-ink));
  background: radial-gradient(circle at 32% 28%, color-mix(in srgb, var(--sand) 60%, var(--accent)), var(--accent) 58%, var(--accent-ink));
}
.i-pause { display: none; }
body.playing .i-pause { display: block; }
body.playing .i-play { display: none; }

/* tracklist */
.tracks { list-style: none; }
.track {
  display: flex; align-items: center; gap: 1rem;
  padding: 1.05rem 0.5rem;
  border-bottom: 1px solid var(--hairline);
  cursor: pointer;
  transition: background 0.4s ease;
  border-radius: 4px;
}
.track:first-child { border-top: 1px solid var(--hairline); }
.track:hover { background: color-mix(in srgb, var(--ink) 5%, transparent); }
.track:focus-visible { outline-offset: -2px; }

.track__num { font-family: var(--font-mono); font-size: 0.72rem; opacity: 0.55; width: 1.6rem; flex: none; }

/* ---- the dot, which is also the favorite control ----
   No heart. The element that already identifies a track becomes the element
   that expresses preference, so nothing new enters the row and no icon column
   has to be invented. Unsaved it is an open ring in the track's own colour;
   saved it fills and lights from inside. -------------------------------- */
.track__fav {
  flex: none;
  width: 2.75rem; height: 2.75rem;     /* 44px of touch area at any dot size */
  margin: -0.7rem -0.85rem;            /* claimed from the row, not added to it */
  display: grid; place-items: center;
  position: relative;
  border-radius: 50%;
}
.track__ring {
  width: 0.85rem; height: 0.85rem; border-radius: 50%;
  border: 2px solid var(--c);
  background: transparent;
  opacity: 0.5;
  transition: opacity 320ms ease, border-color 320ms ease,
              background 420ms ease, box-shadow 420ms ease, transform 320ms cubic-bezier(.34,1.3,.48,1);
}
.track__fav:hover .track__ring,
.track__fav:focus-visible .track__ring { opacity: 0.95; transform: scale(1.12); }
.track__fav:focus-visible { outline: 2px solid var(--accent-ink); outline-offset: -6px; }
.track__fav--none { pointer-events: none; }
.track__fav--none .track__ring { opacity: 0.22; }

/* saved: filled at full saturation, lit from within rather than coloured in */
.track.is-fav .track__ring {
  opacity: 1;
  border-color: color-mix(in srgb, white 18%, var(--c));
  background: radial-gradient(circle at 36% 30%,
    color-mix(in srgb, white 55%, var(--c)) 0%,
    color-mix(in srgb, white 12%, var(--c)) 42%,
    var(--c) 100%);
  box-shadow: 0 0 0 1px color-mix(in srgb, var(--c) 35%, transparent),
              0 0 10px -1px color-mix(in srgb, var(--c) 75%, transparent);
}

/* the moment of saving: one ripple leaving the dot like a struck drum */
.track__ripple {
  position: absolute; left: 50%; top: 50%;
  width: 0.85rem; height: 0.85rem; margin: -0.425rem 0 0 -0.425rem;
  border-radius: 50%;
  border: 1.5px solid var(--c);
  opacity: 0; pointer-events: none;
}
.track__ripple.is-going { animation: fav-ripple 600ms cubic-bezier(.16,.7,.3,1) forwards; }
@keyframes fav-ripple {
  from { transform: scale(1);   opacity: 0.75; }
  to   { transform: scale(3.4); opacity: 0; }
}

/* the rest of the row: one button, so Enter works and the label is real */
.track__go {
  flex: 1; min-width: 0;
  display: flex; align-items: center; gap: 1rem;
  text-align: left; padding: 0;
  color: inherit;
}
.track__go--none { cursor: default; }

.track__title { font-family: var(--font-display); font-weight: 400; font-size: 0.98rem; letter-spacing: 0.02em; flex: 1; }
.track__time { font-family: var(--font-mono); font-size: 0.72rem; opacity: 0.55; }
.track__soon { font-family: var(--font-mono); font-size: 0.62rem; letter-spacing: 0.12em; text-transform: uppercase; opacity: 0.45; }

.track.is-active {
  background: linear-gradient(90deg, color-mix(in srgb, var(--c) 24%, transparent), transparent 78%);
}
.track.is-active .track__title { font-weight: 500; }
.track.is-active .track__num { color: var(--accent-ink); opacity: 1; }
.track.is-broken .track__title { opacity: 0.6; }
.track.is-broken .track__time::after { content: " · didn't load"; opacity: 0.7; }
.track.is-unavailable { cursor: default; opacity: 0.55; }
.track.is-unavailable:hover { background: none; }

/* equalizer dots on the active row */
.track__eq { display: none; gap: 3px; align-items: flex-end; height: 12px; }
.track.is-active.is-playing .track__eq { display: inline-flex; }
.track__eq i { width: 3px; background: var(--accent); border-radius: 2px; animation: eq 1s ease-in-out infinite; }
.track__eq i:nth-child(1) { height: 6px; animation-delay: 0s; }
.track__eq i:nth-child(2) { height: 11px; animation-delay: 0.25s; }
.track__eq i:nth-child(3) { height: 8px; animation-delay: 0.5s; }
@keyframes eq { 0%,100% { transform: scaleY(0.5);} 50% { transform: scaleY(1);} }
@media (prefers-reduced-motion: reduce) {
  .track__eq i { animation: none; }
  .track__ripple.is-going { animation: none; }
  .track__ring { transition: opacity 160ms linear, background 160ms linear; }
}

/* While a saved set is playing, everything outside it steps back. A label on
   its own is easy to miss, and "am I hearing my set or the album" should never
   be a question anyone has to go looking for an answer to. */
.tracks.is-favmode .track:not(.is-fav) { opacity: 0.42; }
.tracks.is-favmode .track { transition: background 0.4s ease, opacity 0.4s ease; }

/* ---- play favorites, or play the album ---- */
.favset { display: grid; justify-items: center; gap: 0.7rem; margin-top: 1.5rem; }
/* Shuffle and the set, one row, wrapping to two on the narrowest phones. */
.favset__row { display: flex; flex-wrap: wrap; justify-content: center; gap: 0.6rem; }
.favset__ico { display: grid; place-items: center; }
.favset__btn--shuffle[aria-pressed="true"] {
  opacity: 1; color: var(--accent-ink);
  border-color: color-mix(in oklab, var(--accent) 65%, transparent);
  background: color-mix(in srgb, var(--accent) 10%, transparent);
}
.favset__btn {
  display: inline-flex; align-items: center; gap: 0.6rem;
  font-family: var(--font-mono); font-size: 0.64rem;
  letter-spacing: 0.14em; text-transform: uppercase;
  padding: 0.6rem 1.15rem; border-radius: 999px;
  border: 1px solid var(--hairline);
  color: var(--ink);
  opacity: 0.8;
  transition: opacity .3s, border-color .3s, background .3s, color .3s;
}
.favset__btn:hover { opacity: 1; border-color: color-mix(in oklab, var(--accent) 60%, transparent); }
.favset__dot {
  width: 0.6rem; height: 0.6rem; border-radius: 50%;
  border: 2px solid currentColor; opacity: 0.7;
  transition: background 320ms ease, opacity 320ms ease, box-shadow 320ms ease;
}
/* the button wears the saved state it plays */
.favset__btn.is-on {
  opacity: 1; color: var(--accent-ink);
  border-color: color-mix(in oklab, var(--accent) 65%, transparent);
  background: color-mix(in srgb, var(--accent) 10%, transparent);
}
.favset__btn.is-on .favset__dot {
  opacity: 1; background: var(--accent);
  box-shadow: 0 0 8px -1px color-mix(in srgb, var(--accent) 80%, transparent);
}
.favset__note {
  min-height: 1.1rem;
  text-transform: none; letter-spacing: 0.08em;
  font-size: 0.62rem; opacity: 0.5;
}
.favset__note.is-hint { opacity: 0.8; color: var(--accent-ink); max-width: 22rem; line-height: 1.5; }

@media (min-width: 900px) {
  .listen { grid-template-columns: 1fr 1.15fr; align-items: center; }
}

/* ---- the dedication -------------------------------------------------------
   Folded away behind one small line. It is not a call to action, so it gets no
   card, no border, no colour and no heading: just a quiet control with a lot of
   air, sitting between the player and what comes next. Opened, it is set as a
   dedication rather than as body copy, with wider leading and its own measure,
   and the signature sits apart from it the way a signature sits apart from a
   letter. ------------------------------------------------------------------- */

.ded { text-align: center; padding-block: clamp(2.5rem, 7svh, 4.5rem); }

/* The resting dimness is colour, not opacity, and there is no transition on
   opacity here on purpose. The section reveal tweens opacity on every direct
   child of a section, and a CSS opacity transition on the same element fights
   that tween and loses at 0, which left this control invisible. Anything that
   sits as a direct child of a section should dim itself the same way. */
.ded__open {
  display: inline-flex; align-items: center; gap: 0.55rem;
  font-family: var(--font-mono); font-size: 0.66rem;
  letter-spacing: 0.18em; text-transform: uppercase;
  padding: 0.7rem 0.4rem;
  color: color-mix(in srgb, var(--ink) 62%, transparent);
  transition: color 400ms ease;
}
.ded__open:hover, .ded__open[aria-expanded="true"] { color: var(--accent-ink); }
.ded__chev { display: grid; place-items: center; transition: transform 400ms cubic-bezier(.22,.8,.3,1); }
.ded__open[aria-expanded="true"] .ded__chev { transform: rotate(180deg); }

.ded__panel {
  display: grid; grid-template-rows: 0fr;
  transition: grid-template-rows 400ms cubic-bezier(.22, .8, .3, 1);
}
.ded.is-open .ded__panel { grid-template-rows: 1fr; }
.ded__inner {
  overflow: hidden;
  opacity: 0; visibility: hidden;
  transition: opacity 300ms ease, visibility 0s linear 400ms;
}
.ded.is-open .ded__inner {
  opacity: 1; visibility: visible;
  transition: opacity 400ms ease 80ms, visibility 0s;
}

.ded__text {
  max-width: 30rem; margin: 0 auto;
  padding-top: 1.9rem;
  font-size: clamp(1.02rem, 2.2vw, 1.12rem);
  line-height: 1.95;                 /* wider than body, so it reads as set apart */
  font-style: italic;
  color: var(--ink-deep);
  opacity: 0.9;
}
.ded__text + .ded__text { padding-top: 1.5rem; }

.ded__sign {
  margin-top: 2.1rem;
  font-family: var(--font-mono); font-size: 0.62rem;
  letter-spacing: 0.16em; text-transform: uppercase;
  opacity: 0.45;
}

@media (prefers-reduced-motion: reduce) {
  .ded__panel, .ded__chev { transition: none; }
  .ded__inner { transition: opacity 200ms linear, visibility 0s linear 200ms; }
  .ded.is-open .ded__inner { transition: opacity 200ms linear, visibility 0s; }
}

/* ---- meet david ---------------------------------------------------------- */

.david { display: grid; gap: 2.5rem; }
/* Credited the way a magazine credits an image: tracked lowercase, very small,
   low contrast, ranged right along the bottom edge of the photo. An editorial
   detail rather than a caption, so it never competes with the portrait. */
.david__credit {
  margin-top: 0.55rem;
  font-family: var(--font-mono);
  font-size: 0.58rem; letter-spacing: 0.18em;
  text-transform: lowercase;
  text-align: right;
  opacity: 0.42;
}
.david__photo.img-missing .david__credit { display: none; }

.david__photo img {
  border-radius: 6px;
  filter: saturate(0.96);
  box-shadow: 0 24px 50px -28px color-mix(in srgb, var(--ink-deep) 55%, transparent);
}
.lede { font-size: clamp(1.3rem, 3.4vw, 1.7rem); font-style: italic; margin-bottom: 1.2rem; line-height: 1.4; color: var(--ink-deep); }
.david__story p + p { margin-top: 1rem; }
/* The final paragraph turns from third person into first. Set apart with more
   air and a touch more weight so it reads as a turn, not an inconsistency. */
.david__turn {
  margin-top: 1.9rem !important;
  font-size: 1.06rem;
  color: var(--ink-deep);
  border-top: 1px solid var(--hairline);
  padding-top: 1.6rem;
}
.david__sig { height: 2.6rem; width: auto; margin-top: 1.75rem; opacity: 0.9; }
.david__actions { display: flex; flex-wrap: wrap; align-items: center; gap: 1.25rem; margin-top: 1.75rem; }

@media (min-width: 820px) {
  .david { grid-template-columns: 0.9fr 1.1fr; align-items: center; gap: 4rem; }
}

/* ---- drum: the breathing pulse -------------------------------------------
   Light moving through the instrument, not a shape growing on top of it. It
   sits BEHIND the drawing, so the drum stays fully readable at every point in
   the cycle and nothing is ever darkened or obscured.

   The timing is the point of the whole effect. 10.6 seconds a cycle is about
   5.7 breaths a minute, the rate associated with parasympathetic activation,
   which is the state this music is for. Anyone watching long enough tends to
   fall into step with it. It is deliberately slower than feels responsive.
   Do not speed this up.

   Eased like breath rather than like a metronome: gentle acceleration off the
   bottom, a soft hold at the top of the expansion, unhurried return.
   -------------------------------------------------------------------------- */

.drum__art { position: relative; }
.drum__art svg { position: relative; z-index: 1; }

.drum__pulse {
  position: absolute; z-index: 0;
  /* the drum's own geometry: centre (200,200) of a 400x420 box, radius 178 */
  left: 50%; top: 47.6%;
  width: 89%; aspect-ratio: 1;
  margin: 0; transform-origin: 50% 50%;
  border-radius: 50%;
  pointer-events: none;
  /* Third pass, doubled again. The core stop has no headroom left at 100, so
     the strength now comes from the falloff: the mid and outer stops carry
     far more colour and the fade reaches further out, which is what actually
     reads as a fuller drum. Run: 62/34/12, then 93/51/18, now 100/88/42.
     It still sits behind the drawing, so nothing is obscured however far
     this gets pushed. */
  background: radial-gradient(circle closest-side,
    color-mix(in srgb, var(--pulse, var(--accent)) 100%, transparent) 0%,
    color-mix(in srgb, var(--pulse, var(--accent)) 88%, transparent) 32%,
    color-mix(in srgb, var(--pulse, var(--accent)) 42%, transparent) 60%,
    transparent 86%);
  filter: blur(6px);
  animation: drum-breath 10.6s infinite;
}
@keyframes drum-breath {
  /* out of the bottom, gathering */
  0%   { transform: translate(-50%, -50%) scale(0.16); opacity: 0.68; animation-timing-function: cubic-bezier(.34, 0, .30, 1); }
  /* reaching the rim */
  44%  { transform: translate(-50%, -50%) scale(1);    opacity: 1; animation-timing-function: linear; }
  /* the soft hold at the top */
  54%  { transform: translate(-50%, -50%) scale(1.02); opacity: 0.99; animation-timing-function: cubic-bezier(.45, 0, .5, 1); }
  /* unhurried return */
  100% { transform: translate(-50%, -50%) scale(0.16); opacity: 0.68; }
}

/* Reduced motion: hold it still at mid expansion. The light is still there,
   it simply is not breathing. */
@media (prefers-reduced-motion: reduce) {
  .drum__pulse {
    animation: none;
    transform: translate(-50%, -50%) scale(0.62);
    opacity: 0.92;
  }
}

/* ---- drum: line-art diagram ------------------------------------------- */

.drum { display: grid; gap: 2.5rem; }
.drum__art { display: grid; place-items: center; padding: 1rem; }
.drum__art svg { width: min(100%, 26rem); height: auto; color: var(--ink); }
.drum__art .accent { color: var(--accent-ink); }
.drum__caption {
  font-family: var(--font-mono); font-size: 0.65rem; letter-spacing: 0.14em;
  text-transform: uppercase; opacity: 0.5; text-align: center; margin-top: 1rem;
}
@media (min-width: 820px) {
  .drum { grid-template-columns: 1.1fr 0.9fr; align-items: center; gap: 4rem; }
}

/* ---- welcome in ----------------------------------------------------------
   The one section built by taking things away. No cards, no borders, no
   icons, no boxes: three lines of type standing in open air, held apart by
   gutters wide enough that nothing needs a container to look separate. If it
   ever starts reading as a features grid, the answer is to cut, not to
   restyle. Total height should feel like a breath between two sections.
   -------------------------------------------------------------------------- */

.openroom { text-align: center; padding-block: clamp(3rem, 9svh, 6rem); }
/* a direct <p> child of a section inherits the 38rem measure, which left-rags
   a one-line label inside a centred block; this one wants the full width */
.openroom__hail { color: var(--accent-ink); opacity: 0.9; max-width: none; }

.openroom__points {
  display: grid; gap: clamp(2.25rem, 5vw, 3.5rem);
  margin: clamp(2rem, 5svh, 3.25rem) auto 0;
  max-width: 58rem;
}
.openroom__p { max-width: 22rem; margin-inline: auto; }
.openroom__p h3 {
  font-family: var(--font-display); font-weight: 400;
  font-size: clamp(1.02rem, 2.1vw, 1.18rem);
  letter-spacing: 0.01em; line-height: 1.3;
  color: var(--ink-deep);
  margin-bottom: 0.5rem;
}
.openroom__p p { font-size: 0.95rem; line-height: 1.6; opacity: 0.68; }

.openroom__close {
  margin: clamp(2.25rem, 5svh, 3.25rem) auto 0;
  max-width: 30rem;
  font-style: italic; font-size: 0.92rem; opacity: 0.55;
}

@media (min-width: 800px) {
  /* wide gutters are the only separator, so they get to be genuinely wide */
  .openroom__points { grid-template-columns: repeat(3, 1fr); gap: clamp(2.5rem, 6vw, 5rem); }
}

/* ---- how this works ------------------------------------------------------
   Two columns on desktop, framing pinned left while the answers move past it,
   so it reads as a conversation rather than a dense list down the middle.
   One accordion at a time is not enforced: someone comparing two answers
   should be able to hold both open. -------------------------------------- */

.how__intro h2 { margin-bottom: 0; }
.how__list { margin-top: 2rem; }

.how__item + .how__item { border-top: 1px solid color-mix(in oklab, var(--ink) 14%, transparent); }

.how__q { font-size: inherit; font-family: inherit; font-weight: inherit; margin: 0; }
.how__btn {
  display: flex; align-items: center; justify-content: space-between; gap: 1.5rem;
  width: 100%; text-align: left;
  padding: 1.15rem 0;                 /* generous tap target, especially on phones */
  color: var(--ink-deep);
  transition: color 300ms ease;
}
.how__qt {
  font-family: var(--font-display); font-weight: 400;
  font-size: clamp(0.95rem, 2vw, 1.03rem); line-height: 1.4; letter-spacing: 0.01em;
}
.how__btn:hover .how__qt { opacity: 0.75; }

/* the only terracotta in this section: a plus that loses its upright on open */
.how__mark { position: relative; flex: none; width: 13px; height: 13px; opacity: 0.55; transition: opacity 300ms ease, color 300ms ease; }
.how__mark::before, .how__mark::after {
  content: ""; position: absolute; background: currentColor;
  transition: transform 320ms cubic-bezier(.34, 1.1, .48, 1);
}
.how__mark::before { left: 0; right: 0; top: 6px; height: 1px; }
.how__mark::after  { top: 0; bottom: 0; left: 6px; width: 1px; }
.how__item.is-open .how__mark { color: var(--accent); opacity: 1; }
.how__item.is-open .how__mark::after { transform: scaleY(0); }

/* height eases open; the words arrive just behind it so nothing snaps */
.how__a {
  display: grid; grid-template-rows: 0fr;
  transition: grid-template-rows 300ms ease;
}
.how__item.is-open .how__a { grid-template-rows: 1fr; }
.how__inner {
  overflow: hidden;
  opacity: 0; visibility: hidden;
  transition: opacity 240ms ease, visibility 0s linear 300ms;
}
.how__item.is-open .how__inner {
  opacity: 1; visibility: visible;
  transition: opacity 300ms ease 120ms, visibility 0s;
}
.how__inner p { max-width: 34rem; opacity: 0.8; font-size: 0.98rem; }
/* the bottom breathing room belongs to the answer, not to its last paragraph,
   or an answer that ends in a list loses it */
.how__inner > :last-child { padding-bottom: 1.35rem; }

@media (min-width: 900px) {
  .how { display: grid; grid-template-columns: 0.85fr 1.15fr; gap: clamp(3rem, 6vw, 5rem); align-items: start; }
  .how__intro { position: sticky; top: 5.5rem; }
  .how__list { margin-top: 0; }
}

@media (prefers-reduced-motion: reduce) {
  .how__a, .how__inner, .how__mark::before, .how__mark::after { transition: none; }
  .how__item.is-open .how__inner { transition: none; }
}

/* ---- watch: the stack -----------------------------------------------------
   Not a grid of embeds. One video in front and the rest receding behind it in
   real perspective, the way Time Machine stacks windows, so browsing reads as
   depth rather than as a carousel. The hard constraint: the ones behind must
   never compete with the one in front. They sit tight, lifted just enough to
   show a top edge, dimmed, and always underneath.

   At rest the front card is held back to 55% so the dust field carries on
   through it and the video reads as part of the room's air rather than as an
   embedded box. Accepting the invitation resolves it to full and tightens the
   frame in around it.
   -------------------------------------------------------------------------- */

/* Centred, matching Support: the section reads as an invitation to sit down
   in front of something, and a left rag under a centred video looked like a
   mistake rather than a choice. */
.watch { text-align: center; }
.watch > p, .watch .section-label, .watch h2 { margin-left: auto; margin-right: auto; }
.watch > p { max-width: 34rem; }

.watch__pause {
  margin-top: 1rem; color: var(--accent-ink); opacity: 0.85;
  text-transform: none; letter-spacing: 0.08em;
}
.watch__pause[hidden] { display: none; }

.vstack {
  perspective: 1400px;
  margin: 2.25rem auto 0;
  max-width: 44rem;
  padding-top: 4.2rem;        /* room for the receding cards to show their tops */
}
.vstack__deck { position: relative; aspect-ratio: 16 / 9; transform-style: preserve-3d; }

.vcard {
  position: absolute; inset: 0;
  transform-origin: 50% 50%;
  transition: transform 500ms cubic-bezier(.22, .8, .3, 1),
              opacity 500ms ease;
  will-change: transform, opacity;
}

/* the depth ladder. translateZ does the shrinking, so the perspective is real
   rather than a scale pretending to be one */
/* The lift has to beat the shrink or nothing peeks: pushing a card back also
   pulls its top edge down, so -10% buys about 30px of visible top edge, not
   60px. Tuned to peek, not to loom. */
.vcard[data-d="0"] { transform: translate3d(0, 0, 0); opacity: 1; z-index: 3; }
.vcard[data-d="1"] { transform: translate3d(0, -13%, -150px); opacity: 0.42; z-index: 2; }
.vcard[data-d="2"] { transform: translate3d(0, -24%, -300px); opacity: 0.24; z-index: 1; }
/* anything deeper than the third position is not a depth cue any more */
.vcard[data-d="deep"] { transform: translate3d(0, -29%, -420px); opacity: 0; z-index: 0; }
.vcard:not([data-d="0"]) { pointer-events: none; }

.vcard__hit {
  position: absolute; inset: 0; width: 100%; padding: 0;
  border-radius: 8px; overflow: hidden;
  background: var(--ground-2);
  display: block; cursor: pointer;
}
/* Held back so the field shows through and the video sits in the room's air.
   The thumbnail alone, never the whole card: dimming the invitation with it
   made the one piece of copy that has a job the hardest thing to read. */
.vcard__thumb {
  width: 100%; height: 100%; object-fit: cover;
  opacity: 0.55; transition: opacity 500ms ease;
}
.vcard[data-d="0"] .vcard__hit:hover .vcard__thumb { opacity: 0.72; }
.vcard.is-live .vcard__thumb { opacity: 1; }

/* the frame, which tightens in when the invitation is accepted */
.vcard__frame {
  position: absolute; inset: -10px;
  border: 1px solid color-mix(in oklab, var(--ink) 30%, transparent);
  border-radius: 12px;
  pointer-events: none;
  transition: inset 500ms cubic-bezier(.22, .8, .3, 1), border-color 500ms ease;
}
.vcard.is-live .vcard__frame {
  inset: 0; border-radius: 8px;
  border-color: color-mix(in oklab, var(--accent) 55%, transparent);
}

/* the dust that plays over the resting card, and pulls back off the live one */
.vcard__dust {
  position: absolute; inset: 0; pointer-events: none; z-index: 2;
  opacity: 1; transition: opacity 600ms ease;
}
.vcard.is-live .vcard__dust, .vcard:not([data-d="0"]) .vcard__dust { opacity: 0; }

.vcard__invite {
  position: absolute; inset: 0; z-index: 3;
  display: grid; place-content: center; justify-items: center; gap: 0.5rem;
  padding: 1rem;
  /* barely a veil: the imagery is doing work and should stay legible, so the
     copy earns its contrast from a shadow rather than from covering the shot */
  background: radial-gradient(120% 90% at 50% 60%,
    color-mix(in srgb, var(--ink-deep) 34%, transparent), transparent 78%);
  color: #fdf9f6;
  text-shadow: 0 1px 12px color-mix(in srgb, #17222b 85%, transparent);
  transition: opacity 400ms ease;
}
.vcard.is-live .vcard__invite { opacity: 0; pointer-events: none; }
.vcard__t { font-size: 0.72rem; letter-spacing: 0.16em; opacity: 0.95; }
.vcard__line { font-style: italic; font-size: 0.95rem; opacity: 0.8; max-width: 22rem; }
.vcard__go {
  margin-top: 0.5rem;
  width: 3.2rem; height: 3.2rem; border-radius: 50%;
  display: grid; place-items: center;
  background: color-mix(in srgb, var(--ink-deep) 70%, transparent);
  border: 1px solid var(--accent);
  transition: background 320ms ease, transform 320ms ease;
}
.vcard[data-d="0"] .vcard__hit:hover .vcard__go { background: var(--accent); transform: scale(1.05); }

.vcard__player { position: absolute; inset: 0; z-index: 4; }
.vcard__player iframe { width: 100%; height: 100%; border: 0; display: block; }

/* the bar: what is in front, and what comes next by name */
.vstack__bar {
  display: grid; justify-items: center; gap: 1rem;
  margin-top: 1.6rem;
}
.vstack__cap {
  font-size: 0.92rem; font-style: italic; opacity: 0.75;
  max-width: 34rem; margin-inline: auto;
  transition: opacity 300ms ease;
}
.vstack__cap.is-turning { opacity: 0; }
.vstack__next {
  display: inline-flex; align-items: center; gap: 0.7rem;
  font-family: var(--font-mono); font-size: 0.7rem;
  letter-spacing: 0.14em; text-transform: uppercase;
  padding: 0.8rem 1.4rem; border-radius: 999px;
  border: 1px solid var(--hairline);
  color: var(--ink-deep);
  transition: border-color 320ms ease, background 320ms ease, color 320ms ease;
}
.vstack__next[hidden] { display: none; }
.vstack__next:hover {
  border-color: color-mix(in oklab, var(--accent) 60%, transparent);
  background: color-mix(in srgb, var(--accent) 10%, transparent);
  color: var(--accent-ink);
}
.vstack__arrow { display: grid; place-items: center; transition: transform 320ms ease; }
.vstack__next:hover .vstack__arrow { transform: translateX(3px); }

/* Phones: no perspective. A stack you cannot reach past is just clutter on a
   narrow screen, so only the front card is drawn and the named next control
   carries the browsing on its own. */
@media (max-width: 720px) {
  .vstack { perspective: none; padding-top: 0; }
  .vcard { transition: opacity 400ms ease; }
  .vcard[data-d="0"] { transform: none; opacity: 1; }
  .vcard:not([data-d="0"]) { transform: none; opacity: 0; }
  .vcard__frame { inset: -6px; }
}

/* Reduced motion: a straight crossfade, no depth, nothing travelling. */
@media (prefers-reduced-motion: reduce) {
  .vstack { perspective: none; padding-top: 0; }
  .vcard { transition: opacity 240ms linear; }
  .vcard[data-d="0"] { transform: none; opacity: 1; }
  .vcard:not([data-d="0"]) { transform: none; opacity: 0; }
  .vcard__frame, .vcard__hit, .vcard__invite, .vcard__dust { transition: none; }
}

/* ---- take it with you ----------------------------------------------------
   Two doors, after the videos and before the email ask. Support further up
   already made the case and holds the subscription levels, so this stays a pair
   links and one line rather than a second pitch. --------------------------- */

.encore { text-align: center; padding-block: clamp(2.5rem, 7svh, 4.5rem); }
.encore[hidden] { display: none; }
.encore__line {
  max-width: 30rem; margin: 0 auto;
  font-style: italic; font-size: 1.02rem; opacity: 0.8;
}
.encore__acts {
  display: flex; flex-wrap: wrap; justify-content: center;
  gap: 0.9rem; margin-top: 1.6rem;
}
.encore__acts .btn[hidden] { display: none; }
.encore__acts .btn:not(.btn--gold):hover {
  border-color: color-mix(in oklab, var(--accent) 65%, transparent);
  color: var(--accent-ink);
}

/* ---- tap in ---------------------------------------------------------------- */

/* ---- tap in: Kit's embed --------------------------------------------------
   Kit controls the form's own look from their designer. Only three things
   belong here: hold its space so the async load can't shift the page, and
   nudge the button's radius and hover to match the site's other buttons.
   Nothing structural, because Kit can change their internals without warning. */

.tapin__embed { margin-top: 0; width: 100%; }
/* Kit's form lays an image beside the fields; a narrow cap clipped it. Let it
   use the column, and stop its own media from overflowing. */
.tapin__embed [data-uid], .tapin__embed form { max-width: 100% !important; width: 100% !important; }
.tapin__embed img { max-width: 100%; height: auto; }
.tapin__embed * { box-sizing: border-box; }
.tapin__embed form { margin: 0 !important; }
.tapin__embed button,
.tapin__embed [type="submit"] {
  border-radius: 999px !important;
  transition: filter .3s ease, transform .3s ease !important;
}
.tapin__embed button:hover,
.tapin__embed [type="submit"]:hover { filter: brightness(1.06) !important; }
.tapin__embed input { border-radius: 999px !important; }

/* ---- support ---------------------------------------------------------------- */

.support { text-align: center; }
.support > p, .support .section-label, .support h2 { margin-left: auto; margin-right: auto; }
.support > p { max-width: 34rem; }
.support__btn { display: grid; gap: 0.3rem; padding: 1.3rem 1.5rem; border-radius: 10px; text-align: left; background: color-mix(in srgb, white 35%, transparent); }
body.night .support__btn { background: color-mix(in srgb, white 5%, transparent); }
.support__btn:hover { border-color: color-mix(in oklab, var(--accent) 70%, transparent); }
.support__t { font-family: var(--font-display); font-weight: 400; font-size: 1rem; text-transform: none; letter-spacing: 0.02em; }
.support__d { opacity: 0.6; text-transform: none; letter-spacing: 0.06em; }
@media (min-width: 720px) { .support__row { grid-template-columns: repeat(3, 1fr); } }

/* ---- the form nest --------------------------------------------------------
   Both forms on the page fold away behind an invitation. A Google Form's
   first page is long, and Kit's is not short either; neither should be the
   tallest thing in its section before anyone has chosen to fill it in.

   The panel fans rather than simply growing: the container opens on a 460ms
   settle while the contents ride up behind it, arriving about 100ms later, so
   the motion has a front and a back instead of one flat height change.
   -------------------------------------------------------------------------- */

.nest {
  margin-top: 1.9rem;
  border: 1px solid var(--hairline);
  border-radius: 14px;
  background: color-mix(in srgb, var(--ground-2) 45%, transparent);
  backdrop-filter: blur(6px);
  overflow: hidden;
  transition: border-color 400ms ease, background 400ms ease;
}
.nest[hidden] { display: none; }
.nest.is-open { border-color: color-mix(in oklab, var(--accent) 40%, transparent); }

.nest__face {
  display: grid; gap: 1.5rem; align-items: center;
  padding: clamp(1.5rem, 4vw, 2.25rem);
}
@media (min-width: 720px) {
  .nest__face { grid-template-columns: 12rem 1fr; gap: 2.5rem; }
  /* A nest with no line drawing has no first column to fill, and leaving the
     grid in place squeezed the copy into 12rem with the rest of the row empty. */
  .nest__face:not(:has(.nest__art)) { grid-template-columns: 1fr; }
}

/* the line art, same hand as fig. 1 */
.nest__art { color: var(--ink); opacity: 0.75; }
.nest__art svg { width: 100%; max-width: 12rem; height: auto; margin-inline: auto; display: block; }
.nest__art .accent { color: var(--accent); }

.nest__say h3 {
  font-family: var(--font-display); font-weight: 400;
  font-size: clamp(1.05rem, 2.4vw, 1.25rem); line-height: 1.3;
  color: var(--ink-deep); margin-bottom: 0.55rem;
}
.nest__say p { font-size: 0.97rem; opacity: 0.78; max-width: 32rem; }

.nest__open {
  display: inline-flex; align-items: center; gap: 0.6rem;
  margin-top: 1.35rem;
  font-family: var(--font-mono); font-size: 0.72rem;
  letter-spacing: 0.14em; text-transform: uppercase;
  padding: 0.85rem 1.5rem; border-radius: 999px;
  color: #fdf9f6;
  background: linear-gradient(135deg, var(--accent), color-mix(in srgb, var(--accent-ink) 85%, var(--accent)));
  border: 1px solid color-mix(in oklab, var(--accent) 80%, var(--accent-ink));
  transition: filter 300ms ease, transform 300ms ease;
}
.nest__open:hover { filter: brightness(1.07); transform: translateY(-1px); }
.nest__chev { display: grid; place-items: center; transition: transform 420ms cubic-bezier(.34, 1.28, .48, 1); }
.nest__open[aria-expanded="true"] .nest__chev { transform: rotate(180deg); }

/* the fan: height leads, contents follow */
.nest__panel {
  display: grid; grid-template-rows: 0fr;
  transition: grid-template-rows 460ms cubic-bezier(.22, .8, .3, 1);
}
.nest.is-open .nest__panel { grid-template-rows: 1fr; }
.nest__inner {
  overflow: hidden;
  opacity: 0; visibility: hidden;
  transform: translateY(10px) scale(0.985);
  transform-origin: top center;
  transition: opacity 320ms ease, transform 460ms cubic-bezier(.22, .8, .3, 1),
              visibility 0s linear 460ms;
}
.nest.is-open .nest__inner {
  opacity: 1; visibility: visible; transform: none;
  transition: opacity 380ms ease 100ms,
              transform 500ms cubic-bezier(.22, .8, .3, 1) 100ms,
              visibility 0s;
}
.nest__inner > * { padding: 0 clamp(1.5rem, 4vw, 2.25rem) clamp(1.5rem, 4vw, 2.25rem); }

/* reduced motion: a plain fade, no fan, no drift */
@media (prefers-reduced-motion: reduce) {
  .nest__panel, .nest__chev { transition: none; }
  .nest__inner { transform: none; transition: opacity 200ms linear, visibility 0s linear 200ms; }
  .nest.is-open .nest__inner { transform: none; transition: opacity 200ms linear, visibility 0s; }
}

/* ---- the band above booking ----------------------------------------------
   A thin letterbox, wearing the same edge gradients as the reel so it belongs
   to the page rather than sitting on it. Deliberately short: it is a hint at
   the performance and the travel, not a gallery. ------------------------- */

.band { max-width: none; padding: 0; }
.band__frame { position: relative; height: clamp(11rem, 26svh, 18rem); overflow: hidden; }
.band__img {
  width: 100%; height: 100%;
  object-fit: cover;
  /* A thin band through the middle of this frame lands on cactus and loses
     the player entirely. Sitting the crop higher keeps him, the drum and the
     cymbals in it, with the landscape still reading around them. */
  object-position: 56% 34%;
  display: block;
}
/* Lighter than the reel's veil on purpose. This band is short, so the same
   strength washed the whole picture out and left a smear. Enough to blend the
   edges, not enough to erase what is in them. */
.band__veil {
  position: absolute; inset: 0;
  background:
    linear-gradient(to top,
      color-mix(in srgb, var(--ground) 78%, transparent) 0%,
      color-mix(in srgb, var(--ground) 12%, transparent) 26%,
      transparent 48%),
    linear-gradient(to bottom,
      color-mix(in srgb, var(--ground) 78%, transparent) 0%,
      color-mix(in srgb, var(--ground) 12%, transparent) 26%,
      transparent 48%),
    linear-gradient(to right,
      color-mix(in srgb, var(--ground) 70%, transparent) 0%,
      transparent 16%),
    linear-gradient(to left,
      color-mix(in srgb, var(--ground) 70%, transparent) 0%,
      transparent 16%);
}

/* ---- book ---------------------------------------------------------------- */

.book__embed { margin-top: 0; }
.book__embed iframe { width: 100%; min-height: 44rem; border: 0; border-radius: 8px; background: #fdf9f6; }
.book__fallback { margin-top: 1.5rem; opacity: 0.85; }

/* ---- footer ---------------------------------------------------------------- */

.footer {
  padding: 3rem var(--space);
  text-align: center;
  border-top: 1px solid var(--hairline);
}
.footer__sig { height: 2rem; width: auto; margin: 0 auto 1rem; opacity: 0.8; }
.footer .mono { opacity: 0.55; text-transform: none; letter-spacing: 0.06em; }
/* The mini player is fixed to the bottom edge, so the footer has to hold a
   lane clear for it. Only while it is docked: reserving it always left 72px of
   empty page under the last line, which is what scrolling past the footer into
   nothing actually looked like. */
body.has-mini .footer { padding-bottom: calc(3rem + 4.5rem); }

/* The credit line is a matter of record, so it is set below everything and
   quieter than everything: readable if you look for it, invisible if not. */
.footer__credit {
  margin-top: 0.85rem;
  max-width: 34rem; margin-inline: auto;
  font-size: 0.76rem; line-height: 1.6;
  opacity: 0.38;
}

/* plain list inside an FAQ answer: it is a list of what money does, so it
   reads as a list, but without bullets shouting from a centred column */
.how__list-plain { list-style: none; margin: 0.75rem 0 0; padding: 0; max-width: 34rem; }
.how__list-plain li { position: relative; padding-left: 1.1rem; opacity: 0.8; font-size: 0.98rem; }
.how__list-plain li + li { margin-top: 0.4rem; }
.how__list-plain li::before {
  content: ""; position: absolute; left: 0; top: 0.72em;
  width: 5px; height: 5px; border-radius: 50%;
  background: var(--accent); opacity: 0.7;
}

/* An email address written for people, not for link harvesters. It is set in
   the mono face so it reads as something to copy rather than something to
   click, and it is allowed to break anywhere so it cannot push a narrow
   phone into horizontal scroll. */
.mail {
  font-family: var(--font-mono);
  font-size: 0.94em;
  color: var(--accent-ink);
  overflow-wrap: anywhere;
}

/* ---- mini player ---------------------------------------------------------- */

.mini[hidden] { display: none; }
.mini {
  position: fixed; left: 50%; bottom: max(0.8rem, env(safe-area-inset-bottom)); z-index: 50;
  transform: translateX(-50%);
  display: flex; align-items: center; gap: 0.8rem;
  width: min(92vw, 22rem);
  padding: 0.6rem 0.9rem;
  border-radius: 999px;
  background: color-mix(in srgb, var(--ground) 78%, var(--room));
  border: 1px solid var(--hairline);
  backdrop-filter: blur(14px);
  box-shadow: 0 18px 40px -18px color-mix(in srgb, var(--ink-deep) 50%, transparent);
}
.mini__play, .mini__next { width: 2.2rem; height: 2.2rem; border-radius: 50%; display: grid; place-items: center; flex: none; }
.mini__play { border: 1px solid color-mix(in oklab, var(--accent) 80%, transparent); color: var(--accent-ink); }
.mini__meta { flex: 1; min-width: 0; display: grid; gap: 0.35rem; }
.mini__title {
  font-family: var(--font-display); font-weight: 400; font-size: 0.78rem; letter-spacing: 0.03em;
  white-space: nowrap; overflow: hidden; text-overflow: ellipsis;
}
.mini__bar { height: 2px; border-radius: 2px; background: var(--hairline); overflow: hidden; display: block; }
.mini__fill { display: block; height: 100%; width: 0%; background: var(--accent); }

/* ---- reduced motion ---------------------------------------------------------- */

@media (prefers-reduced-motion: reduce) {
  #field { display: none; }
  .welcome { transition: none; }
  #field-fallback, .track, .btn, .modesw { transition: none; }
}

/* ---- the playing video ---------------------------------------------------
   Full-bleed frame that breaks out of the contained column, because the footage is
   a room you look into, not a card on the page. ------------------------- */

.reel { max-width: none; padding: 0; }
.reel__frame {
  position: relative;
  height: min(76svh, 40rem);
  overflow: hidden;                 /* clips the parallax drift */
  background: var(--ground-2);
}
.reel__video {
  position: absolute; inset: -6% 0;  /* headroom for the parallax drift */
  width: 100%; height: 112%;
  object-fit: cover;
  object-position: center 40%;       /* favours hands and drum over ceiling */
}
/* The ground closing in from every edge, so the clip sits inside the page
   rather than being pasted onto it. Only the bottom was treated before, which
   left the top and both sides as hard cuts and made a full bleed band out of
   what should be a window. The bottom is still the strongest, because the
   caption sits on it and has to stay readable. Painted under the caption and
   the play control, never over them. */
.reel__veil {
  position: absolute; inset: 0;
  background:
    linear-gradient(to top,
      color-mix(in srgb, var(--ground) 92%, transparent) 0%,
      color-mix(in srgb, var(--ground) 26%, transparent) 38%,
      transparent 72%),
    linear-gradient(to bottom,
      color-mix(in srgb, var(--ground) 82%, transparent) 0%,
      color-mix(in srgb, var(--ground) 22%, transparent) 20%,
      transparent 46%),
    linear-gradient(to right,
      color-mix(in srgb, var(--ground) 70%, transparent) 0%,
      color-mix(in srgb, var(--ground) 14%, transparent) 9%,
      transparent 22%),
    linear-gradient(to left,
      color-mix(in srgb, var(--ground) 70%, transparent) 0%,
      color-mix(in srgb, var(--ground) 14%, transparent) 9%,
      transparent 22%);
}
.reel__tap {
  position: absolute; left: 50%; top: 50%;
  transform: translate(-50%, -50%);
  display: inline-flex; align-items: center; gap: 0.5rem;
  padding: 0.75rem 1.35rem;
  border-radius: 999px;
  color: #fdf9f6;
  background: color-mix(in srgb, var(--accent) 88%, transparent);
  border: 1px solid color-mix(in oklab, var(--accent) 60%, white);
  backdrop-filter: blur(6px);
  font-family: var(--font-mono); font-size: 0.66rem;
  letter-spacing: 0.14em; text-transform: uppercase;
  box-shadow: 0 10px 30px -12px color-mix(in srgb, black 70%, transparent);
  transition: transform .3s ease, filter .3s ease;
}
.reel__tap[hidden] { display: none; }
.reel__tap:hover { filter: brightness(1.08); transform: translate(-50%, -50%) scale(1.03); }

.reel__caption {
  position: absolute; left: 0; right: 0; bottom: 0;
  padding: 0 var(--space) clamp(2rem, 5svh, 3.5rem);
  max-width: var(--maxw); margin: 0 auto;
}
.reel__caption .section-label { margin-bottom: 0.5rem; }
.reel__line {
  font-family: var(--font-body); font-style: italic;
  font-size: clamp(1.15rem, 3vw, 1.6rem);
  color: var(--ink-deep); max-width: 26rem;
}
@media (max-width: 700px) {
  .reel__frame { height: min(70svh, 34rem); }
  .reel__video { object-position: center 42%; }
}

/* ---- thank-you page ------------------------------------------------------ */

body.ty { display: block; }
.ty__wrap { max-width: 44rem; margin: 0 auto; padding: clamp(3rem, 9svh, 6rem) var(--space) 6rem; }
.ty__mark { display: inline-block; margin-bottom: 2.5rem; }
.ty__mark .sig { height: 2rem; width: auto; }
.ty__title {
  font-family: var(--font-hero); font-weight: 500;
  font-size: clamp(2rem, 7vw, 3.4rem); line-height: 1.05;
  color: var(--ink-deep); margin-bottom: 1rem;
}
.ty__lede { font-size: 1.1rem; font-style: italic; opacity: 0.85; max-width: 32rem; }
.ty__download { margin-top: 2.5rem; }
.ty__zip { display: inline-block; }
.ty__hint { margin-top: 0.8rem; opacity: 0.55; text-transform: uppercase; }
.ty__tracks { list-style: none; margin-top: 1rem; }
.ty__tracks a {
  display: flex; align-items: center; gap: 1rem;
  padding: 0.85rem 0.5rem;
  border-bottom: 1px solid var(--hairline);
  text-decoration: none;
  transition: background .3s ease;
  border-radius: 4px;
}
.ty__tracks li:first-child a { border-top: 1px solid var(--hairline); }
.ty__tracks a:hover { background: color-mix(in srgb, var(--ink) 5%, transparent); }
.ty__num { font-family: var(--font-mono); font-size: 0.72rem; opacity: 0.5; width: 1.6rem; }
.ty__t { font-family: var(--font-display); font-size: 0.95rem; flex: 1; }
.ty__dl { color: var(--accent-ink); opacity: 0.85; }
.ty__rule { border: 0; border-top: 1px solid var(--hairline); margin: 3rem 0; }
.ty__give h2 { margin-bottom: 0.75rem; }
.ty__back { margin-top: 3rem; }

/* ---- the privacy sheet ----------------------------------------------------
   A panel over the page, not a page. Opening the policy used to navigate away,
   which stopped playback, which breaks the one promise the whole site makes.
   Nothing here interrupts the audio: no navigation, no reload, no new document.
   -------------------------------------------------------------------------- */

.sheet[hidden] { display: none; }
.sheet { position: fixed; inset: 0; z-index: 200; }
.sheet__scrim {
  position: absolute; inset: 0;
  background: color-mix(in srgb, #17222b 62%, transparent);
  backdrop-filter: blur(3px);
  opacity: 0; transition: opacity 320ms ease;
}
.sheet.is-open .sheet__scrim { opacity: 1; }

.sheet__panel {
  position: absolute; inset: 0;
  margin: auto;
  width: min(46rem, calc(100% - 2rem));
  max-height: min(88svh, 52rem);
  overflow-y: auto; overscroll-behavior: contain;
  background: var(--ground);
  border: 1px solid var(--hairline);
  border-radius: 14px;
  padding: clamp(1.75rem, 5vw, 3rem);
  opacity: 0; transform: translateY(14px) scale(0.99);
  transition: opacity 320ms ease, transform 380ms cubic-bezier(.22, .8, .3, 1);
}
.sheet.is-open .sheet__panel { opacity: 1; transform: none; }

.sheet__close {
  position: sticky; top: 0; float: right;
  display: inline-flex; align-items: center; gap: 0.45rem;
  font-family: var(--font-mono); font-size: 0.6rem;
  letter-spacing: 0.14em; text-transform: uppercase;
  padding: 0.5rem 0.85rem; border-radius: 999px;
  border: 1px solid var(--hairline);
  background: var(--ground);
  color: var(--ink);
  transition: border-color .3s, color .3s;
}
.sheet__close:hover { border-color: color-mix(in oklab, var(--accent) 60%, transparent); color: var(--accent-ink); }

.sheet__body .ty__title { font-size: clamp(1.6rem, 5vw, 2.4rem); }
.sheet__body .ty__mark, .sheet__body .ty__back { display: none; }
.sheet__loading { opacity: 0.5; text-transform: none; letter-spacing: 0.1em; }

body.sheet-open { overflow: hidden; }

@media (prefers-reduced-motion: reduce) {
  .sheet__scrim, .sheet__panel { transition: opacity 180ms linear; }
  .sheet__panel { transform: none; }
}

/* ---- privacy ---------------------------------------------------------------
   Same shell as the thank-you page. A privacy policy nobody can read is not a
   privacy policy, so this one is set in the body face at reading size rather
   than in legal small print. -------------------------------------------- */
.legal h2 {
  font-size: clamp(1.05rem, 2.6vw, 1.25rem);
  margin-top: 2.5rem; margin-bottom: 0.6rem;
}
.legal p + p { margin-top: 0.85rem; }
.legal ul { margin: 0.85rem 0 0 1.1rem; }
.legal li + li { margin-top: 0.5rem; }
.legal li, .legal p { opacity: 0.88; }
.legal__date { margin-top: 3rem; opacity: 0.5; text-transform: none; letter-spacing: 0.08em; }

/* ---- the offer control ----------------------------------------------------
   One pill holding all three ways to take part, giving first. The fill physically
   travels between options rather than snapping, and sheds a little dust off its
   trailing edge, so the control belongs to the same air as the rest of the
   site instead of arriving from a component library. ---------------------- */

.offer { display: grid; justify-items: center; gap: 1.5rem; margin-top: 2.25rem; }

.offer__switch {
  position: relative;
  display: inline-grid;
  grid-auto-flow: column;
  padding: 0.32rem;
  border-radius: 999px;
  border: 1px solid color-mix(in oklab, var(--ink) 18%, transparent);
  background: color-mix(in srgb, var(--ground-2) 55%, transparent);
  backdrop-filter: blur(6px);
  isolation: isolate;
}

/* the travelling fill */
.offer__fill {
  position: absolute;
  top: 0.32rem; left: 0.32rem;   /* height set in JS so it can travel on both axes */
  border-radius: 999px;
  background: linear-gradient(135deg, var(--accent), color-mix(in srgb, var(--accent-ink) 80%, var(--accent)));
  box-shadow: 0 6px 18px -8px color-mix(in srgb, var(--accent-ink) 70%, transparent);
  z-index: -1;
  /* a settle at the end rather than a hard stop */
  transition: transform 460ms cubic-bezier(.34, 1.32, .48, 1),
              width 460ms cubic-bezier(.34, 1.32, .48, 1),
              height 460ms cubic-bezier(.34, 1.32, .48, 1);
}

/* the dust the fill sheds as it travels */
.offer__dust {
  position: absolute; inset: 0;
  border-radius: 999px;
  pointer-events: none;
  z-index: 0;
}

.offer__opt {
  position: relative; z-index: 1;
  font-family: var(--font-mono); font-size: 0.72rem;
  letter-spacing: 0.14em; text-transform: uppercase;
  color: var(--ink);
  padding: 0.85rem 1.5rem;
  border-radius: 999px;
  white-space: nowrap;
  transition: color 320ms ease, background 320ms ease;
}
.offer__opt:hover { background: color-mix(in srgb, var(--sand) 22%, transparent); }
.offer__opt[aria-selected="true"] { color: #fdf9f6; }
.offer__opt[aria-selected="true"]:hover { background: transparent; }

.offer__body { display: grid; justify-items: center; gap: 1.1rem; min-height: 8.5rem; }
.offer__body:has(.levels:not([hidden])) { gap: 0.95rem; }
.offer__sub {
  font-style: italic; opacity: 0.85; max-width: 28rem;
  transition: opacity 320ms ease, transform 320ms ease;
}
.offer__go {
  font-family: var(--font-mono); font-size: 0.75rem; letter-spacing: 0.14em;
  text-transform: uppercase; text-decoration: none;
  padding: 0.95rem 2.1rem; border-radius: 999px;
  color: #fdf9f6;
  background: linear-gradient(135deg, var(--accent), color-mix(in srgb, var(--accent-ink) 85%, var(--accent)));
  border: 1px solid color-mix(in oklab, var(--accent) 80%, var(--accent-ink));
  transition: filter 320ms ease, transform 320ms ease;
}
.offer__go:hover { filter: brightness(1.07); transform: translateY(-1px); }

/* ---- the second layer: subscription levels ------------------------------------
   Three prices for one thing, so they read as a set: same pill language as
   the control above them, one size quieter, and the selected one fills from
   the bottom up rather than switching colour. Filling upward is the only
   ornament here and it is doing a job, because "level" is the idea and a
   rising fill is what that looks like. ------------------------------------ */

.levels {
  display: flex; flex-wrap: wrap; justify-content: center;
  gap: 0.6rem;
}
.levels[hidden] { display: none; }

.lvl {
  position: relative; isolation: isolate;
  display: grid; gap: 0.1rem; justify-items: center;
  min-width: 5.4rem;
  padding: 0.6rem 1rem;
  border-radius: 14px;
  border: 1px solid color-mix(in oklab, var(--ink) 20%, transparent);
  background: color-mix(in srgb, var(--ground-2) 45%, transparent);
  color: var(--ink);
  overflow: hidden;
  /* the staggered entry, undone by .is-in */
  opacity: 0; transform: translateY(6px);
  transition: opacity 380ms ease, transform 380ms cubic-bezier(.34, 1.32, .48, 1),
              color 300ms ease, border-color 300ms ease;
}
.lvl.is-in { opacity: 1; transform: none; }

/* fills from the floor up when chosen */
.lvl__fill {
  position: absolute; inset: 0; z-index: -1;
  border-radius: inherit;
  transform-origin: bottom;
  transform: scaleY(0);
  background: linear-gradient(135deg, var(--accent), color-mix(in srgb, var(--accent-ink) 80%, var(--accent)));
  transition: transform 420ms cubic-bezier(.34, 1.32, .48, 1);
}
.lvl[aria-checked="true"] .lvl__fill { transform: scaleY(1); }
.lvl[aria-checked="true"] {
  color: #fdf9f6;
  border-color: color-mix(in oklab, var(--accent) 80%, var(--accent-ink));
}
.lvl:hover { border-color: color-mix(in oklab, var(--accent) 55%, transparent); }
.lvl:focus-visible { outline: 2px solid var(--accent); outline-offset: 3px; }

.lvl__amt {
  font-family: var(--font-mono); font-size: 1.05rem; letter-spacing: 0.02em;
  line-height: 1;
}
.lvl__per {
  font-family: var(--font-mono); font-size: 0.58rem; letter-spacing: 0.12em;
  text-transform: uppercase; opacity: 0.7;
}

/* three across is the comparison; wrapping one below breaks it, so the chips
   give up padding before they give up the row */
@media (max-width: 360px) {
  .lvl { min-width: 4.4rem; padding: 0.55rem 0.6rem; }
  .levels { gap: 0.45rem; }
}

.levels__note {
  font-style: italic; font-size: 0.95rem; opacity: 0.8;
  max-width: 30rem; margin-inline: auto;
  transition: opacity 280ms ease, transform 280ms ease;
}
.levels__note[hidden] { display: none; }
.levels__note.is-swapping { opacity: 0; transform: translateY(3px); }

@media (prefers-reduced-motion: reduce) {
  .lvl { transition: color 200ms ease, border-color 200ms ease; opacity: 1; transform: none; }
  .lvl__fill { transition: none; }
  .levels__note { transition: opacity 160ms linear; }
  .levels__note.is-swapping { transform: none; }
}

/* swapping sides: the copy exhales and comes back */
.offer__body.is-swapping .offer__sub,
.offer__body.is-swapping .offer__go { opacity: 0; transform: translateY(4px); }

@media (prefers-reduced-motion: reduce) {
  .offer__fill { transition: none; }
  .offer__sub, .offer__go { transition: opacity 160ms linear; }
  .offer__body.is-swapping .offer__sub,
  .offer__body.is-swapping .offer__go { transform: none; }
}

@media (max-width: 720px) {
  .offer__opt { padding: 0.8rem 1.1rem; font-size: 0.66rem; letter-spacing: 0.1em; }
}
/* Three options will not sit side by side on a phone without becoming
   unreadable, so the pill becomes a column and the fill slides down it. */
@media (max-width: 560px) {
  .offer__switch { grid-auto-flow: row; width: min(100%, 20rem); }
  .offer__opt { text-align: center; }
}
