/* =========================================================================
   Whisper — built to www.interfaces.new-DESIGN.md
   Warm near-black (#130F0C) · pure white type · TeX Gyre Heros 400 · rounded
   4px spacing scale · monochrome (no red / orange accents)
   ========================================================================= */

@font-face {
  font-family: "TeX Gyre Heros";
  src: url("fonts/texgyreheros-regular.woff2") format("woff2");
  font-weight: 400;
  font-style: normal;
  font-display: swap;
  /* The face sits its letters low in the line box (ascent 1.15em, descent
     0.28em, caps 0.73em), so text read below centre in every pill and
     button. Same total height, rebalanced so caps sit in the middle;
     nothing moves in layout, and canvas text (alphabetic baseline) is
     untouched. */
  ascent-override: 108%;
  descent-override: 35%;
}
@font-face {
  font-family: "TeX Gyre Heros";
  src: url("fonts/texgyreheros-italic.woff2") format("woff2");
  font-weight: 400;
  font-style: italic;
  font-display: swap;
  /* The face sits its letters low in the line box (ascent 1.15em, descent
     0.28em, caps 0.73em), so text read below centre in every pill and
     button. Same total height, rebalanced so caps sit in the middle;
     nothing moves in layout, and canvas text (alphabetic baseline) is
     untouched. */
  ascent-override: 108%;
  descent-override: 35%;
}

/* EB Garamond (testimonial quotes), Imperial Script (the hero notecard's
   hand) and Aguafina Script (its "Listen." sticker), served from here
   rather than Google Fonts: no third-party stylesheet holding up the first
   paint. Latin subsets from Fontsource, SIL Open Font License (fonts/README). */
@font-face {
  font-family: "EB Garamond";
  src: url("fonts/eb-garamond-latin-400-normal.woff2") format("woff2");
  font-weight: 400;
  font-style: normal;
  font-display: swap;
  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: "EB Garamond";
  src: url("fonts/eb-garamond-latin-500-normal.woff2") format("woff2");
  font-weight: 500;
  font-style: normal;
  font-display: swap;
  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: "EB Garamond";
  src: url("fonts/eb-garamond-latin-400-italic.woff2") format("woff2");
  font-weight: 400;
  font-style: italic;
  font-display: swap;
  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: "Imperial Script";
  src: url("fonts/imperial-script-latin-400-normal.woff2") format("woff2");
  font-weight: 400;
  font-style: normal;
  font-display: swap;
  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: "Aguafina Script";
  src: url("fonts/aguafina-script-latin-400-normal.woff2") format("woff2");
  font-weight: 400;
  font-style: normal;
  font-display: swap;
  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: "Reenie Beanie";
  src: url("fonts/reenie-beanie-latin-400-normal.woff2") format("woff2");
  font-weight: 400;
  font-style: normal;
  font-display: swap;
}
/* Bold, under its own name so only what asks for it (the Overheard quotes'
   key lines) changes; everything else bold keeps the browser's emboldened
   regular */
@font-face {
  font-family: "TeX Gyre Heros Bold";
  src: url("fonts/texgyreheros-bold.woff2") format("woff2");
  font-weight: 400;
  font-style: normal;
  font-display: swap;
  ascent-override: 108%;
  descent-override: 35%;
}
/* 8-bit: the iceberg's questions and talk bubbles, beside the pixel heart
   and question marks */
@font-face {
  font-family: "Pixelify Sans";
  src: url("fonts/pixelify-sans-latin-400-normal.woff2") format("woff2");
  font-weight: 400;
  font-style: normal;
  font-display: swap;
}

:root {
  color-scheme: dark;
  /* Static fallbacks only. paint() writes live --bg/--fg on body so a scroll
     frame does not invalidate every color-mix inheritor hung off :root.
     --ground on :root is the document-canvas wash (html + Safari chrome). */
  --bg:            #000000;
  --fg:            #FFFFFF;
  --ground:        #000000;

  --accent-cool:   #00D9FF;   /* focus ring only */
  --accent-yellow: #FFD700;   /* invalid field ring */
  --accent-red:    #E11D2E;   /* form error copy */

  /* Spacing scale — multiples of 4px only */
  --s-1:   4px;
  --s-2:   8px;
  --s-3:  12px;
  --s-4:  16px;
  --s-5:  20px;
  --s-6:  24px;
  --s-8:  32px;
  --s-12: 48px;
  --s-14: 56px;
  /* Section title → first content block (rail, bands, FAQ list). */
  --gap-title-block: var(--s-14);
  /* Shared mobile tile height: How bands, Overheard quotes, Bartow cards. */
  --tile-h: 420px;
  --s-20: 80px;
  --s-27: 108px;

  /* Radius */
  --r-sm:   8px;   /* inputs */
  /* Ramp How we build cards: --homepage-card-radius: 40px with
     --homepage-card-corner-shape: superellipse(1.6) — between round (1)
     and squircle (2), not a circular arc. Photos/tiles/imagery follow;
     pills stay capsules. */
  --r-md:  40px;
  --r-md-shape: superellipse(1.6);
  --r-pill: 999px; /* CTA buttons */

  /* Type */
  --font: "TeX Gyre Heros", Helvetica, "Helvetica Neue", Arial, sans-serif;
  --font-quote: "EB Garamond", "Times New Roman", Times, serif;
  --t-micro:   12px;
  --t-caption: 13px;
  --t-body:    16px;
  --t-h2:      18px;

  --wrap: 1440px;
  --pad: var(--s-6);

  --ease: cubic-bezier(0.16, 1, 0.3, 1);
}

/* Ramp pairs 40px radius with superellipse(1.6). Unsupported browsers
   ignore corner-shape and keep the circular 40px arc — same fallback as
   ramp.design. Pills, buttons, stepper, and Donate stay off this list. */
.photo,
.slot,
.show__viewport,
.quote,
.pop img,
.video,
.gath__img,
.gath__stage,
.agenda__img,
.held__shot,
.story__card,
.path,
.step,
.giftfan,
.band__inner,
.stats > .stat,
.stats > .surgeon {
  corner-shape: var(--r-md-shape);
}
.photo img,
.gath__img img,
.held__shot img {
  border-radius: inherit;
  corner-shape: inherit;
}

/* Phone: match overheard’s ~40% cut under section titles */
@media (max-width: 767px) {
  :root { --gap-title-block: calc(var(--s-14) * 0.6); }
}

/* ── Ground ──────────────────────────────────────────────────────────────
   One fixed layer holds the page colour. script.js interpolates it between
   the grounds declared on sections, so every change arrives as a fade rather
   than a hard edge at a section boundary. Text tokens follow the ground's
   luminance.

   iOS Safari: fixed inset:0 stops at the layout viewport while carousels /
   fixed chips still paint under the floating URL bar. Extend the wash past
   that edge, and keep html on the same --ground so the document canvas (the
   band Safari reveals behind chrome, plus overscroll) matches the section. */
.ground {
  position: fixed;
  inset: 0;
  bottom: calc(-1 * (env(safe-area-inset-bottom, 0px) + 96px));
  z-index: -2;
  background: var(--ground, #000000);
  pointer-events: none;
}
body { background: rgba(0, 0, 0, 0); }

.lightmark,
.groundmark { display: block; height: 0; }

/* spacing for the deep-blue end of the ramp: three stops need room to cross
   into each other, so the ramp is scroll distance, not decoration. It used to
   carry all 360px of that distance alone; the deck pitch now sits above it and
   supplies most of it, so the ramp only has to make up the difference — any
   more and it reads as a hole in the page. */
.groundramp {
  display: grid;
  height: 120px;
  align-content: space-between;
}

@media (min-width: 768px)  { :root { --pad: var(--s-8);  } }
@media (min-width: 1024px) { :root { --pad: var(--s-12); } }
@media (min-width: 1440px) { :root { --pad: var(--s-14); } }

*, *::before, *::after { box-sizing: border-box; }
/* Empty <i>s (dots, burger lines, the FAQ's plus) hold no text, but the
   browser still resolves their font, and <i> is italic by default — so the
   italic Heros (53KB) was downloaded for nothing at every load. */
i:empty { font-style: normal; }

/* overflow-anchor off: the ground ladder and the back-to-top jump both depend on
   scroll position meaning what it says, and anchoring silently rewrites it when
   media above the viewport resolves */
html {
  -webkit-text-size-adjust: 100%;
  scroll-behavior: smooth;
  overflow-anchor: none;
  /* Safari paints this canvas behind bottom chrome / overscroll; paint()
     sets it inline to match the fixed .ground wash. */
  background-color: #000000;
  min-height: 100%;
  min-height: 100dvh;
  /* body alone is not enough on iOS — a rotated or max-content child can still
     widen the document and let the page pan sideways. hidden first for older
     WebKit; clip where supported so the page does not grow a second scrollbar. */
  overflow-x: hidden;
  overflow-x: clip;
}
/* Anchor targets clear the sticky nav plus its 125% progressive blur.
   Split between 48px (titles under frost) and 128/188 (too much air). */
.section[id] { scroll-margin-top: calc(var(--s-20) + var(--s-4)); } /* 96px */
#top { scroll-margin-top: 0; }
/* Footer signup keeps the old #newsletter hash. */
#newsletter { scroll-margin-top: calc(var(--s-20) + var(--s-4)); }
@media (min-width: 900px) {
  .section[id],
  #newsletter { scroll-margin-top: calc(var(--s-27) + var(--s-12)); } /* 156px */
}
@media (prefers-reduced-motion: reduce) { html { scroll-behavior: auto; } }

body {
  /* Mixes live here so they recompute from body's --fg, not :root's. */
  --fg-60:         color-mix(in srgb, var(--fg) 62%, transparent);
  --fg-50:         color-mix(in srgb, var(--fg) 52%, transparent);
  --fg-35:         color-mix(in srgb, var(--fg) 35%, transparent);
  --rule:          color-mix(in srgb, var(--fg) 16%, transparent);
  --rule-strong:   color-mix(in srgb, var(--fg) 32%, transparent);
  margin: 0;
  background: var(--bg);
  color: var(--fg);
  font-family: var(--font);
  font-size: var(--t-body);
  font-weight: 400;
  line-height: 24px;
  -webkit-font-smoothing: antialiased;
  overflow-x: hidden;
  overflow-x: clip;
}

h1, h2, h3, h4, p, ul, ol, figure, blockquote { margin: 0; }
ul, ol { padding: 0; list-style: none; }
img, svg { max-width: 100%; display: block; }
button { font: inherit; color: inherit; }

a { color: var(--fg); text-decoration: none; transition: opacity .18s var(--ease); }
a:hover { opacity: .7; }
a:active { opacity: .5; }

:focus-visible { outline: 2px solid var(--fg); outline-offset: 4px; }

/* Selection inverts the ground rather than using the OS blue: --fg and --bg are
   both rewritten per-scroll, so the highlight is black on the light sections and
   white on the dark ones, and the selected text is always the ground it sits on.
   ::selection does not inherit, hence declaring it rather than relying on a
   parent — but it does resolve custom properties, so it tracks the scroll. */
::selection { background: var(--fg); color: var(--bg); }
::-moz-selection { background: var(--fg); color: var(--bg); }

.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: var(--s-4); top: -100px; z-index: 100;
  background: var(--fg); color: var(--bg); padding: var(--s-3) var(--s-4);
  border-radius: var(--r-pill);
  font-size: var(--t-caption); line-height: 19.5px;
}
.skip:focus { top: var(--s-4); }

.wrap {
  width: 100%;
  max-width: var(--wrap);
  margin-inline: auto;
  padding-inline: var(--pad);
}

/* ── Type roles ─────────────────────────────────────────────────────────── */

.display {
  /* 52px / 14vw owned the phone so hard that only fragments of the line were
     on screen. Back to a firm hero without blowing past the column: ~40px at
     375, still a step up from the old 38px floor, ceiling unchanged for desktop. */
  /* 42/10.5vw: a hair above the 40/10 rollback, still well under the 52/14
     phone takeover that clipped the line. */
  font-size: clamp(42px, 10.5vw, 86px);
  /* as tight as this face goes before a descender meets the next line's ascender */
  line-height: 1.05;
  /* same tracking as the Partner with Whisper title (.cine__title) */
  letter-spacing: -0.05em;
  font-weight: 400;
  text-wrap: balance;
}

.h1 {
  /* Floor used to sit at 28px, then 36px — still caption-adjacent for a
     two-line section title on a phone. 42px at 375 is the smallest that
     still reads as a chapter head next to body. */
  font-size: clamp(42px, 10vw, 48px);
  line-height: 1.05;
  /* same tracking as the Partner with Whisper title (.cine__title) */
  letter-spacing: -0.05em;
  font-weight: 400;
  text-wrap: balance;
}

.h2-lg {
  font-size: clamp(24px, 5.2vw, 32px);
  line-height: 1.2;
  letter-spacing: -0.012em;
  font-weight: 400;
  text-wrap: balance;
  max-width: 26ch;
}

/* Arrow standing in for "to" — stroke matched to the typeface's stem weight */
.arrow {
  display: inline-block;
  width: 0.95em;
  height: 1em;
  vertical-align: -0.24em;
}
.arrow svg { width: 100%; height: 100%; overflow: visible; }
.arrow path {
  fill: none;
  stroke: currentColor;
  stroke-width: 3.5;   /* 40 units = 1em, so ~0.088em — Helvetica's stem */
  stroke-linecap: butt;
  stroke-linejoin: miter;
}
/* ↗ on Shop links — Heros's U+2197 is a hairline. Same stem as .arrow, cap-height, baseline. */
.arrow--ne {
  width: 0.78em;
  height: 0.73em;
  margin-left: 0.16em;
  vertical-align: 0;
}
.arrow--ne path { stroke-width: 1.4; } /* 12-unit box at cap-height ≈ Heros stem */
/* → in buttons: Heros's U+2192 sits low on the baseline. Drawn instead, as
   tall as the x-height and centred on it, the same stem as ↗ */
.arrow--e {
  width: 0.82em;
  height: 0.7em;
  margin-left: 0.4em;
  vertical-align: -0.02em;
}
.arrow--e path { stroke-width: 1.3; }

.nobr { white-space: nowrap; }

.lede {
  font-size: clamp(16px, 1.7vw, 20px);
  line-height: 1.55;
  color: var(--fg-60);
  max-width: 46ch;
}

/* ── Buttons ────────────────────────────────────────────────────────────── */

.btn {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  min-height: 40px;
  font-size: var(--t-body);
  line-height: 22px;
  border-radius: var(--r-pill);
  cursor: pointer;
  transition: opacity .18s var(--ease), border-color .18s var(--ease), transform .18s var(--ease);
}
.btn--lg { min-height: 44px; padding-inline: var(--s-8); }

/* Marquee label: the text runs continuously inside the pill on hover.

   The second copy of the label is drawn by CSS from data-label and taken out of
   flow, parked one label-width to the right. In flow it would count toward the
   button's intrinsic width and the pill would come out twice as wide, since
   overflow:hidden only clips what is painted — it does not shrink the box.
   Translating by exactly that offset lands copy two where copy one began, so
   the loop has no seam and the button keeps its normal width.

   Named btn__label, not .marquee: .marquee is already the schools row's
   component further down this file, and reusing the name here handed the button
   that component's box. script.js does the wrapping, so every .btn gets this. */
.btn--marquee { overflow: hidden; }
.btn--marquee .btn__label {
  position: relative;
  display: inline-block;
  white-space: nowrap;
  line-height: inherit;
}
.btn--marquee .btn__label::after {
  content: attr(data-label);
  position: absolute;
  top: 0;
  left: 100%;
  padding-left: var(--s-6);
  /* Hidden at rest. On a wide pill — the phone menu's Donate stretches to the
     sheet — overflow:hidden still leaves room for this second copy to peek
     beside the first, so "Donate Donate" was painting. It only needs to exist
     while the marquee is running. */
  opacity: 0;
}
.btn--marquee:hover .btn__label,
.btn--marquee:focus-visible .btn__label { animation: btnMarquee 2s linear infinite; }
.btn--marquee:hover .btn__label::after,
.btn--marquee:focus-visible .btn__label::after { opacity: 1; }
@keyframes btnMarquee { to { transform: translateX(calc(-100% - var(--s-6))); } }

/* the second copy only earns its keep while it is moving */
@media (prefers-reduced-motion: reduce) {
  .btn--marquee:hover .btn__label,
  .btn--marquee:focus-visible .btn__label { animation: none; }
  .btn--marquee:hover .btn__label::after,
  .btn--marquee:focus-visible .btn__label::after { opacity: 0; }
}

.btn--solid {
  background: var(--fg);
  color: var(--bg);
  border: 1px solid var(--fg);
  padding: calc(var(--s-2) + 1px) var(--s-6) calc(var(--s-2) - 1px);   /* the face sits its letters a hair high: 1px more on top centres them */
}
.btn--solid:hover { opacity: .8; }
.btn--solid:active { opacity: .6; transform: translateY(1px); }

.btn--ghost {
  background: rgba(0, 0, 0, 0);
  color: var(--fg);
  border: 1px solid var(--rule-strong);
  padding: calc(var(--s-2) + 1px) var(--s-6) calc(var(--s-2) - 1px);   /* the face sits its letters a hair high: 1px more on top centres them */
}
.btn--ghost:hover { opacity: 1; border-color: var(--fg); }
.btn--ghost:active { opacity: .6; }

.cta-row { margin-top: var(--s-14); display: flex; justify-content: center; }
.cta-row--left { justify-content: flex-start; margin-top: var(--s-8); }

/* ── Navigation ─────────────────────────────────────────────────────────── */

.nav {
  /* Fixed overlay so logo / Donate / stepper can compact on scroll without
     shrinking a sticky box (that shoved main). A .nav-spacer holds the rest
     height so the hero does not jump. Vertical pad stays --pad at every
     scroll position — the bar must not climb toward the viewport edge. */
  position: fixed; top: 0; left: 0; right: 0; z-index: 50;
  background: rgba(0, 0, 0, 0);
  --nav-compact: 0;
  /* Burger disc (44 → 36). */
  --nav-disc-rest: 44px;
  --nav-disc-delta: 8px;
  --nav-disc: calc(var(--nav-disc-rest) - var(--nav-disc-delta) * var(--nav-compact, 0));
  /* Stepper track: 2× pad (3→2) + links (40→32) = 46→36. Not nav pad. */
  --nav-track-compact: calc(2 * (3px - 1px) + (var(--s-2) - 1px - 2px) + (24px - 4px) + (var(--s-2) + 1px - 2px));
  /* Clicks pass through the empty overlay; children opt back in. */
  pointer-events: none;
  /* Own stacking context so .nav__blur (z-index 0) cannot paint over
     .nav__inner (60) — the earlier mobile fog was the band sitting on top of
     the bare wordmark and burger. */
  isolation: isolate;
}
.nav__inner { pointer-events: auto; }
/* Matches rest .nav__inner (pad + 36px mark). JS overwrites with the
   measured rest height so breakpoint padding stays in lockstep. */
.nav-spacer {
  height: calc(var(--pad) * 2 + 36px);
  pointer-events: none;
}

/* Progressive blur: three stacked layers (was six). Each backdrop-filter is a
   full-viewport sample — six of them on every scrolled frame was the main
   chrome jank. Opacity snaps on scroll (no fade) so the GPU is not rebuilding
   blur while interpolating; the cheap ::after wash still eases in. */
.nav__blur {
  position: absolute;
  inset: 0 0 auto 0;
  height: 125%;
  pointer-events: none;
  opacity: 0;
  /* opacity:0 still samples backdrop-filter; hide the layers until scrolled */
  visibility: hidden;
  z-index: 0;
  contain: layout style paint;
}
.nav[data-scrolled] .nav__blur { opacity: 1; visibility: visible; }
/* phones: the bar stays over the full-size carousel, but not its blur band —
   that frosted the top of the photo */
@media (max-width: 899px) {
  html.is-show-full .nav[data-scrolled] .nav__blur { opacity: 0; visibility: hidden; }
}
/* While the Overheard photo show is at full bleed (script.js toggles the class
   on <html>), the bar slides up out of the picture and comes back as the
   frame shrinks to a card again. */
.nav { transition: transform .45s var(--ease); }
/* desktop only: on a phone the bar is just the logo and the menu disc, and
   they stay put through the carousel */
@media (min-width: 900px) { html.is-show-full .nav { transform: translateY(-110%); } }
@media (prefers-reduced-motion: reduce) { .nav { transition: none; } }

.nav__blur::after {
  content: "";
  position: absolute;
  inset: 0;
  background: linear-gradient(to bottom,
    color-mix(in srgb, var(--bg) 40%, transparent) 0%,
    color-mix(in srgb, var(--bg) 18%, transparent) 50%,
    color-mix(in srgb, var(--bg) 0%, transparent) 100%);
  opacity: 0;
  transition: opacity .35s var(--ease);
}
.nav[data-scrolled] .nav__blur::after { opacity: 1; }
.nav__blur span {
  position: absolute;
  inset: 0;
}
/* Extra empty spans in the markup are harmless no-ops. */
.nav__blur span:nth-child(1) {
  backdrop-filter: blur(4px);
  -webkit-backdrop-filter: blur(4px);
  -webkit-mask-image: linear-gradient(to top, transparent 0%, #000 18% 40%, transparent 62%);
  mask-image: linear-gradient(to top, transparent 0%, #000 18% 40%, transparent 62%);
}
.nav__blur span:nth-child(2) {
  backdrop-filter: blur(12px);
  -webkit-backdrop-filter: blur(12px);
  -webkit-mask-image: linear-gradient(to top, transparent 22%, #000 42% 64%, transparent 86%);
  mask-image: linear-gradient(to top, transparent 22%, #000 42% 64%, transparent 86%);
}
.nav__blur span:nth-child(3) {
  backdrop-filter: blur(24px);
  -webkit-backdrop-filter: blur(24px);
  -webkit-mask-image: linear-gradient(to top, transparent 48%, #000 70% 100%);
  mask-image: linear-gradient(to top, transparent 48%, #000 70% 100%);
}
.nav__blur span:nth-child(n + 4) { display: none; }

.nav__inner {
  position: relative;
  /* Above .menu (55): the dismiss layer is fixed inset:0 inside .nav, and
     at z-index 1 the brand + burger sat under it. The dropdown hangs below
     via --menu-top. */
  z-index: 60;
  max-width: var(--wrap);
  margin-inline: auto;
  /* Rest padding at every scroll position. Compact shrinks chrome, not the
     gap from the viewport top. */
  padding: var(--pad);
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: calc(var(--s-6) - 8px * var(--nav-compact, 0));
}

/* color re-declared, not inherited: body sets color: var(--fg), but a resolved
   inherited rgb would stick when the ground rewrites --fg on body — and the
   wordmark (fill: currentColor) would stay white on a pale scrolled ground. */
.nav__brand {
  font-size: var(--t-h2);
  line-height: 28px;
  display: inline-flex;
  align-items: center;
  color: var(--fg);
}
/* White Ramp sheet under the bar while paint() ground is still charcoal —
   force dark ink so the wordmark and burger stay readable. */
.nav[data-on-sheet] {
  --fg: #130F0C;
  --bg: #FFFFFF;
  /* the muted mixes resolve where they're declared (root, white ink), so the
     section links stayed white-on-white here; restate them in dark ink */
  --fg-60: color-mix(in srgb, #130F0C 62%, transparent);
  --fg-50: color-mix(in srgb, #130F0C 52%, transparent);
  --fg-35: color-mix(in srgb, #130F0C 35%, transparent);
  color: #130F0C;
}
.nav[data-on-sheet] .burger {
  color: #130F0C;
}
/* Under the water in the iceberg dive the page ground is still paper — the
   blue is the dive's own canvas — so paint()'s ink would stay black there.
   iceberg.js sets html.is-underwater once the camera is below the surface
   while the stage is behind the bar; the bar and the floating buttons take
   white ink until the Box Set blue arrives and paint() agrees. */
html.is-underwater .nav,
html.is-underwater .footdock {
  --fg: #FFFFFF;
  --bg: #0056D8;   /* the water: Donate's ink on its white pill, not paper on white */
  --fg-60: color-mix(in srgb, #FFFFFF 62%, transparent);
  --fg-50: color-mix(in srgb, #FFFFFF 52%, transparent);
  --fg-35: color-mix(in srgb, #FFFFFF 35%, transparent);
  color: #FFFFFF;
}
html.is-underwater .nav .burger { color: #FFFFFF; }


/* Brand mark — inlined as a sprite so it inherits currentColor */
.sprite { position: absolute; width: 0; height: 0; overflow: hidden; }
.logo {
  height: 36px;
  width: calc(36px * 351.4 / 92.3);
  fill: currentColor;
  display: block;
  /* the pill's outline runs to the very edge of the viewBox, and the compact
     bar parks the mark on a fractional pixel (32.4px tall) — an svg clips to
     its box by default, so the bottom edge's antialiasing was shaved off */
  overflow: visible;
}
/* Rest stays 36px. Compact uses Donate's rendered 40→36 scale (90%), so
   the mark goes 36→32.4 — same % as the CTA, not the old 22px tuck and not
   a lock to stepper/burger's 36px (which cancelled the morph). */
.nav__brand .logo {
  --logo-h: calc(36px * (1 - 0.1 * var(--nav-compact, 0)));
  height: var(--logo-h);
  width: calc(var(--logo-h) * 351.4 / 92.3);
}
.logo--footer {
  height: 36px;
  width: calc(36px * 351.4 / 92.3);
}
/* The nav mark stays still — the slow front roll it used to do is gone.
   The hover lift rides `scale`, not `transform`. */
.nav__brand .logo { transition: scale .3s var(--ease); view-transition-name: whisper-brand; }
.nav__brand:hover .logo { scale: 1.03; }
/* the hit area was exactly the wordmark's 137×36 box — a click a few pixels off
   the glyphs landed on the nav and did nothing. The padding grows the target,
   the negative margin keeps the mark where it was. Footer is separate: its
   negative margin pulled the mark under the contact sheet (z-index 2) and
   clipped the top of the wordmark. */
.nav__brand { padding: var(--s-2); margin: calc(var(--s-2) * -1); }

.footer__brand {
  display: inline-block;
  color: inherit;
  transition: opacity .25s var(--ease);
  /* Hit padding without escaping the footer — no negative margin. */
  padding: var(--s-2);
  margin: 0;
}
.footer__brand:hover { opacity: .65; }
.nav__cta {
  display: none;
  flex-shrink: 0;
  min-height: calc(40px - 8px * var(--nav-compact, 0));
  font-size: calc(var(--t-body) - 2px * var(--nav-compact, 0));
  line-height: calc(22px - 4px * var(--nav-compact, 0));
  padding-inline: calc(var(--s-6) - 8px * var(--nav-compact, 0));
  /* the 1px lift that centres the label at rest is too much in the 36px
     compact bar: it eases back to even as the bar compacts */
  padding-top: calc(var(--s-2) + 1px - 1px * var(--nav-compact, 0));
  padding-bottom: calc(var(--s-2) - 1px + 1px * var(--nav-compact, 0));
}
/* Desktop only — below 900 the sticky chip owns Donate. */
@media (min-width: 900px) { .nav__cta { display: inline-flex; } }

.nav__end {
  display: flex;
  align-items: center;
  gap: var(--s-3);
  flex-shrink: 0;
}
/* Donate page: the burger's disc becomes Back, on every width. */
.donatepage .nav__end { display: flex; }
.navback::after { content: none; }
/* the homepage row is as tall as its 46px section track; match it so the
   wordmark and disc land on the same pixels */
@media (min-width: 900px) { .donatepage .nav__end { min-height: 46px; } }
.navback__mark {
  width: 18px; height: 18px;
  fill: none; stroke: currentColor; stroke-width: 2;
  stroke-linecap: round; stroke-linejoin: round;
}

/* Mobile Donate: fixed bottom-left. Same soft liquid glass as .burger /
   .folder / .totop — not the solid header CTA — so the foot chrome reads as
   one family. */
.sticky-donate {
  display: none;
  position: fixed;
  left: var(--pad);
  bottom: var(--s-6);
  z-index: 70;
  /* Beat .btn--solid: frost over the page, ink tracks the ground. */
  background: color-mix(in srgb, var(--fg) 8%, transparent);
  color: var(--fg);
  border: 0;
  /* Slightly softer blur than 16px — still reads as glass, cheaper with the
     nav frost + burger also sampling the page. */
  backdrop-filter: blur(12px) saturate(1.3);
  -webkit-backdrop-filter: blur(12px) saturate(1.3);
  box-shadow: inset 0 0 0 1px color-mix(in srgb, var(--fg) 6%, transparent);
  overflow: hidden;
  transition:
    opacity .25s var(--ease),
    transform .25s var(--ease),
    background-color .25s var(--ease),
    box-shadow .25s var(--ease),
    color .25s var(--ease);
}
.sticky-donate:hover {
  opacity: 1;
  background: color-mix(in srgb, var(--fg) 10%, transparent);
  box-shadow: inset 0 0 0 1px color-mix(in srgb, var(--fg) 12%, transparent);
}
.sticky-donate:active {
  opacity: 1;
  transform: scale(0.96);
}
@media (max-width: 899px) {
  .sticky-donate { display: inline-flex; }
  /* hidden at the very top of the page; slides in once scrolling starts */
  html.donate-wait:not(.is-scrolled) .footdock .sticky-donate {
    opacity: 0; transform: translateY(12px); pointer-events: none;
  }
}
/* A glint across the Donate pill every few seconds — enough to catch the eye on
   a page the reader is scrolling past, not enough to become a loop they notice.
   Nearly the whole timeline is idle: the band only crosses in the last fifth,
   so it reads as intermittent rather than as a running animation. */
.nav__cta { position: relative; overflow: hidden; }
.nav__cta::after,
.sticky-donate::after {
  content: "";
  position: absolute;
  top: 0;
  bottom: 0;
  left: 0;
  width: 55%;
  /* Solid header CTA glints in --bg; the glass chip glints in --fg so the
     band reads on frost the same way it does on the opaque pill. A soft
     wash with a bright core, so it reads as a glisten, not a smudge. */
  background: linear-gradient(100deg,
    transparent 15%,
    color-mix(in srgb, var(--bg) 6%, transparent) 36%,
    rgba(255, 255, 255, .42) 50%,
    color-mix(in srgb, var(--bg) 6%, transparent) 64%,
    transparent 85%);
  /* Fully off-stage + invisible while idle. -160% of a 40%-wide band still
     peeked into the left rim for ~5s of every cycle, which read as a stuck
     shine. opacity:0 holds the rest pose; linear keeps the sweep even. */
  transform: translateX(-220%) skewX(-12deg);
  opacity: 0;
  pointer-events: none;
  animation: ctaGlint 9s cubic-bezier(.4, 0, .4, 1) 2s infinite;
}
.sticky-donate::after {
  background: linear-gradient(100deg,
    transparent 10%,
    color-mix(in srgb, var(--fg) 24%, transparent) 38%,
    color-mix(in srgb, var(--fg) 55%, transparent) 50%,
    color-mix(in srgb, var(--fg) 24%, transparent) 62%,
    transparent 90%);
}
@keyframes ctaGlint {
  0%, 72% {
    transform: translateX(-220%) skewX(-12deg);
    opacity: 0;
  }
  74% {
    opacity: 1;
  }
  100% {
    transform: translateX(360%) skewX(-12deg);
    opacity: 1;
  }
}
@media (prefers-reduced-motion: reduce) {
  .nav__cta::after,
  .sticky-donate::after { animation: none; opacity: 0; }
}

/* ── Phone menu ─────────────────────────────────────────────────────────
   Below 900px the section track will not fit. The burger opens a compact
   glass dropdown (right-aligned under the disc) — dark near-black frost,
   clean list, grouped with a hairline — not a full-screen sheet. */

.burger {
  /* carried across to Studio's and the Wiki's menu disc (see the view
     transition at the end of this file) */
  view-transition-name: menu-disc;
  /* flex-shrink:0 is load-bearing. The row is space-between with a ~137px
     wordmark; without a locked basis the disc was the first thing the flex
     algorithm ate, and on a phone it collapsed to a few empty pixels — which
     is why the menu looked like it had "disappeared" against the ground. */
  display: inline-grid;
  place-items: center;
  flex: 0 0 var(--nav-disc);
  width: var(--nav-disc);
  height: var(--nav-disc);
  min-width: var(--nav-disc);
  padding: 0;
  border: 0;
  border-radius: var(--r-pill);
  /* Same soft glass as .stepper — a 1.5px / 32% ring read as a thick outline
     on dark grounds and made the disc feel like a different chrome family. */
  background: color-mix(in srgb, var(--fg) 8%, transparent);
  backdrop-filter: blur(12px) saturate(1.3);
  -webkit-backdrop-filter: blur(12px) saturate(1.3);
  box-shadow: inset 0 0 0 1px color-mix(in srgb, var(--fg) 6%, transparent);
  color: var(--fg);
  cursor: pointer;
  position: relative;
  z-index: 2;
  /* Scale is in the same transition as the wash so a press does not fight the
     colour change. deck.gallery's disc dips a few percent on click — enough to
     feel pressed, not enough to look like a bounce. */
  transition: background-color .25s var(--ease), transform .18s var(--ease),
              box-shadow .25s var(--ease), color .25s var(--ease);
}
.burger:hover { background: color-mix(in srgb, var(--fg) 10%, transparent); }
.burger:active { transform: scale(0.92); }

/* On hover (mouse only), a small hint under the disc that the menu is
   also the way to the Wiki and Studio. Gone while the menu is open. */
@media (hover: hover) {
  .burger::after {
    content: "Wiki, Studio & more";
    position: absolute; top: calc(100% + 8px); right: 0;
    padding: 6px 10px; border-radius: 10px;
    background: #130F0C; color: #FFFFFF;
    box-shadow: inset 0 0 0 1px rgba(255, 255, 255, .12), 0 8px 20px -10px rgba(0, 0, 0, .5);
    font-size: 13px; line-height: 1.2; letter-spacing: 0; white-space: nowrap;
    pointer-events: none; opacity: 0; transform: translateY(-4px);
    transition: opacity .18s ease, transform .18s ease;
  }
  .burger:hover::after { opacity: 1; transform: none; transition-delay: .35s; }
  .burger[aria-expanded="true"]::after { opacity: 0; transition-delay: 0s; }
}
@media (prefers-reduced-motion: reduce) { .burger::after { transition: none; } }
@media (prefers-reduced-motion: reduce) {
  .burger { transition: background-color .25s var(--ease), color .25s var(--ease); }
  .burger:active { transform: none; }
}
/* Desktop keeps the disc too, beside Donate, with the same menu as on a
   phone — and the same as Studio's and the Wiki's. */

.burger__mark { display: grid; gap: 4px; width: 18px; }
.burger__mark i {
  display: block;
  /* 2px on a 4px gap keeps all three rules on whole pixels — at 1.5px a
     phone rounded them differently and the middle one looked thinner */
  height: 2px;
  border-radius: 2px;
  background: currentColor;
  /* Center origin so the open state forms a real × — left origin pivoted both
     bars into a chevron. Width is static (no transition) so uneven closed bars
     do not reintroduce the open-path layout stutter. */
  transform-origin: center;
  transition: transform .3s var(--ease), opacity .2s var(--ease);
}
/* the three rules are deliberately uneven, longest first */
.burger__mark i:nth-child(1) { width: 100%; }
.burger__mark i:nth-child(2) { width: 72%; }
.burger__mark i:nth-child(3) { width: 44%; }

/* open: the outer two cross into × and the middle one goes */
.burger[aria-expanded="true"] .burger__mark i:nth-child(1) {
  width: 100%;
  transform: translateY(6px) rotate(45deg);
}
.burger[aria-expanded="true"] .burger__mark i:nth-child(2) { opacity: 0; }
.burger[aria-expanded="true"] .burger__mark i:nth-child(3) {
  width: 100%;
  transform: translateY(-6px) rotate(-45deg);
}

/* Tap-catcher only — no opacity fade on this full-viewport layer (that was
   painting the whole screen every frame while the glass panel opened). */
.menu {
  position: fixed;
  inset: 0;
  z-index: 55;
  background: rgba(0, 0, 0, 0);
  pointer-events: none;
  /* the page dims a little behind the open menu */
  transition: background-color .3s ease;
}
.menu.is-open { pointer-events: auto; background: rgba(0, 0, 0, .35); }
.menu[hidden] { display: none; }

/* Compact panel under the button, right-aligned with the bar. --menu-top is
   kept current by script on scroll/resize (not measured in the open click).
   Colors locked to site charcoal so a mid-scroll ground cannot tint the glass.
   Open animates transform only; opacity snaps on so backdrop-filter is not
   rebuilt while fading. Blur is modest + fill more opaque — cheaper on iOS. */
.menu__sheet {
  position: absolute;
  top: var(--menu-top, 80px);
  right: var(--pad);
  width: min(280px, calc(100vw - var(--s-8)));
  display: grid;
  gap: 0;
  padding: var(--s-3);
  border-radius: 28px;
  color: #FFFFFF;
  --fg: #FFFFFF;
  --bg: #000000;
  --fg-60: color-mix(in srgb, #FFFFFF 62%, transparent);
  --fg-35: color-mix(in srgb, #FFFFFF 35%, transparent);
  --rule: color-mix(in srgb, #FFFFFF 14%, transparent);
  --rule-strong: color-mix(in srgb, #FFFFFF 28%, transparent);
  /* More opaque fill = less backdrop sampling while the panel opens. */
  background: color-mix(in srgb, #000000 94%, transparent);
  backdrop-filter: blur(12px) saturate(1.25);
  -webkit-backdrop-filter: blur(12px) saturate(1.25);
  box-shadow:
    inset 0 0 0 1px color-mix(in srgb, #FFFFFF 10%, transparent),
    inset 0 1px 0 0 color-mix(in srgb, #FFFFFF 14%, transparent),
    0 18px 40px -20px rgba(0, 0, 0, .5);
  /* Opens the way iOS 26 menus do: it grows out of the menu disc — a soft,
     blurred blob stretching down and left, landing sharp with a little
     spring — and folds back into the disc on close. The origin is the
     disc's centre, just above the panel's top-right corner. */
  transform-origin: calc(100% - 20px) -20px;
  transform: scale(.12, .06);
  border-radius: 60px;
  filter: blur(10px);
  opacity: 0;
  transition:
    transform .2s cubic-bezier(.4, 0, .7, .2),
    border-radius .2s, filter .2s, opacity .16s linear .04s;
  contain: layout style paint;
}
.menu.is-open .menu__sheet {
  transform: none;
  border-radius: 28px;
  filter: blur(0);
  opacity: 1;
  transition:
    transform .5s cubic-bezier(.32, 1.28, .42, 1),
    border-radius .5s cubic-bezier(.2, .8, .2, 1),
    filter .34s cubic-bezier(.2, .8, .2, 1), opacity .1s linear;
  will-change: transform, filter;
}
/* the items sharpen in just behind the glass */
.menu__sheet > * { opacity: 0; filter: blur(6px); transform: translateY(-4px); transition: opacity .12s, filter .12s, transform .12s; }
.menu.is-open .menu__sheet > * { opacity: 1; filter: none; transform: none; transition: opacity .3s .08s, filter .36s .08s, transform .4s cubic-bezier(.2, .8, .2, 1) .08s; }
@media (prefers-reduced-motion: reduce) {
  .menu__sheet, .menu.is-open .menu__sheet, .menu__sheet > *, .menu.is-open .menu__sheet > * { transition: opacity .15s linear; transform: none; filter: none; border-radius: 28px; }
}

.menu__group {
  display: grid;
  gap: 2px;
  padding: var(--s-2) 0;
}
.menu__group + .menu__group {
  border-top: 1px solid var(--rule);
}

/* FAQ / Contact / Shop / Donate as slightly rounded tiles. */
.menu__group--tiles {
  display: grid;
  grid-template-columns: 1fr 1fr;
  gap: var(--s-2);
  padding: var(--s-3) var(--s-2);
}
.menu__tile {
  display: grid;
  place-items: center;
  min-height: 52px;
  padding: var(--s-3) var(--s-2);
  border-radius: 12px;
  font-size: 15px;
  line-height: 1.2;
  letter-spacing: -0.01em;
  text-align: center;
  color: #FFFFFF;
  background: color-mix(in srgb, #FFFFFF 9%, transparent);
  box-shadow: inset 0 0 0 1px color-mix(in srgb, #FFFFFF 8%, transparent);
  transition: background-color .2s var(--ease), opacity .2s var(--ease);
}
.menu__tile:hover,
.menu__tile:focus-visible {
  background: color-mix(in srgb, #FFFFFF 16%, transparent);
  outline: none;
}
/* Site · Wiki · Studio: one segmented control, the product you're on lit white */
.menu__group--apps { grid-template-columns: repeat(3, 1fr); }
.menu__group.menu__group--apps { gap: 4px; margin: var(--s-1) var(--s-2) var(--s-3); padding: 4px; border-radius: var(--r-pill); background: color-mix(in srgb, #FFFFFF 9%, transparent); }
.menu__group + .menu__group.menu__group--apps { border-top: 0; }
.menu__group--apps + .menu__group { border-top: 0; padding-top: 0; }
.menu__group--apps .menu__tile { min-height: 44px; padding: 0; border-radius: var(--r-pill); background: transparent; box-shadow: none; }
.menu__group--apps .menu__tile:hover, .menu__group--apps .menu__tile:focus-visible { background: color-mix(in srgb, #FFFFFF 10%, transparent); }
.menu__tile[aria-current="page"] { background: #FFFFFF; color: #130F0C; box-shadow: none; }
/* Contact, Shop and Donate: three equal buttons, a symbol over each label */
.menu__group--links { grid-template-columns: repeat(3, 1fr); }
.menu__group--links .menu__tile { gap: 6px; min-height: 68px; font-size: 14px; }
.menu__ic { width: 20px; height: 20px; fill: none; stroke: currentColor; stroke-width: 1.6; stroke-linecap: round; stroke-linejoin: round; }
.menu__tile--donate {
  background: #FFFFFF;
  color: #130F0C;
  box-shadow: none;
}
.menu__tile--donate:hover,
.menu__tile--donate:focus-visible {
  background: #FFFFFF;
  opacity: .88;
}

/* Clean left-aligned list — hover/focus gets a soft light pill, idle is bare. */
.menu__link {
  display: block;
  padding: var(--s-3) var(--s-4);
  border-radius: var(--r-pill);
  font-size: 17px;
  line-height: 1.3;
  letter-spacing: -0.01em;
  color: #FFFFFF;
  background: transparent;
  transition: background-color .2s var(--ease);
}
.menu__link:hover,
.menu__link:focus-visible {
  background: color-mix(in srgb, #FFFFFF 10%, transparent);
  outline: none;
}

.menu__social {
  display: flex;
  align-items: center;
  gap: var(--s-1);
  padding: var(--s-3) var(--s-2) var(--s-2) var(--s-3);
  border-top: 1px solid var(--rule);
}
.menu__social a {
  display: grid;
  place-content: center;
  width: 40px;
  height: 40px;
  border-radius: var(--r-pill);
  color: #FFFFFF;
  transition: background-color .2s var(--ease);
}
.menu__social a:hover,
.menu__social a:focus-visible {
  background: color-mix(in srgb, #FFFFFF 10%, transparent);
  outline: none;
}
.menu__social svg { width: 20px; height: 20px; }

/* ── The menu's contents (same as Studio's and the Wiki's) ─────────────
   Site, Wiki and Studio set large — the one you're on white with a dot, the
   others grey — then Contact · Shop · Donate as one line, then the socials. */
.menu__words { display: grid; padding: var(--s-1) var(--s-2) 0 var(--s-3); }
.menu__word {
  display: flex;
  align-items: center;
  gap: 10px;
  padding: 2px 0;
  font-size: 34px;
  line-height: 1.12;
  letter-spacing: -0.04em;
  color: color-mix(in srgb, #FFFFFF 40%, transparent);
  transition: color .2s var(--ease);
}
.menu__word:hover, .menu__word:focus-visible { color: #FFFFFF; outline: none; }
.menu__word[aria-current="page"] { color: #FFFFFF; }
.menu__word[aria-current="page"]::after { content: ""; width: 8px; height: 8px; border-radius: 50%; background: #FFFFFF;
  /* centred on the lowercase letters, not the line box (which the face's
     tall ascent pushes up): a tenth of the type size down */
  transform: translateY(.1em); }
/* Get involved, in the menu: a quiet list under a hairline, between the three
   product words and the Shop / Donate line */
.menu__involve {
  display: grid;
  margin: var(--s-3) var(--s-2) 0 var(--s-3);
  padding-top: var(--s-3);
  border-top: 1px solid var(--rule);
}
.menu__label { font-size: 13px; line-height: 19.5px; color: var(--fg-60); margin-bottom: var(--s-1); }
.menu__involve a {
  padding-block: 7px;
  font-size: 17px; line-height: 1.25; letter-spacing: -0.01em;
  color: #FFFFFF;
  transition: color .2s var(--ease);
}
.menu__involve a:hover, .menu__involve a:focus-visible { color: var(--fg-60); outline: none; }
.menu__acts { display: flex; flex-wrap: wrap; gap: 6px 18px; padding: var(--s-4) var(--s-2) var(--s-3) var(--s-3); font-size: 15px; }
/* a hairline between the Get involved list and Shop / Donate, matching the
   one above the list */
.menu__involve + .menu__acts { margin: var(--s-3) var(--s-2) 0 var(--s-3); padding-left: 0; padding-right: 0; border-top: 1px solid var(--rule); }
.menu__acts a { color: var(--fg-60); transition: color .2s var(--ease); }
.menu__acts a:hover, .menu__acts a:focus-visible { color: #FFFFFF; outline: none; }
.menu__acts .menu__give { color: #FF8FA3; }

/* Phone: it unrolls down from just under the bar, right-aligned under the
   disc — the same as Studio's and the Wiki's menus. The disc stays in the
   bar above it as the ×. */
@media (max-width: 899px) {
  .menu__sheet {
    left: auto;
    right: var(--pad);
    width: min(300px, calc(100vw - 2 * var(--pad)));
    padding: var(--s-4) var(--s-3) 10px;   /* a little more air over "Site" */
    border-radius: 34px;
    filter: none;
    transform: translateY(-10px);
    clip-path: inset(0 0 100% 0 round 34px);
    /* closing mirrors opening: it starts moving the moment you tap */
    transition: transform .24s cubic-bezier(.2, .9, .25, 1), clip-path .24s cubic-bezier(.2, .9, .25, 1), opacity 0s linear .24s;
  }
  .menu.is-open .menu__sheet {
    border-radius: 34px;
    filter: none;
    transform: none;
    clip-path: inset(0 0 0 0 round 34px);
    transition: transform .55s cubic-bezier(.32, 1.25, .42, 1), clip-path .5s cubic-bezier(.2, .9, .25, 1), opacity .1s linear;
  }
  .menu__word { font-size: 40px; }
}
/* "Follow us" on the left, the icons on the right */
.menu__follow { margin-right: auto; font-size: 15px; color: var(--fg-60); }


.menu__social svg :not(.fill) { fill: none; stroke: currentColor; stroke-width: 1.6; }
.menu__social svg .fill { fill: currentColor; stroke: none; }

@media (prefers-reduced-motion: reduce) {
  .menu__sheet, .burger__mark i { transition: none; }
  .menu.is-open .menu__sheet { transition: none; will-change: auto; }
}

/* ── Section stepper ────────────────────────────────────────────────────── */

/* The five sections as one segmented control rather than five loose links.
   The ratio is the whole point: 3px of padding on the track against a marker
   the full height of what is left, so the pill reads as a position inside a
   control instead of a highlight floating in an empty bar. Option 01 on
   tests/nav.html; the version this replaced is archived there as 07. */
.stepper { display: none; }
@media (min-width: 900px) {
  .stepper {
    display: flex;
    align-items: center;
    gap: 2px;
    padding: calc(3px - var(--nav-compact, 0) * 1px);
    border-radius: var(--r-pill);
    /* layout only — shell chrome (fill / blur / lift) lives on ::before so
       the section links stay readable at the hero tip */
    background: transparent;
    box-shadow: none;
  }
  .stepper::before {
    content: "";
    position: absolute;
    inset: 0;
    z-index: 0;
    border-radius: inherit;
    pointer-events: none;
    background: color-mix(in srgb, var(--fg) 10%, transparent);
    box-shadow:
      inset 0 1px 0 0 color-mix(in srgb, var(--fg) 12%, transparent),
      0 0 6px 0 rgba(0, 0, 0, .05),
      0 6px 30px 0 rgba(0, 0, 0, .10);
    /* Static blur when scrolled — never fade backdrop-filter (that rebuilds
       the sample every frame of the opacity tween and fought the nav lift). */
    backdrop-filter: blur(12px) saturate(1.35);
    -webkit-backdrop-filter: blur(12px) saturate(1.35);
    opacity: 0;
    transform: translateY(3px);
    /* opacity + settle only — same curve/duration as .nav__inner lift */
    transition:
      opacity .55s cubic-bezier(0.22, 1, 0.36, 1),
      transform .55s cubic-bezier(0.22, 1, 0.36, 1);
  }
  .nav[data-scrolled] .stepper::before {
    opacity: 1;
    transform: translateY(0);
  }
}
.stepper__link {
  font-size: calc(var(--t-body) - 2px * var(--nav-compact, 0));
  line-height: calc(24px - 4px * var(--nav-compact, 0));
  color: var(--fg-50);
  padding: calc(var(--s-2) - 2px * var(--nav-compact, 0)) calc(var(--s-4) - 4px * var(--nav-compact, 0));
  border-radius: 999px;
  transition: color .25s var(--ease), background-color .25s var(--ease);
}
/* one line each, always: a squeezed bar stacked "How / we / work" */
.stepper__link, .stepper button { white-space: nowrap; flex: none; }
@media (min-width: 900px) and (max-width: 1099px) {
  .stepper__link { padding-inline: calc(var(--s-3) - 2px * var(--nav-compact, 0)); font-size: calc(var(--t-body) - 2px - 2px * var(--nav-compact, 0)); }
}
.stepper__link:hover { color: var(--fg); }
.stepper__link[aria-current="true"] { color: var(--fg); }

/* The marker is a single element that slides between the links, built by
   script and measured off whichever one is current. It sits on a lighter
   surface than the track it is in — the track is the recess, the marker is the
   key raised out of it: a lit top edge and one tight contact shadow.

   Width and height are animated alongside the position, so a move from a short
   label to a long one stretches into place instead of stepping. */
.stepper { position: relative; }
.stepper__pill {
  position: absolute;
  top: 0;
  left: 0;
  z-index: 0;
  border-radius: var(--r-pill);
  background: color-mix(in srgb, var(--fg) 12%, transparent);
  box-shadow:
    inset 0 1px 0 0 color-mix(in srgb, var(--fg) 14%, transparent),
    0 1px 2px 0 rgba(0, 0, 0, .18);
  opacity: 0;
  pointer-events: none;
  transition:
    transform .42s var(--ease-pop, cubic-bezier(.22, 1.2, .36, 1)),
    width .42s var(--ease-pop, cubic-bezier(.22, 1.2, .36, 1)),
    height .42s var(--ease),
    opacity .25s var(--ease);
}
/* the labels have to sit over it */
.stepper__link { position: relative; z-index: 1; }

@media (prefers-reduced-motion: reduce) {
  .stepper__pill { transition: opacity .25s var(--ease); }
}

/* ── Get involved: the nav's one dropdown ─────────────────────────────────
   Locked to the menu sheet's charcoal glass (.menu__sheet) whatever the
   ground: following the page colour left it a pale wash over pale sections,
   reading as part of the form behind it. It hangs off the track's right
   edge (.stepper is the positioned box; .navdrop deliberately is not, so the
   pill's offsetLeft maths still measure against the track). */
.navdrop { display: contents; }
.navdrop__trigger { display: inline-flex; align-items: center; gap: 6px; white-space: nowrap; }
.navdrop__caret {
  width: 6px; height: 6px; margin-top: -3px;
  border-right: 1.5px solid currentColor; border-bottom: 1.5px solid currentColor;
  transform: rotate(45deg);
  transition: transform .25s var(--ease), margin .25s var(--ease);
}
.navdrop.is-open .navdrop__trigger { color: var(--fg); }
.navdrop.is-open .navdrop__caret { transform: rotate(-135deg); margin-top: 3px; }
.navdrop__panel {
  position: absolute; top: calc(100% + 10px); right: 0; z-index: 2;
  width: 300px; padding: 6px;
  border-radius: 20px;
  --fg: #FFFFFF;
  --fg-50: color-mix(in srgb, #FFFFFF 55%, transparent);
  color: #FFFFFF;
  background: color-mix(in srgb, #000000 92%, transparent);
  backdrop-filter: blur(12px) saturate(1.25);
  -webkit-backdrop-filter: blur(12px) saturate(1.25);
  box-shadow:
    inset 0 0 0 1px color-mix(in srgb, #FFFFFF 10%, transparent),
    inset 0 1px 0 0 color-mix(in srgb, #FFFFFF 14%, transparent),
    0 18px 40px -16px rgba(0, 0, 0, .5);
  opacity: 0; visibility: hidden; transform: translateY(-6px);
  transition: opacity .18s var(--ease), transform .25s var(--ease), visibility 0s linear .25s;
}
/* bridges the gap under the trigger, so moving down onto the panel never
   counts as leaving it */
.navdrop__panel::before { content: ""; position: absolute; left: 0; right: 0; top: -12px; height: 12px; }
.navdrop.is-open .navdrop__panel {
  opacity: 1; visibility: visible; transform: none;
  transition: opacity .18s var(--ease), transform .25s var(--ease), visibility 0s;
}
.navdrop__panel a {
  display: block; padding: 10px 14px; border-radius: 14px;
  color: var(--fg);
  transition: background-color .2s var(--ease);
}
.navdrop__panel a:hover, .navdrop__panel a:focus-visible { background: color-mix(in srgb, var(--fg) 9%, transparent); }
.navdrop__panel b { display: block; font-weight: 400; font-size: 15px; line-height: 1.3; }
.navdrop__panel em { display: block; font-style: normal; font-size: 12.5px; line-height: 1.4; color: var(--fg-50); }
.navdrop__panel hr { border: 0; height: 1px; margin: 6px 14px; background: color-mix(in srgb, var(--fg) 12%, transparent); }
@media (prefers-reduced-motion: reduce) {
  .navdrop__panel, .navdrop__caret { transition: none; }
}

/* There is no bar behind the row any more — the segmented track is the only
   surface in the header, and the wordmark and Donate button sit straight on
   the page at either end of it. Three objects rather than one long pill, which
   is what the track's own shape earns once it is strong enough to hold on its
   own. The progressive blur band below still does the page-edge job.

   The track therefore has to carry what the bar used to: it takes the blur and
   the lift on scroll, when there is content passing underneath it. Links stay
   visible at the tip; only the ::before shell fades in once scrolled. */
@media (prefers-reduced-motion: reduce) {
  .stepper::before { transition: none; transform: none; }
}

/* Compact shrinks logo / Donate / stepper. Padding stays --pad so the bar
   does not sit closer to the top after scroll. */

/* the contents have to ride above the pill */
.nav__brand, .stepper, .nav__cta { position: relative; }

@media (prefers-reduced-motion: reduce) {
  .nav__inner::before { transition: none; transform: none; }
}

/* ── Sections ───────────────────────────────────────────────────────────── */

.section { padding-block: var(--s-20); border-top: 1px solid var(--rule); }
@media (min-width: 1024px) { .section { padding-block: var(--s-27); } }

.split { display: grid; gap: var(--s-8); align-items: start; }
@media (min-width: 900px) {
  .split { grid-template-columns: 1.05fr 0.95fr; gap: var(--s-14); }
}
.split__body p { color: var(--fg-60); max-width: 62ch; line-height: 1.6; }
.split__body .split__src {
  margin-top: var(--s-4);
  font-size: var(--t-micro);
  line-height: 1.4;
  color: var(--fg-35);
  max-width: 52ch;
}
.split__body p + p { margin-top: var(--s-5); }
/* the Problem's case in full white; its sources stay muted */
.problem .split__body p:not(.split__src) { color: #fff; }

/* ── Sharpie on the stall door ───────────────────────────────────────────
   Three things a student might write where nobody is meant to see them,
   filling the dead space under the loneliness heading. The panel is absolute
   from the bottom of the heading, so it costs the page no height at all, and
   the scrawls inside it are absolute too — the column keeps the size it had.

   There is no handwriting face in this project, so the marker look is built:
   set small, tilted per phrase, kept under full white the way ink on a painted
   surface never reads as bright as type, and roughened by the SVG filter. The
   clip-path is the pen tip — the phrase is fully laid out from the start and a
   window opens across it, so nothing reflows as it writes. */
.split__head { position: relative; }
.stall {
  position: absolute;
  top: 100%;
  left: 0;
  right: 0;
  height: 300px;
  pointer-events: none;
}
.scrawl {
  position: absolute;
  left: var(--x, 0);
  top: var(--y, 0);
  margin: 0;
  /* small: this is something muttered in a margin, never a headline */
  font-size: clamp(20px, 2.2vw, 30px);
  line-height: 1.15;
  letter-spacing: -.01em;
  color: color-mix(in srgb, var(--fg) 30%, transparent);
  /* nobody writing on a stall door reaches for the shift key, and lowercase
     keeps these quieter than the set type around them */
  text-transform: lowercase;
  transform: rotate(var(--rot, 0deg));
  transform-origin: 0 50%;
  white-space: nowrap;
  filter: url(#inkRough);
  clip-path: inset(0 100% 0 0);
  transition: clip-path 1s steps(24, end);
  transition-delay: var(--d, 0ms);
}
.stall.is-writing .scrawl { clip-path: inset(0 -2% 0 0); }

@media (max-width: 899px) {
  /* one column: there is no dead space beside the copy to write in */
  .stall { display: none; }
}
@media (prefers-reduced-motion: reduce) {
  /* the writing is the effect, so with motion off it is simply already written */
  .scrawl { clip-path: none; transition: none; }
}

/* ── Hero ───────────────────────────────────────────────────────────────── */

/* 20px off the top: the headline sits closer to the nav than to the lede */
.hero { padding-block: calc(var(--s-20) - 20px) var(--s-20); }
@media (min-width: 1024px) { .hero { padding-block: calc(var(--s-27) - 20px) var(--s-27); } }

.hero__inner { display: grid; gap: var(--s-14); align-items: center; min-width: 0; }
@media (min-width: 1024px) { .hero__inner { grid-template-columns: 1.1fr 0.9fr; } }

.hero__copy { min-width: 0; max-width: 100%; }
/* Hero h1 a hair larger than global .display; gaps below nudged a tad tighter. */
.hero__copy .display {
  font-size: clamp(44px, 11vw, 92px);
  margin-bottom: var(--s-6); /* was s-8 (32) → 24 */
  max-width: 14ch;
}
@media (min-width: 1024px) {
  /* 11vw is already at the 92px ceiling when the two-column kicks in, so the
     nowrap "alone → known" walks into the collage. Scale off viewport instead
     so the line stays in the copy track; 92px still lands around 1700. */
  .hero__copy .display {
    font-size: clamp(52px, 5.4vw, 92px);
  }
}
.hero__copy .lede { margin-bottom: var(--s-8); } /* was s-12 (48) → 32 */
.hero__actions { display: flex; flex-wrap: wrap; gap: var(--s-4); margin-bottom: var(--s-2); }
@media (max-width: 1023px) {
  /* Room between Get Started and the orange sticker — s-2 left them kissing. */
  .hero__actions { margin-bottom: var(--s-6); } /* was s-8 (32) → 24 */
}


/* max-width alone is not enough: the sticker's max-content track and its
   -1.5° tip both want to widen the column, so the slot itself clips */
.hero__stat {
  padding-top: var(--s-4); /* restored from s-2 (8) → 16; with actions mb = 24 desktop / 40 mobile */
  max-width: min(46ch, 100%);
  min-width: 0;
  overflow: hidden;
}

/* The stat as a bumper sticker: it brings its own ink and stock rather than
   reading --fg, because the whole idea is an object applied to the page — it
   should not change when the ground scrolls underneath it. Shares the CTAs' pill
   geometry so it adds an object without adding a new shape language. */
.statsticker {
  display: block;
  box-sizing: border-box;
  /* fixed width and a clip: the line has to have somewhere to run out of.
     max-width caps the pill even when a parent is sizing from content. */
  width: min(340px, 100%);
  max-width: 100%;
  overflow: hidden;
  padding: var(--s-3) var(--s-6);
  background: #FF4D00;
  color: #FFFFFF;
  border-radius: var(--r-pill);
  font-size: var(--t-caption);
  line-height: 19.5px;
  letter-spacing: .1em;
  text-transform: uppercase;
  /* applied by hand, so it never sits perfectly square — the tip is why the
     parent clips: without it, the rotated corners widen the document */
  transform: rotate(-1.5deg);
  transform-origin: left center;
  transition: transform .35s var(--ease);
  box-shadow: 0 6px 16px -8px rgba(0, 0, 0, .5);
}
@media (max-width: 600px) {
  /* the orange board was reading as a second hero on a phone — type, padding
     and width all step down so it supports the headline instead of matching it */
  .statsticker {
    width: min(260px, 100%);
    padding: var(--s-2) var(--s-4);
    font-size: 11px;
    line-height: 16px;
    letter-spacing: .08em;
    box-shadow: 0 4px 12px -8px rgba(0, 0, 0, .45);
  }
}
/* the number is the point, so it gets its tracking back */
.statsticker b { font-weight: 400; letter-spacing: .04em; }
.statsticker:hover { transform: rotate(0deg); }

/* two copies end to end, translated by exactly half the track, so the loop has
   no seam. It never stops — the sticker is a board, not a control. The track is
   allowed to be wider than the pill; the pill's overflow:hidden is the window. */
.statsticker__track {
  display: flex;
  width: max-content;
  max-width: none;
  animation: stickerRoll 13s linear infinite;
}
.statsticker__set { white-space: nowrap; padding-right: 3em; flex: none; }
@keyframes stickerRoll {
  to { transform: translateX(-50%); }
}

@media (prefers-reduced-motion: reduce) {
  .statsticker, .statsticker:hover { transition: none; }
  /* still clipped to the column — width:auto used to let the full sentence
     push the page wider than the phone */
  .statsticker { width: max-content; max-width: 100%; }
  .statsticker__track { animation: none; width: auto; }
  .statsticker__set + .statsticker__set { display: none; }
  .statsticker__set { padding-right: 0; white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }
}

/* ── Hero collage ────────────────────────────────────────────────────────
   The print collateral collage, rebuilt from its layers. The artboard is
   493 × 371; every piece is placed as a percentage of that, and anything that
   has to keep its proportion to the drawing (radii, borders, the notecard's
   script, the sticker's type) is sized in cqw against .collage. So the whole
   thing scales as one object without a single media query.

   Three motions, all small. At rest the pile breathes — each piece drifts on
   its own gentle float (individual translate/rotate so it composes with the
   hover-and-pointer transform below). Hovering loosens the pile along each
   piece's --dx/--dy/--dr. Under that, the pointer's position in the box moves
   each piece by its --p depth, so nearer stickers travel further than the
   photographs behind them. */
.collage {
  position: relative;
  width: 100%;
  aspect-ratio: 493 / 371;
  /* the drawing is bigger than the column it sits in and bleeds out of both
     sides of it — only once the hero is two columns and there is room to */
  max-width: none;
  container-type: inline-size;
  /* the parallax pair, written by script.js in the -1…1 range */
  --mx: 0;
  --my: 0;
  contain: layout style;
}

.cl {
  position: absolute;
  left: calc(var(--x) * 1%);
  top: calc(var(--y) * 1%);
  width: calc(var(--w) * 1%);
  height: calc(var(--h) * 1%);
  display: grid;
  place-items: center;
  /* the box is only a coordinate: the artwork inside takes the pointer */
  pointer-events: none;
  transform: translate3d(
    calc((var(--hx, 0) + var(--mx) * var(--p, .5) * 1.7) * 1cqw),
    calc((var(--hy, 0) + var(--my) * var(--p, .5) * 1.2) * 1cqw), 0);
  transition: transform .42s var(--ease-pop, cubic-bezier(.22, 1.28, .38, 1));
  /* the shallow pieces settle a beat after the ones in front */
  transition-delay: calc((1 - var(--p, .5)) * 55ms);
  /* Idle float defaults — nth-child overrides stagger duration/phase/path so
     the pile doesn't bob as one rigid unit. Amplitude scales with --p. */
  --idle-dur: 10s;
  --idle-delay: 0s;
  animation: clFloatA var(--idle-dur) ease-in-out var(--idle-delay) infinite alternate;
}
/* Three paths, opposite drifts and tips, so neighbouring pieces don't sync. */
@keyframes clFloatA {
  from { translate: 0 0; rotate: -.3deg; }
  to   { translate: calc(var(--p, .5) * .5cqw) calc(var(--p, .5) * -1.1cqw); rotate: .55deg; }
}
@keyframes clFloatB {
  from { translate: 0 0; rotate: .25deg; }
  to   { translate: calc(var(--p, .5) * -.55cqw) calc(var(--p, .5) * -.85cqw); rotate: -.5deg; }
}
@keyframes clFloatC {
  from { translate: 0 0; rotate: -.2deg; }
  to   { translate: calc(var(--p, .5) * .4cqw) calc(var(--p, .5) * .7cqw); rotate: .4deg; }
}
/* Per-piece stagger: unique period + negative delay (phase) + path variant. */
.cl:nth-child(1)  { --idle-dur: 11.2s; --idle-delay: -1.4s; animation-name: clFloatA; }
.cl:nth-child(2)  { --idle-dur: 8.6s;  --idle-delay: -4.8s; animation-name: clFloatB; }
.cl:nth-child(3)  { --idle-dur: 9.8s;  --idle-delay: -2.2s; animation-name: clFloatC; }
.cl:nth-child(4)  { --idle-dur: 10.5s; --idle-delay: -6.1s; animation-name: clFloatA; }
.cl:nth-child(5)  { --idle-dur: 7.9s;  --idle-delay: -3.5s; animation-name: clFloatB; }
.cl:nth-child(6)  { --idle-dur: 9.2s;  --idle-delay: -5.7s; animation-name: clFloatC; }
.cl:nth-child(7)  { --idle-dur: 10.8s; --idle-delay: -.9s;  animation-name: clFloatB; }
.cl:nth-child(8)  { --idle-dur: 8.3s;  --idle-delay: -7.2s; animation-name: clFloatA; }
.cl:nth-child(9)  { --idle-dur: 9.5s;  --idle-delay: -1.8s; animation-name: clFloatC; }
.cl:nth-child(10) { --idle-dur: 11s;   --idle-delay: -4.1s; animation-name: clFloatA; }
.cl:nth-child(11) { --idle-dur: 8.9s;  --idle-delay: -6.6s; animation-name: clFloatB; }
.cl:nth-child(12) { --idle-dur: 10.2s; --idle-delay: -2.9s; animation-name: clFloatC; }
.cl:nth-child(13) { --idle-dur: 9s;    --idle-delay: -5.3s; animation-name: clFloatA; }
/* script.js pauses idle CSS while the collage is off-screen */
.collage.is-idle-paused .cl { animation-play-state: paused; }
.cl:has(:hover) { z-index: 5; }

.collage:hover .cl { --hx: var(--dx); --hy: var(--dy); }

/* --r is the angle the piece was applied at, --hr the tip it takes on hover */
.cl__rot,
.cl__free {
  pointer-events: auto;
  transform: rotate(calc(var(--r, 0deg) + var(--hr, 0deg))) scale(var(--k, 1));
  transition: transform .42s var(--ease-pop, cubic-bezier(.22, 1.28, .38, 1));
  transition-delay: inherit;
}
.collage:hover .cl__rot,
.collage:hover .cl__free { --hr: calc(var(--dr, 0) * 1deg); }

.cl__rot { width: var(--iw, 100%); height: var(--ih, 100%); }
.cl__free { position: absolute; }

/* picking up a single piece lifts it clear of the pile */
.cl__rot:hover,
.cl__free:hover { --k: 1.06; }

/* Photographs: white hairline, 16px artboard radius, one soft drop */
.cl__card {
  overflow: hidden;
  border-radius: 3.25cqw;
  border: max(1px, .1cqw) solid #FFFFFF;
  box-shadow: 0 1.2cqw 2.4cqw -1.2cqw rgba(0, 0, 0, .55);
  background: #201A16;
}
.cl__card > img,
.cl__card > .cl__crop { width: 100%; height: 100%; }
.cl img { display: block; object-fit: cover; }
.cl__card > img { object-position: center; }

/* the layers that were cropped inside their frame keep the exact offsets they
   were given rather than being re-fitted */
.cl__crop { position: relative; overflow: hidden; }
.cl__crop > img { position: absolute; max-width: none; }
/* a cropped piece that is also placed by hand keeps its own coordinates:
   .cl__crop's positioning context must not demote it back into the grid */
.cl__free.cl__crop { position: absolute; }

/* the “Listen.” sticker: the two red shapes are its outline, and the word is
   set over them, so it stays type rather than becoming another flat image */
.cl__listen {
  font-family: "Aguafina Script", var(--font-quote);
  font-size: 5.5cqw;
  line-height: 4.64cqw;
  color: #B1BDFF;
  white-space: nowrap;
  transform: translate(-50%, -50%) rotate(calc(var(--r, 0deg) + var(--hr, 0deg))) scale(var(--k, 1));
}

/* Notecard — live type, so the script stays sharp at any size */
.cl__note {
  background: #FFE600;
  border-radius: .72cqw;
  display: grid;
  place-items: center;
  box-shadow: 0 .8cqw 1.6cqw -1cqw rgba(0, 0, 0, .5);
}
.cl__note p {
  font-family: "Imperial Script", var(--font-quote);
  font-size: 2.1cqw;
  line-height: 1.62cqw;
  color: #0015D2;
  text-align: center;
  /* the hand runs to the card's edges on the printed piece, but not past them */
  width: 92%;
}

/* Round sticker */
.cl__badge { border-radius: 50%; }
.cl__badge img { position: absolute; inset: 0; width: 100%; height: 100%; border-radius: 50%; }
.cl__word {
  position: absolute;
  pointer-events: none;
  font-size: .99cqw;
  line-height: 1;
  letter-spacing: -.05em;
  color: #000000;
  text-align: center;
  white-space: nowrap;
  transform: translateY(-50%) rotate(calc(var(--r, 0deg) + var(--hr, 0deg)));
  transition: transform .42s var(--ease-pop, cubic-bezier(.22, 1.28, .38, 1));
}
.collage:hover .cl__word { --hr: calc(var(--dr, 0) * 1deg); }

/* Frosted whisper. pill — solid-enough wash, no live backdrop sample in the
   already-animated collage (blur here fought the idle floats). */
.cl__pill {
  background: rgba(255, 255, 255, .28);
  border-radius: var(--r-pill);
  box-shadow: 0 .57cqw .86cqw rgba(0, 0, 0, .25);
  display: grid;
  place-items: center;
}
.cl__pill img { width: 80%; height: auto; }

/* Phone: ~15% larger cluster. Width + equal negative margins keep the
   artboard centred; html/body overflow-x:clip holds the bleed (same pattern
   as the desktop 144% widen — not a transform, so cqw sizing stays honest). */
@media (max-width: 1023px) {
  .hero__media .collage { width: 115%; margin-inline: -7.5%; }
}
@media (min-width: 1024px) {
  .hero__media .collage { width: 144%; margin-inline: -22%; }
}

@media (prefers-reduced-motion: reduce) {
  .cl, .cl__rot, .cl__free, .cl__word { transition: none; }
  .cl { animation: none; }
  .collage { --mx: 0; --my: 0; }
}

/* ── Hero orbit (promoted from tests/hero-layouts #v19, ink halo) ──────── */
.hero--orbit {
  /* Orbit stage height — one var so the lede pull-up tracks it. Was
     min(28.8vw, 368px) * 2 + 260px (~1000px tall); the rings are banked
     flatter below, so the stage no longer needs the room. */
  --hl19-h: calc(min(20vw, 256px) * 2 + 190px);
  padding-block: var(--s-4) var(--s-12);
  overflow: visible;
}
.hero--orbit.hl,
.hl.hero--orbit {
  overflow: visible;
}
/* ── Quiet hero ─────────────────────────────────────────────────────────
   The message on its own, centred on the dark ground, then one slow strip
   of gathering photos and stickers running edge to edge underneath. "alone"
   is drawn in outline at rest — the word itself looks empty — and both words
   keep their hearts on hover (.hl19-alone / .hl19-known below). */
.hero--quiet { padding-block: var(--s-14) var(--s-14); overflow: hidden; }
.qhero { display: grid; justify-items: center; gap: var(--s-6); text-align: center; }
/* a quiet grey lead, then the pair as the headline on one line of its own
   (tests/headers.html 26) */
.qhero__title { display: grid; justify-items: center; gap: .3em; font-weight: 400; color: #fff; }
.qhero__lead { font-size: clamp(18px, 2vw, 24px); line-height: 1.3; letter-spacing: -0.01em; color: var(--fg); }
.qhero__pair {
  white-space: nowrap;
  font-size: clamp(44px, 11vw, 140px);
  line-height: 1; letter-spacing: -0.05em;
}
.qhero__pair .arrow { width: .76em; height: .72em; margin-inline: .2em; vertical-align: 0; }
.qhero__pair .arrow path { stroke-width: 4.6; } /* box scales at .018em/unit; 4.6 ≈ .083em, the k's stem */
/* the orbit title turned .hl19-known into a block on phones to break the
   line there; this one stays a single line at every width */
.qhero .hl19-known { display: inline-block; }
/* phones: stacked (tests/headers.html 27) — "alone", an arrow turned to
   point down, "known", each on its own line and sized off the width so
   "known" runs nearly gutter to gutter. The arrow is drawn like a glyph of
   the face: about cap height, its stroke matching the stems (≈0.088em). */
@media (max-width: 599px) {
  .qhero__pair {
    display: grid; justify-items: center;
    white-space: normal;
    font-size: 25.5vw;   /* 10% under the 28.35vw before it (19% under gutter to gutter) */
    line-height: .9;
  }
  .qhero__pair .arrow { width: .76em; height: .72em; margin: .1em 0 .02em; transform: rotate(90deg); }
  .qhero__pair .arrow path { stroke-width: 4.8; stroke-linecap: butt; stroke-linejoin: miter; }
  .qhero .hl19-alone__stroke { -webkit-text-stroke-width: 1.4px; }
  /* a little less room over "alone", so Get Started and Give now land clear
     of the floating Donate on first load */
  .hero--quiet { padding-top: var(--s-12); }
  .qhero__title { gap: .15em; }
  .qhero__lead { margin-bottom: 8px; }   /* a touch more air above "alone" */
}
.qhero .lede { margin-inline: auto; color: rgba(255, 255, 255, .62); }
.qhero__title + .lede { margin-top: -6px; }   /* a touch closer to the headline than the 24px rhythm below */
/* the three closing words each lift a small photo while the pointer is on
   them (or a tap is held on touch). The picture is out of flow, so the
   sentence stays put, and it leaves with the pointer. */
.lede-peek {
  position: relative;
  color: inherit;
  cursor: pointer;
  text-decoration: none;
  border-bottom: 0;
  outline: none;
}
.lede-peek:hover,
.lede-peek:focus-visible,
.lede-peek.is-on { color: #fff; text-decoration: none; border-bottom: 0; }
.lede-peek__pic {
  position: absolute;
  z-index: 6;
  left: 50%;
  bottom: calc(100% + 10px);
  width: 132px;
  height: 88px;
  object-fit: cover;
  border-radius: 12px;
  box-shadow: 0 14px 32px rgba(0, 0, 0, .5);
  opacity: 0;
  visibility: hidden;
  pointer-events: none;
  transform: translateX(-50%) translateY(8px) rotate(var(--tilt, -4deg));
  transition: opacity .18s var(--ease), transform .18s var(--ease), visibility .18s;
}
.lede-peek:nth-of-type(2) { --tilt: 3deg; }
.lede-peek:nth-of-type(3) { --tilt: -2deg; }
@media (hover: hover) and (pointer: fine) {
  .lede-peek:hover .lede-peek__pic {
    opacity: 1;
    visibility: visible;
    transform: translateX(-50%) translateY(0) rotate(var(--tilt, -4deg));
  }
}
.lede-peek:focus-visible .lede-peek__pic,
.lede-peek.is-on .lede-peek__pic {
  opacity: 1;
  visibility: visible;
  transform: translateX(-50%) translateY(0) rotate(var(--tilt, -4deg));
}
@media (prefers-reduced-motion: reduce) {
  .lede-peek__pic { transition: none; }
}
.qhero__ctas { display: flex; flex-wrap: wrap; gap: 10px; justify-content: center; }
.qhero .hl19-alone__solid { color: transparent; -webkit-text-fill-color: transparent; }
.qhero .hl19-alone__stroke { opacity: 1; -webkit-text-stroke-width: max(1px, .014em); }

.qhero__strip {
  --h: clamp(120px, 15vw, 210px);
  /* room above and below the tiles for the stickers that ride over their
     edges, taken back out of the margins so the hero keeps its height */
  --st-pad: calc(var(--h) * .2);
  padding-block: var(--st-pad);
  margin-top: calc(var(--s-14) - var(--st-pad));
  margin-bottom: calc(-1 * var(--st-pad));
  overflow: hidden;
  -webkit-mask-image: linear-gradient(90deg, transparent, #000 6%, #000 94%, transparent);
  mask-image: linear-gradient(90deg, transparent, #000 6%, #000 94%, transparent);
}
.qhero__track {
  display: flex;
  align-items: center;
  gap: var(--s-6);
  width: max-content;
  animation: qheroSlide 130s linear infinite;
}
.qhero__strip:hover .qhero__track { animation-play-state: paused; }
.qhero__ph { height: var(--h); width: auto; flex: none; border-radius: 18px; }
/* silent loops cut from the Whisper YouTube channel (assets/video/loops,
   picked on tests/loops.html), 16:9 beside the photos */
.qhero__vid { aspect-ratio: 16 / 9; object-fit: cover; background: #111; }
/* Stickers aren't tiles: each sits in a zero-width holder whose negative
   margins cancel its own gap, so the photos and videos run as one even row
   and the sticker is stuck over the seam — high or low, tilted, nudged onto
   a corner, a different way for each of the eleven in a set. */
.qhero__stk {
  position: relative; z-index: 2;
  flex: none; width: 0; height: var(--h);
  margin-inline: calc(var(--s-6) / -2);
}
.qhero__st {
  position: absolute; left: 50%; top: var(--y, 50%);
  height: calc(var(--h) * .5); width: auto; max-width: none;
  transform: translate(-50%, -50%) translateX(var(--x, 0px)) rotate(var(--r, 0deg));
  filter: drop-shadow(0 8px 16px rgba(0, 0, 0, .45));
}
/* eleven a set (the six 2025 stickers with the original header's five
   between them), so the cycle is 11n: the second set lands exactly like
   the first and the loop has no seam */
.qhero__stk:nth-of-type(11n + 1) .qhero__st { --y: 12%; --x: calc(var(--h) * .18); --r: -11deg; }
.qhero__stk:nth-of-type(11n + 2) .qhero__st { --y: 82%; --x: calc(var(--h) * .1); --r: 7deg; height: calc(var(--h) * .62); }
.qhero__stk:nth-of-type(11n + 3) .qhero__st { --y: 86%; --x: calc(var(--h) * -.12); --r: 8deg; }
.qhero__stk:nth-of-type(11n + 4) .qhero__st { --y: 16%; --x: calc(var(--h) * -.08); --r: -8deg; height: calc(var(--h) * .56); }
.qhero__stk:nth-of-type(11n + 5) .qhero__st { --y: 20%; --x: calc(var(--h) * -.2); --r: 6deg; height: calc(var(--h) * .58); }
.qhero__stk:nth-of-type(11n + 6) .qhero__st { --y: 84%; --x: calc(var(--h) * .12); --r: -6deg; height: calc(var(--h) * .5); }
.qhero__stk:nth-of-type(11n + 7) .qhero__st { --y: 80%; --x: calc(var(--h) * .14); --r: -9deg; }
.qhero__stk:nth-of-type(11n + 8) .qhero__st { --y: 15%; --x: calc(var(--h) * -.1); --r: 9deg; height: calc(var(--h) * .42); }
.qhero__stk:nth-of-type(11n + 9) .qhero__st { --y: 14%; --r: 13deg; height: calc(var(--h) * .44); }
.qhero__stk:nth-of-type(11n + 10) .qhero__st { --y: 84%; --x: calc(var(--h) * .1); --r: -10deg; height: calc(var(--h) * .46); }
.qhero__stk:nth-of-type(11n) .qhero__st { --y: 84%; --x: calc(var(--h) * -.16); --r: -5deg; }
/* one set's width, not -50%: the gap after the last item is part of the loop */
@keyframes qheroSlide { to { transform: translateX(calc(-50% - var(--s-6) / 2)); } }
@media (prefers-reduced-motion: reduce) {
  .qhero__track { animation: none; }
  .qhero__strip { overflow-x: auto; }
}
/* script.js drives it once loaded: drift, drag and throw (Hero strip) */
.qhero__strip.is-live { overflow: hidden; cursor: grab; touch-action: pan-y; user-select: none; -webkit-user-select: none; }
.qhero__strip.is-live .qhero__track { animation: none; will-change: transform; }
.qhero__strip.is-dragging { cursor: grabbing; }

/* ── 19  Full collage orbit ────────────────────────────────────────────── */
.hl--19 {
  padding-block: var(--s-4) var(--s-20);
  overflow: visible;
}
/* Yaw/pitch live on custom props so the rings themselves stay untransformed.
   A transform on .hl19-ring would trap satellite z-index inside one stacking
   context and the title could never sit between front and back sats. */
@property --yaw {
  syntax: "<angle>";
  inherits: true;
  initial-value: 0deg;
}
@property --pitch {
  syntax: "<angle>";
  inherits: true;
  initial-value: 0deg;
}
@property --behind {
  syntax: "<number>";
  inherits: false;
  initial-value: 0;
}
.hl19-stage {
  position: relative;
  width: min(100%, 1440px);
  margin-inline: auto;
  height: var(--hl19-h);
  display: grid;
  place-items: center;
  perspective: 1800px;
  overflow: visible;
  transform-style: preserve-3d;
}
/* Grab the globe: pan-y stays until JS commits a drag, then none. */
.hl19-stage[data-orbit] {
  cursor: grab;
  touch-action: pan-y;
  user-select: none;
  -webkit-user-select: none;
}
.hl19-stage[data-orbit].is-orbit-dragging {
  cursor: grabbing;
  touch-action: none;
}
.hl19-sat img {
  -webkit-user-drag: none;
  user-select: none;
}
.hl19-ring {
  position: absolute;
  inset: 0;
  --yaw: 0deg;
  --pitch: 0deg;
  transform-style: preserve-3d;
  /* JS owns yaw/pitch (finger + fling). A CSS transition lagged the
     gesture and cancelled Ramp’s inertia. */
  pointer-events: none;
}
.hl19-sat {
  position: absolute;
  left: 50%;
  top: 50%;
  margin: 0;
  --behind: 0;
  --z: 21;
  --bank: 48deg;
  --ly: 0;
  --ly-scale: 0.7;
  --rise: 24px;
  z-index: var(--z);
  animation: hl19orbit 48s linear infinite;
  pointer-events: auto;
  /* Opacity always tracks depth so the fade is continuous. Filter is
     gated on .is-back so front sats keep mix-blend (holo) unflattened. */
  opacity: calc(1 - var(--behind) * 0.34);
}
.hl19-sat.is-back {
  /* Ramp-soft: 6px blur, ~36% darker — readable, not a smudge. */
  filter:
    blur(calc(var(--behind) * 6px))
    brightness(calc(1 - var(--behind) * 0.36));
}
/* Banks flattened (were 46/54/36) so the rings read as wide ellipses and the
   hero takes less height. */
.hl19-ring--mid .hl19-sat { animation-duration: 44s; --bank: 32deg; }
.hl19-ring--in .hl19-sat { animation-duration: 40s; --bank: 38deg; }
.hl19-ring--out .hl19-sat { --bank: 26deg; }
/* Off-screen: keep the current pose, stop the compositor spinning 18 layers. */
.hero--orbit.is-orbit-paused .hl19-sat { animation-play-state: paused; }
.hl19-sat--out { --rad: min(36vw, 500px); }
.hl19-sat--mid { --rad: min(28.5vw, 392px); }
.hl19-sat--in  { --rad: min(21.5vw, 292px); }

/* Original collage photos — framed prints, not stickers. */
.hl19-photo { width: min(118px, 10.5vw); }
.hl19-photo--wide { width: min(156px, 14vw); }
.hl19-photo img {
  width: 100%;
  aspect-ratio: 4 / 5;
  object-fit: cover;
  display: block;
  border-radius: 10px;
  border: 1px solid #fff;
  outline: 0;
  box-shadow: none;
}
.hl19-photo--wide img { aspect-ratio: 16 / 10; }
.hl19-photo--wide img[src*="photo-f"] {
  aspect-ratio: 16 / 9;
  object-position: 50% 42%;
}
/* 3D carousel around the headline (Ramp): a steeper rotateX bank so sats
   occupy the hero in X and Y — above, below, and to the sides — while
   still traveling in front of the type and behind it. --ly is a screen-Y
   offset (fraction of --rad) so rings are a volume, not one equator.
   Billboard un-rotates so art stays camera-facing. --yaw/--pitch from
   pointer are folded in here (not on the ring). */
@keyframes hl19orbit {
  from {
    transform:
      translate(-50%, -50%)
      translateY(calc(var(--rise, 0px) + var(--ly, 0) * var(--ly-scale, 1) * var(--rad)))
      rotateX(calc(var(--bank) + var(--pitch, 0deg)))
      rotateY(calc(var(--a) + var(--yaw, 0deg)))
      translateZ(var(--rad))
      rotateY(calc(-1 * (var(--a) + var(--yaw, 0deg))))
      rotateX(calc(-1 * (var(--bank) + var(--pitch, 0deg))))
      rotate(var(--tilt, 0deg));
  }
  to {
    transform:
      translate(-50%, -50%)
      translateY(calc(var(--rise, 0px) + var(--ly, 0) * var(--ly-scale, 1) * var(--rad)))
      rotateX(calc(var(--bank) + var(--pitch, 0deg)))
      rotateY(calc(var(--a) + var(--yaw, 0deg) + 360deg))
      translateZ(var(--rad))
      rotateY(calc(-1 * (var(--a) + var(--yaw, 0deg) + 360deg)))
      rotateX(calc(-1 * (var(--bank) + var(--pitch, 0deg))))
      rotate(var(--tilt, 0deg));
  }
}

/* Real sticker files — native aspect, alpha-shaped shadow, no CSS mockups. */
.hl19-sticker {
  border: 0;
  outline: 0;
  background: none;
  overflow: visible;
}
.hl19-sticker img {
  width: 100%;
  height: auto;
  display: block;
  object-fit: contain;
  border: 0;
  outline: 0;
  border-radius: 0;
  box-shadow: none;
  background: none;
  filter: drop-shadow(0 16px 20px rgba(0, 0, 0, .38));
}
.hl19-sticker--card img {
  filter: none;
}
.hl19-sticker--hand img {
  filter: drop-shadow(0 22px 28px rgba(0, 0, 0, .45));
}
.hl19-sticker--ygt        { width: min(108px, 10vw); }
.hl19-sticker--listen     { width: min(148px, 13.5vw); }
.hl19-sticker--note       { width: min(168px, 15.5vw); }
.hl19-sticker--how        { width: min(156px, 14.5vw); }
.hl19-sticker--bread      { width: min(112px, 10.5vw); }
.hl19-sticker--yellow     { width: min(148px, 13.5vw); }
.hl19-sticker--phone      { width: min(128px, 12vw); }
.hl19-sticker--holo       {
  width: min(118px, 11vw);
  --mx: 0.5;
  --my: 0.32;
  --px: 50%;
  --py: 32%;
}
/* Kiss-cut holographic foil sits behind opaque black ink — substrate
   rainbows; type does not. Pointer --mx/--my shift the spectral bands. */
.hl19-holo {
  position: relative;
  display: block;
  isolation: isolate;
  filter: drop-shadow(0 16px 20px rgba(0, 0, 0, .38));
}
.hl19-sticker--holo img {
  position: relative;
  z-index: 1;
  filter: none;
  mix-blend-mode: multiply;
}
.hl19-holo__disc {
  position: absolute;
  /* SVG circle is inset ~4px in a 130px viewBox */
  inset: 3.1%;
  z-index: 0;
  border-radius: 50%;
  pointer-events: none;
  background:
    radial-gradient(
      circle at var(--px) var(--py),
      rgba(255, 255, 255, 0.95) 0%,
      rgba(255, 255, 255, 0.18) 16%,
      transparent 40%
    ),
    repeating-linear-gradient(
      calc(118deg + (var(--mx) - 0.5) * 90deg),
      #ff7aa8 0 12%,
      #ffe08a 12% 24%,
      #7dffc6 24% 36%,
      #7ec8ff 36% 48%,
      #c9a5ff 48% 60%,
      #ff7aa8 60% 72%
    ),
    conic-gradient(
      from calc(var(--mx) * 360deg) at var(--px) var(--py),
      #f7f4ff,
      #e4f3fb,
      #eef8e8,
      #fff6dc,
      #fbe4f0,
      #f7f4ff
    );
  background-size: 100% 100%, 260% 260%, 170% 170%;
  background-position:
    0 0,
    calc(var(--mx) * 100%) calc(var(--my) * 100%),
    calc(var(--mx) * 70%) calc(var(--my) * 70%);
  background-blend-mode: overlay, overlay, normal;
  box-shadow: inset 0 0 0 0.6px rgba(0, 0, 0, .18);
}
@media (prefers-reduced-motion: reduce) {
  .hl19-holo__disc {
    background-position: 0 0, 50% 32%, 50% 32%;
  }
}
.hl19-sticker--substance  { width: min(156px, 14.5vw); }
.hl19-sticker--card       { width: min(86px, 8vw); }
.hl19-sticker--hand       { width: min(110px, 10vw); }

.hl19-core {
  position: relative;
  z-index: 21;
  transform: translateZ(0);
  display: grid;
  justify-items: center;
  justify-content: center;
  width: 100%;
  max-width: 100%;
  box-sizing: border-box;
  pointer-events: none;
  padding: 2.6rem 1.2rem 2.4rem;
  text-align: center;
}
.hl19-title {
  position: relative;
  z-index: 1;
  margin: 0;
  width: 100%;
  text-align: center;
  font-weight: 400;
  color: #fff;
  pointer-events: none;
}
.hl19-lead {
  display: block;
  width: 100%;
  white-space: nowrap;
  text-align: center;
  font-size: clamp(24px, 2.8vw, 42px);
  line-height: 1.15;
  letter-spacing: -0.02em;
  color: rgba(255, 255, 255, .88);
}
.hl19-lead-break { display: none; }
.hl19-pair {
  display: block;
  width: 100%;
  margin-top: .06em;
  margin-inline: auto;
  text-align: center;
  white-space: nowrap;
  font-size: clamp(112px, 13.2vw, 192px);
  line-height: .92;
  letter-spacing: -0.04em;
  color: #fff;
}
.hl19-pair .arrow {
  width: 0.76em;
  height: 0.72em;
  margin-inline: 0.04em 0.06em;
  vertical-align: 0;
  color: inherit;
}

/* “alone”: solid type at rest; outline + broken-heart overlay on hover.
   “known”: solid type at rest and hover; glossy heart overlay only (no letter fill).
   Art from Figma 15:6. */
.hl19-alone,
.hl19-known {
  position: relative;
  display: inline-block;
  pointer-events: auto;
  isolation: isolate;
}
/* html.is-cold (index.html, cleared by script.js just after the first
   paint) holds the hover hearts back: they only show on hover, and loading
   them up front sat in the way of the headline's first paint on phones */
html:not(.is-cold) .hl19-alone { --hl19-heart: url("assets/hero/alone-heart.webp"); }
html:not(.is-cold) .hl19-known { --hl19-heart: url("assets/hero/known-heart.webp"); }
.hl19-alone::before,
.hl19-known::before {
  content: "";
  position: absolute;
  left: 50%;
  top: 50%;
  width: 1em;
  height: 1em;
  background: var(--hl19-heart) center / contain no-repeat;
  /* -36% (not -50%): heart art is top-heavy, so sit it on the glyphs, not above caps. */
  transform: translate(-50%, -36%) scale(.84);
  transform-origin: center;
  opacity: 0;
  pointer-events: none;
  z-index: 3;
  filter: drop-shadow(0 12px 18px rgba(0, 0, 0, .48));
  transition:
    opacity .28s ease,
    transform .42s cubic-bezier(.22, 1.28, .38, 1);
}
.hl19-alone:hover::before,
.hl19-alone.is-lit::before,
.hl19-known:hover::before,
.hl19-known.is-lit::before {
  opacity: 1;
  transform: translate(-50%, -36%) scale(1);
}
.hl19-alone__solid {
  position: relative;
  z-index: 1;
}
/* the outline copy is drawn here, not typed in the page, so the headline's
   text reads "from alone to known" to search engines, not "alonealone" */
.hl19-alone__stroke::before { content: "alone"; }
.hl19-alone__stroke {
  position: absolute;
  left: 0;
  top: 0;
  z-index: 2;
  color: transparent;
  -webkit-text-fill-color: transparent;
  -webkit-text-stroke: 1px #fff;
  paint-order: stroke fill;
  text-shadow: none;
  pointer-events: none;
  opacity: 0;
}
/* Hollow broken-heart needs an outline so “alone” stays readable on hover only. */
.hl19-alone:hover .hl19-alone__solid,
.hl19-alone.is-lit .hl19-alone__solid {
  color: transparent;
  -webkit-text-fill-color: transparent;
  text-shadow: none;
}
.hl19-alone:hover .hl19-alone__stroke,
.hl19-alone.is-lit .hl19-alone__stroke {
  opacity: 1;
}

.hl19-known {
  z-index: 1;
}
.hl19-known__spray {
  position: absolute;
  left: 50%;
  top: 42%;
  width: 0;
  height: 0;
  overflow: visible;
  pointer-events: none;
  z-index: 4;
}
.hl19-below {
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: var(--s-6);
  text-align: center;
  max-width: 36rem;
  /* Stage is sized for the 3D orbit (sats at 50%), so ~half its height sits
     empty under the title. Pull the lede up: 50px - 0.4*stageH leaves ~32px
     title→lede (~30px tighter than the prior ~62px remainder). */
  margin: calc(50px - 0.4 * var(--hl19-h)) auto 0;
  padding-inline: var(--pad);
  padding-bottom: var(--s-14);
  position: relative;
  z-index: 40;
}
.hl19-below .lede { margin: 0; color: rgba(255, 255, 255, .62); }
.hl19-ctas {
  display: flex;
  gap: 10px;
  flex-wrap: wrap;
  justify-content: center;
}

@media (prefers-reduced-motion: reduce) {
  .hl19-ring { transition: none; }
  .hl19-sat {
    animation: none;
    transform:
      translate(-50%, -50%)
      translateY(calc(var(--rise, 0px) + var(--ly, 0) * var(--ly-scale, 1) * var(--rad)))
      rotateX(calc(var(--bank) + var(--pitch, 0deg)))
      rotateY(calc(var(--a) + var(--yaw, 0deg)))
      translateZ(var(--rad))
      rotateY(calc(-1 * (var(--a) + var(--yaw, 0deg))))
      rotateX(calc(-1 * (var(--bank) + var(--pitch, 0deg))))
      rotate(var(--tilt, 0deg));
  }
  .hl19-alone::before,
  .hl19-known::before {
    transition: none;
  }
}
@media (max-width: 1023px) {
  /* Mobile keeps the 3D orbit (hl19orbit + drag). Radii/tiles shrink so the
     ring stays inside a clipped stage; stacked “alone → / known” stays. */
  .hero--orbit {
    overflow-x: clip;
    overflow-y: visible;
    /* Was s-4 (16px). +40px pushes the whole stage (title + orbit) down for
       breathing room under the nav without growing the centered stage void. */
    padding-block: calc(var(--s-4) + 40px) var(--s-12);
  }
  .hl19-stage {
    /* Was clamp(640px, 168vw, 820px). Title is place-items:center, so ~half
       the stage sat empty between the nav and “Guiding students”. Exact half
       of those clamps cuts that dead air ~50%; orbit still clips via overflow. */
    height: clamp(320px, 84vw, 410px);
    overflow: hidden;
    width: 100%;
    max-width: 100%;
    perspective: 1200px;
  }
  .hl19-below {
    /* 44px base (was 24): the lede had been crowding up under "known" */
    margin-top: calc(44px - 0.28 * clamp(320px, 84vw, 410px));
  }
  .hl19-sat { --rise: 8px; --ly-scale: 0.72; }
  .hl19-sat--out { --rad: min(40vw, 152px); }
  .hl19-sat--mid { --rad: min(32vw, 122px); }
  .hl19-sat--in  { --rad: min(24vw, 92px); }
  .hl19-photo { width: 72px; }
  .hl19-photo--wide { width: 92px; }
  .hl19-sticker--ygt        { width: 58px; }
  .hl19-sticker--listen     { width: 74px; }
  .hl19-sticker--note       { width: 82px; }
  .hl19-sticker--how        { width: 76px; }
  .hl19-sticker--bread      { width: 60px; }
  .hl19-sticker--yellow     { width: 72px; }
  .hl19-sticker--phone      { width: 66px; }
  .hl19-sticker--holo       { width: 60px; }
  .hl19-sticker--substance  { width: 76px; }
  .hl19-sticker--card       { width: 44px; }
  .hl19-sticker--hand       { width: min(31px, 8.3vw); }
  .hl19-core {
    width: 100%;
    max-width: 100%;
    padding: 1.1rem var(--pad) 1.2rem;
    justify-items: center;
    justify-content: center;
    text-align: center;
    box-sizing: border-box;
  }
  .hl19-title { width: 100%; text-align: center; }
  .hl19-lead {
    width: 100%;
    max-width: min(100%, 9.6em);
    margin-inline: auto;
    text-align: center;
    white-space: normal;
    overflow-wrap: break-word;
    font-size: clamp(20px, 5.8vw, 28px);
    line-height: 1.2;
  }
  .hl19-lead-break { display: inline; }
  /* Stack like “Beyond / work”: first line “alone →”, second “known”.
     .nobr is nowrap sitewide — override so the pair can wrap. */
  .hl19-pair,
  .hl19-pair.nobr {
    display: block;
    width: 100%;
    max-width: 100%;
    margin-inline: auto;
    text-align: center;
    white-space: normal;
    font-size: clamp(56px, 18vw, 88px);
    letter-spacing: -0.045em;
    line-height: 0.9;
  }
  .hl19-known { display: block; }
}

/* Scatter styles retained inert (JS no longer enables .is-scatter on mobile). */
.hl19-stage.is-scatter {
  cursor: default;
  touch-action: pan-y;
  overflow: hidden;
  perspective: none;
}
.hl19-stage.is-scatter .hl19-sat {
  animation: none;
  left: 0;
  top: 0;
  margin: 0;
  cursor: grab;
  touch-action: none;
  transform:
    translate3d(var(--sx, 0px), var(--sy, 0px), 0)
    rotate(var(--tilt, 0deg))
    scale(var(--sc, 1));
  transform-origin: 50% 50%;
  will-change: transform;
  filter: none;
  opacity: 1;
  z-index: 28;
}
.hl19-stage.is-scatter .hl19-sat.is-back {
  filter: blur(8px) brightness(0.86);
  opacity: 0.72;
  z-index: 8;
}
.hl19-stage.is-scatter .hl19-core {
  z-index: 30;
  transform: none;
  pointer-events: none;
}
.hl19-stage.is-scatter .hl19-lead {
  position: relative;
  z-index: 31;
}
.hl19-stage.is-scatter .hl19-pair {
  position: relative;
  z-index: 31;
}
.hl19-stage.is-scatter .hl19-alone,
.hl19-stage.is-scatter .hl19-known {
  pointer-events: auto;
}
.hl19-stage.is-scatter .hl19-sat.is-scatter-drag {
  cursor: grabbing;
  z-index: 40;
}
@media (prefers-reduced-motion: reduce) {
  .hl19-stage.is-scatter .hl19-sat {
    will-change: auto;
  }
}

/* Image slots */
.photo {
  aspect-ratio: 4 / 5;
  border: 0;
  border-radius: var(--r-md);
  background: color-mix(in srgb, var(--fg) 7%, var(--bg));
  display: flex;
  align-items: flex-end;
  padding: var(--s-6);
}
.photo__label {
  font-size: var(--t-caption);
  line-height: 19.5px;
  color: var(--fg-50);
}

.slot {
  border: 0;
  border-radius: var(--r-md);
  background: color-mix(in srgb, var(--fg) 7%, var(--bg));
  display: grid;
  place-content: center;
  margin-top: var(--s-20);
}
.slot--cards { aspect-ratio: 21 / 9; margin-top: var(--s-12); }
.slot__label {
  font-size: var(--t-caption);
  line-height: 19.5px;
  color: var(--fg-50);
}

/* ── 3D iceberg (iceberg.js) ────────────────────────────────────────────
   Homepage runs the "dive" variant inside the solution section; the other
   variants live on tests/iceberg.html. --wl (waterline, px) is written from
   the 3D water plane each frame so the paper → ocean split tracks it. */
.berg3d {
  --wl: 38%;
  position: relative;
  height: max(640px, 100svh);
  overflow: hidden;
  background: linear-gradient(180deg,
    #ffffff 0,
    #EBEEF3 calc(var(--wl) - 1px),
    #87b3ff var(--wl),
    #0064F3 calc(var(--wl) + 9%),
    #002e8d calc(var(--wl) + 26%),
    #071450 calc(var(--wl) + 50%),
    #050d38 100%);
  touch-action: pan-y;
  user-select: none;
  -webkit-user-select: none;
}
/* dive: tall section, the stage sticks while the scroll drives the camera */
.b3v--dive { height: 320vh; }
/* the stage is as tall as the screen with Safari's bars tucked away (lvh):
   at svh, scrolling tucked the bars, the screen grew past the stage, and
   the paper ground showed as a pale strip under the water. With the bars
   out, the extra sits behind the bottom bar. */
.b3v--dive .berg3d { position: sticky; top: 0; height: 100vh; height: 100lvh; }
/* dive opens on the saturated blue it reaches mid-scroll, not the dim deep.
   Above the water the stage is clear, so the page ground shows through with
   no seam; below, it settles on #0056D8 — the Box Set blue the page ground
   crossfades to as the stage releases — so the bottom has no seam either. */
.b3v--dive .berg3d {
  background: linear-gradient(180deg,
    transparent 0,
    transparent calc(var(--wl) - 60px),
    rgba(135, 179, 255, .35) calc(var(--wl) - 1px),
    #87b3ff var(--wl),
    #0064F3 calc(var(--wl) + 8%),
    #0059e2 calc(var(--wl) + 60%),
    #0056D8 calc(var(--wl) + 100%));
}
.berg3d canvas {
  position: absolute;
  inset: 0;
  width: 100%;
  height: 100%;
  display: block;
  cursor: grab;
}
.berg3d.is-dragging canvas { cursor: grabbing; }

.berg3d__labels { position: absolute; inset: 0; pointer-events: none; }
.b3-label {
  position: absolute;
  left: 0;
  top: 0;
  /* hidden until iceberg.js places it: the labels are built a screen ahead
     but only positioned once the render loop starts (stage on screen), and
     before that every question sat stacked in this top-left corner */
  opacity: 0;
  will-change: transform, opacity;
  letter-spacing: -0.03em;
}

/* surface talk — the same bubbles as the live block */
.b3-bubble {
  width: max-content;
  margin: 0;
  padding: .42em .8em .48em;
  border-radius: 1.1em;
  font-size: clamp(18px, 2.2vw, 34px);
  line-height: 1.1;
  box-shadow: 0 14px 30px -18px rgba(0, 0, 0, .45);
  opacity: 0;
  transform: translateY(24px) scale(0.6);
  transition:
    opacity .4s var(--ease),
    transform .65s cubic-bezier(.22, 1.35, .36, 1);
}
.b3-bubble.is-in { opacity: 1; transform: rotate(var(--r, 0deg)); }
.b3-bubble--ask { transform-origin: 0 100%; }
.b3-bubble--reply { transform-origin: 100% 100%; }
.b3-bubble:not(.is-typed) .b3-bubble__text { display: none; }
.b3-bubble.is-typed .b3-typing { display: none; }
.b3-typing {
  display: inline-flex;
  gap: .18em;
  padding-block: .38em;
  vertical-align: middle;
}
.b3-typing i {
  width: .32em;
  height: .32em;
  border-radius: 50%;
  background: currentColor;
  opacity: .45;
  animation: b3Typing 1.1s ease-in-out infinite;
}
.b3-typing i:nth-child(2) { animation-delay: .15s; }
.b3-typing i:nth-child(3) { animation-delay: .3s; }
@keyframes b3Typing {
  0%, 60%, 100% { transform: none; opacity: .45; }
  30% { transform: translateY(-.14em); opacity: .9; }
}
@media (prefers-reduced-motion: reduce) {
  .b3-bubble { transition: none; }
}
/* Real Messages bubbles: system font, full rounding, the curled tail */
.b3-bubble {
  font-family: -apple-system, BlinkMacSystemFont, "SF Pro Text", "Helvetica Neue", Arial, sans-serif;
  letter-spacing: -0.02em;
  border-radius: .9em;
  padding: .36em .72em .42em;
}
.b3-bubble::before {
  content: "";
  position: absolute;
  bottom: 0;
  width: .85em;
  height: .85em;
  background: var(--tail);
  -webkit-mask: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 20 20'%3E%3Cpath d='M0 0H9C9 9 12 15 20 20C12.5 20.6 5.5 18.8 0 15.5Z'/%3E%3C/svg%3E") no-repeat 0 0 / 100% 100%;
          mask: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 20 20'%3E%3Cpath d='M0 0H9C9 9 12 15 20 20C12.5 20.6 5.5 18.8 0 15.5Z'/%3E%3C/svg%3E") no-repeat 0 0 / 100% 100%;
}
.b3-bubble--ask {
  --tail: #E9E9EB;
  background: #E9E9EB; color: #000;
}
.b3-bubble--ask::before { left: -.26em; transform: scaleX(-1); }
.b3-bubble--reply {
  --tail: #0A84FF;
  background: #0A84FF; color: #fff;
}
.b3-bubble--reply::before { right: -.26em; }
/* homepage dive: the surface talk in 8-bit, like the questions below it.
   The bubble itself is a canvas iceberg.js draws pixel by pixel ("Lit
   pixels", tests/bubbles8.html 03); the box is only the text's room. */
.berg3d[data-look="ps1"] .b3-bubble {
  position: relative; isolation: isolate;
  font-family: "Pixelify Sans", ui-monospace, monospace;
  font-size: clamp(20px, 2.1vw, 32px); line-height: 1.15; letter-spacing: 0;
  padding: 16px 24px 18px;
  border-radius: 0; background: none; box-shadow: none;
}
.berg3d[data-look="ps1"] .b3-bubble::before { display: none; }
.berg3d[data-look="ps1"] .b3-bubble.is-in { transform: rotate(var(--r, 0deg)); }
.b3-bubble__px { position: absolute; z-index: -1; image-rendering: pixelated; image-rendering: crisp-edges; pointer-events: none; }
.berg3d[data-look="ps1"] .b3-typing i { border-radius: 0; width: .5em; height: .5em; }

/* below the surface — shared base, then one look per variant */
.b3-q {
  width: max-content;
  max-width: 18ch;
  color: #fff;
  font-size: clamp(19px, 2.2vw, 34px);
  line-height: 1.12;
}
.b3-q--dive { text-align: center; transform-origin: 50% 50%; }
.b3-q--pinned::before {
  content: "";
  position: absolute;
  left: -24px;
  top: .45em;
  width: 7px;
  height: 7px;
  border-radius: 50%;
  background: #fff;
  box-shadow: 0 0 0 4px rgba(255, 255, 255, .22);
}
.b3-q--crystal {
  padding: .45em .9em .5em;
  border-radius: 999px;
  background: #fff;
  color: #071450;
  text-shadow: none;
  font-size: clamp(14px, 1.4vw, 20px);
  max-width: 22ch;
  box-shadow: 0 12px 26px -14px rgba(0, 0, 0, .6);
}
.b3-q--gauge {
  display: grid;
  gap: 4px;
  max-width: min(24ch, 30vw);
  padding-block: 6px;
}
.b3-q--gauge.is-left { padding-left: 14px; border-left: 2px solid rgba(255, 255, 255, .6); }
.b3-q--gauge.is-right { padding-right: 14px; border-right: 2px solid rgba(255, 255, 255, .6); text-align: right; }
.b3-q__depth {
  font-size: 12px;
  letter-spacing: .06em;
  color: #9cc4ff;
  text-shadow: none;
}
.b3-q--rise {
  display: grid;
  place-items: center;
  width: clamp(120px, 12vw, 170px);
  aspect-ratio: 1;
  padding: 1.1em;
  border-radius: 50%;
  border: 1.5px solid rgba(255, 255, 255, .55);
  background: radial-gradient(circle at 30% 28%, rgba(255, 255, 255, .35), rgba(255, 255, 255, .06) 60%);
  text-align: center;
  font-size: clamp(12px, 1.05vw, 16px);
  text-shadow: none;
}
.b3-q--ring {
  max-width: 18ch;
  padding: .7em .95em;
  border-radius: 16px;
  background: rgba(255, 255, 255, .16);
  border: 1px solid rgba(255, 255, 255, .35);
  backdrop-filter: blur(10px);
  -webkit-backdrop-filter: blur(10px);
  font-size: clamp(14px, 1.3vw, 19px);
  text-shadow: none;
}
/* dive: the five questions once each, centred blurbs floating in the lower
   water — three over two on wide screens, two columns on phones — each on
   its own slow drift and sway (iceberg.js builds them, fades the set in) */
.b3-floats { position: absolute; inset: 0; opacity: 0; pointer-events: none; }
.b3-float {
  position: absolute; left: var(--x); top: var(--y);
  width: 15ch; margin: 0; translate: -50% 0;
  color: #fff; text-align: center; text-wrap: balance; opacity: var(--o, 1);
  /* 8-bit: a pixel face with a hard block shadow, and a drift that only
     slides — a tilt or a scale would smear the pixels */
  font-family: "Pixelify Sans", ui-monospace, monospace;
  font-size: clamp(18px, 1.8vw, 27px); line-height: 1.2; letter-spacing: 0;
  text-shadow: .14em .14em 0 #000;
  -webkit-font-smoothing: none; font-smooth: never;
  animation: b3FloatPx var(--t, 14s) ease-in-out var(--dl, 0s) infinite alternate;
}
.b3-float:nth-child(1) { --x: 19%; --y: 36%; --t: 8s;  --dl: -2s; --o: 1;   --dx: 40px;  --dy: -32px; --r: -2.6deg; }
.b3-float:nth-child(2) { --x: 50%; --y: 58%; --t: 10s; --dl: -6s; --o: .9;  --dx: -34px; --dy: -40px; --r: 2.2deg; }
.b3-float:nth-child(3) { --x: 81%; --y: 33%; --t: 7.5s; --dl: -3s; --o: .95; --dx: -44px; --dy: -28px; --r: 3deg; }
.b3-float:nth-child(4) { --x: 33%; --y: 76%; --t: 9s;  --dl: -7s; --o: .85; --dx: 36px;  --dy: -36px; --r: 2.4deg; }
.b3-float:nth-child(5) { --x: 67%; --y: 79%; --t: 11s; --dl: -4s; --o: .9;  --dx: -38px; --dy: -30px; --r: -2.2deg; }
@media (max-width: 700px) {
  .b3-float { width: 13ch; font-size: 17px; }
  /* raised as a group: they sat low, the last one near the foot */
  /* a wide zigzag: same-side questions about a quarter of the stage apart,
     so even mid-drift they never touch */
  .b3-float:nth-child(1) { --x: 27%; --y: 30%; }
  .b3-float:nth-child(2) { --x: 73%; --y: 43%; }
  .b3-float:nth-child(3) { --x: 27%; --y: 56%; }
  .b3-float:nth-child(4) { --x: 73%; --y: 69%; }
  .b3-float:nth-child(5) { --x: 50%; --y: 82%; }
}
/* phones: narrower water, so a shorter sideways wander keeps neighbours apart */
@media (max-width: 700px) {
  .b3-float:nth-child(1) { --dx: 18px; --dy: -10px; }
  .b3-float:nth-child(2) { --dx: -16px; --dy: -12px; }
  .b3-float:nth-child(3) { --dx: 18px; --dy: -10px; }
  .b3-float:nth-child(4) { --dx: -16px; --dy: -12px; }
  .b3-float:nth-child(5) { --dx: 14px; --dy: -10px; }
}
/* a buoyant drift: a wider wander than before, a little tilt, and a slight
   swell, so the questions read as carried by the water, not pinned */
@keyframes b3Float {
  0%   { transform: translate(0, 0) rotate(0deg) scale(1); }
  30%  { transform: translate(calc(var(--dx) * .6), var(--dy)) rotate(var(--r)) scale(1.03); }
  65%  { transform: translate(var(--dx), calc(var(--dy) * .25)) rotate(calc(var(--r) * -.6)) scale(.98); }
  100% { transform: translate(calc(var(--dx) * .15), calc(var(--dy) * -.5)) rotate(calc(var(--r) * .5)) scale(1.02); }
}
@keyframes b3FloatPx {
  0%   { transform: translate(0, 0); }
  30%  { transform: translate(calc(var(--dx) * .6), var(--dy)); }
  65%  { transform: translate(var(--dx), calc(var(--dy) * .25)); }
  100% { transform: translate(calc(var(--dx) * .15), calc(var(--dy) * -.5)); }
}
@media (prefers-reduced-motion: reduce) { .b3-float { animation: none; } }
.b3-divelead {
  position: absolute;
  opacity: 0;   /* the frame loop fades it in; same reason as .b3-label */
  left: 50%;
  /* below the middle, so it lands in the water clear of the berg's tip */
  top: 60%;
  z-index: 30;
  margin: 0;
  white-space: nowrap;
  text-align: center;
  color: #fff;
  font-size: clamp(36px, 5.6vw, 96px);
  line-height: 1;
  letter-spacing: -0.05em;
  opacity: 0;
}
.b3-hint {
  position: absolute;
  left: 50%;
  bottom: 22px;
  translate: -50% 0;
  margin: 0;
  padding: 8px 14px;
  border-radius: 999px;
  background: rgba(255, 255, 255, .12);
  color: rgba(255, 255, 255, .8);
  font-size: 13px;
  backdrop-filter: blur(8px);
}
.b3-fallback {
  position: absolute; inset: 0;
  display: grid; place-items: center;
  color: #fff;
}

/* Homepage dive: full bleed out of the .wrap, starts after the solution lede */
.b3v--home {
  width: 100vw;
  margin: var(--s-20) 0 0 calc(50% - 50vw);
}
.b3v--home .b3-hint { display: none; }
/* the lead's mark: flat white, no 3D chrome */
.b3-divelead .b3-logo__flat { fill: #fff; }
.b3-divelead .b3-logo__gl { display: none; }
@media (max-width: 640px) {
  /* a few px tighter under the solution lede; desktop stays --s-20 */
  .b3v--home { margin-top: calc(var(--s-8) - var(--s-1)); }
  .b3-divelead { font-size: min(9vw, 44px); }
}
/* ocean mode: the sea is drawn by the shader, so what sits behind the
   part-transparent water is one flat blue — no gradient stops to show
   through as lines */
.b3v--dive .berg3d[data-water="ocean"] {
  background: linear-gradient(180deg,
    transparent 0,
    transparent var(--wl),
    #0056D8 calc(var(--wl) + 14%),   /* fades in under the far, hazy water */
    #0056D8 100%);   /* = the Box Set ground the page hands over to */
}
/* ocean mode paints its own sky: fade the canvas in at the top so the sky
   melts into the page ground instead of starting on a hard edge */
.berg3d[data-water="ocean"] canvas {
  -webkit-mask-image: linear-gradient(180deg, transparent 0, #000 22%);
  mask-image: linear-gradient(180deg, transparent 0, #000 22%);
}

/* ── Schools ────────────────────────────────────────────────────────────── */

.schools { padding-block: var(--s-12); border-top: 1px solid var(--rule); }
.schools__head { margin-bottom: var(--s-8); }
.schools__lead {
  font-size: clamp(20px, 2.4vw, 28px);
  line-height: 1.25;
  letter-spacing: -0.01em;
}
.schools__sub {
  font-size: clamp(20px, 2.4vw, 28px);
  line-height: 1.25;
  letter-spacing: -0.01em;
  color: var(--fg-50);
}

.marquee {
  overflow: hidden;
  padding-block: var(--s-5);
  mask-image: linear-gradient(90deg, transparent, #000 8%, #000 92%, transparent);
}
.marquee__track { display: flex; width: max-content; will-change: transform; }
.marquee__set { display: flex; align-items: center; }
.marquee__set li {
  font-size: var(--t-h2);
  line-height: 28px;
  color: var(--fg-60);
  padding-inline: var(--s-6);
  white-space: nowrap;
  display: flex;
  align-items: center;
  transition: color .25s var(--ease);
}
.marquee__set li:hover { color: var(--fg); }
.marquee__set li::after {
  content: "";
  width: var(--s-12);
}
@media (prefers-reduced-motion: reduce) {
  .marquee__track { flex-wrap: wrap; width: 100%; transform: none !important; }
  .marquee { mask-image: none; }
}

/* ── Problem ────────────────────────────────────────────────────────────── */

.stats {
  margin-top: var(--s-20);
  display: grid;
  gap: var(--s-12);
  border-top: 1px solid var(--rule);
  padding-top: var(--s-12);
}
/* Three across, not two and a panel — the Surgeon General's line is the third
   column. Everything breaks on 900 together: below it the row stacks, because
   two of these columns beside each other at tablet width leaves the labels
   setting three and four words to a line. */
@media (min-width: 900px) { .stats { grid-template-columns: repeat(3, 1fr); gap: var(--s-12); } }

/* Phone: the three facts as a snap rail rather than a tall stack. Full-bleed
   like .rail so the next tile peeks past the content edge and reads as
   swipeable; desktop keeps the three-column grid above. */
@media (max-width: 899px) {
  .stats {
    display: flex;
    gap: var(--s-5);
    width: 100vw;
    max-width: 100vw;
    margin-left: calc(50% - 50vw);
    padding-inline: var(--pad);
    padding-bottom: var(--s-2);
    /* Horizontal rail for the tiles. Do NOT set touch-action: pan-x or
       overscroll-behavior-y: none — those trapped vertical page scroll when
       the finger was over the carousel. overflow-y: hidden clips stray paint
       without creating a nested vertical scroller. */
    overflow-x: auto;
    overflow-y: hidden;
    overscroll-behavior-x: contain;
    scroll-snap-type: x mandatory;
    scroll-padding-inline: var(--pad);
    scrollbar-width: none;
    -webkit-overflow-scrolling: touch;
    /* full-bleed width means the existing top rule spans the phone, which
       reads cleaner than a hairline that stopped at the first tile */
  }
  .stats::-webkit-scrollbar { display: none; }
  .stats > .stat,
  .stats > .surgeon {
    flex: 0 0 min(300px, 78vw);
    scroll-snap-align: start;
    min-width: 0;
    /* Quiet unit chrome for the carousel — enough to read as a tile, not a
       heavy card. Desktop keeps the bare three-column facts. */
    padding: var(--s-6);
    border-radius: var(--r-md);
    background: color-mix(in srgb, var(--fg) 5%, transparent);
    box-shadow: inset 0 0 0 1px color-mix(in srgb, var(--fg) 8%, transparent);
  }
  /* give the third tile the same column rhythm as the numerals so a swipe
     does not jump the eye between two layouts */
  .stats > .surgeon { gap: var(--s-4); }
  .stats .stat__label,
  .stats .surgeon blockquote { max-width: none; }
  /* Tile numerals were ~40px tall with room to spare — step up hard on phones */
  .stats .stat__num {
    font-size: clamp(64px, 18vw, 84px);
    line-height: 0.9;
  }
  /* Carousel tile: 17px was caption-scale next to the tall numerals */
  .stats .surgeon blockquote {
    font-size: 21px;
    line-height: 1.3;
  }
}

.stat__num {
  /* 8vw filled a half-width column; in a third it collided with the label */
  font-size: clamp(44px, 6vw, 84px);
  line-height: 0.92;
  letter-spacing: -0.04em;
  font-variant-numeric: tabular-nums;
}
.stat__label {
  margin-top: var(--s-4);
  font-size: var(--t-h2);
  line-height: 28px;
  color: var(--fg-60);
  /* the measure that keeps all three columns wrapping to the same depth */
  max-width: 26ch;
}
/* attribution sits quiet under the claim — present, never competing */
.stat__src {
  margin-top: var(--s-3);
  font-size: var(--t-micro);
  line-height: 1.4;
  color: var(--fg-35);
}
/* 100 dots, the share filled — the count does the arguing */
.dots {
  /* positioned so each dot's offsetLeft/offsetTop is measured against the field
     itself — the proximity paint compares them with pointer coordinates that
     are relative to the field's own box */
  position: relative;
  display: grid;
  grid-template-columns: repeat(10, 1fr);
  /* a third of the original 260px field: a mark beside the numeral rather than
     a chart. The gap scales with it, or 6px of gutter on 6px dots reads as a
     grid of gaps. */
  gap: 2px;
  max-width: 87px;
  margin-bottom: var(--s-8);
}
.dots span {
  aspect-ratio: 1;
  border-radius: var(--r-pill);
  background: color-mix(in srgb, var(--fg) 14%, transparent);
  /* --dd staggers the fill sweep; it is set per dot in script.js. It rides its
     own property rather than transition-delay so the hover transform below
     isn't held back by it too. */
  transition: background-color .45s var(--ease) var(--dd, 0ms);
}

/* Proximity scale: the pointer's radius shrinks the fill it passes over,
   uncovering the empty socket underneath. Transform only, and will-change is
   carried just while the field is in use. */
.dots span.is-on {
  background: var(--fg);
  transform: scale(calc(1 - var(--t, 0)));
  transition: background-color .45s var(--ease) var(--dd, 0ms),
              transform .32s var(--ease);
}
.dots.is-live span { will-change: transform; }

/* The whole field arrives with the block as one object, then the share fills
   in sequence — colour only, so each dot lights in place rather than fading
   in from nothing on a timeline of its own. */
.reveal:not(.is-in) .dots span.is-on {
  background: color-mix(in srgb, var(--fg) 14%, transparent);
}

/* No surface of its own any more. Standing in the row it only has to keep the
   same rhythm as the two figures beside it — marks where their numeral sits,
   sentence where their label sits, source where their source sits — and a tinted
   card would have made one of three equal facts look like an aside. */
.surgeon {
  border: 0;
  display: grid;
  gap: var(--s-3);
  align-content: start;
  justify-items: start;
}

.surgeon__viz { display: flex; gap: 3px; align-items: flex-end; }
.surgeon__viz span {
  /* Same tally as tests/surgeon.html option 09 (.tally--sm): fifteen uniform
     marks, solid ink — the ash tip was a leftover from the full-size panel and
     read as a second colour against the dark ground. */
  width: 2px; height: 18px;
  border-radius: 1px;
  background: var(--fg);
  opacity: .9;
  transform-origin: bottom;
  transform: scaleY(0);
  transition: transform 420ms var(--ease);
}
.reveal.is-in .surgeon__viz span { transform: scaleY(1); }
.surgeon__viz span:nth-child(1)  { transition-delay: 40ms; }
.surgeon__viz span:nth-child(2)  { transition-delay: 80ms; }
.surgeon__viz span:nth-child(3)  { transition-delay: 120ms; }
.surgeon__viz span:nth-child(4)  { transition-delay: 160ms; }
.surgeon__viz span:nth-child(5)  { transition-delay: 200ms; }
.surgeon__viz span:nth-child(6)  { transition-delay: 240ms; }
.surgeon__viz span:nth-child(7)  { transition-delay: 280ms; }
.surgeon__viz span:nth-child(8)  { transition-delay: 320ms; }
.surgeon__viz span:nth-child(9)  { transition-delay: 360ms; }
.surgeon__viz span:nth-child(10) { transition-delay: 400ms; }
.surgeon__viz span:nth-child(11) { transition-delay: 440ms; }
.surgeon__viz span:nth-child(12) { transition-delay: 480ms; }
.surgeon__viz span:nth-child(13) { transition-delay: 520ms; }
.surgeon__viz span:nth-child(14) { transition-delay: 560ms; }
.surgeon__viz span:nth-child(15) { transition-delay: 600ms; }

.surgeon blockquote {
  /* A step above the old 17px so the third tile's claim matches the weight of
     the numerals beside it, without climbing toward display size (32px ran
     three columns deep next to two-line labels). */
  font-size: 19px;
  line-height: 1.35;
  letter-spacing: -0.01em;
  color: var(--fg);
  max-width: 26ch;
}

/* no measure of its own — the two sources beside it run the full column, and
   capping this one is what broke a single citation across three lines */
.surgeon__src {
  font-size: var(--t-micro);
  line-height: 1.4;
  color: var(--fg-35);
}

/* ── Solution ───────────────────────────────────────────────────────────── */

/* the deck rail carries its own bottom padding, so the section needs less */
.solution { padding-bottom: var(--s-8); }

/* Solution no longer paints its own ramp — the blue sequence is carried by
   data-ground stops like every other section, so the whole viewport is one
   flat colour that crossfades as you scroll. */
.solution { position: relative; }

/* Kept as optional pins for blocks that still need a known ink pair while the
   ground is mid-crossfade (e.g. the solid CTA on .deckpitch). Prefer letting
   paint() derive --fg when the surrounding ground is already settled. */
.on-blue {
  --fg: #FFFFFF;
  --bg: #0056D8;
  color: var(--fg);
}
.on-paper {
  --fg: #130F0C;
  --bg: #EBEEF3;
  color: var(--fg);
}

.solution__body { padding-block: var(--s-20); display: grid; gap: var(--s-4); justify-items: center; }
/* centred, so the measure comes in — 68ch of centred text loses its left edge */
/* muted like the body copy in the Box Set pitch below (.split__body p) */
.solution__body p { color: var(--fg-60); line-height: 1.6; max-width: 58ch; text-align: center; }
/* The closing line as a statement, a size up from the section heads (.h1),
   its words lit one by one as it scrolls through: dim at --fg-35 until
   script.js marks them. */
.solution__body p.solution__statement {
  margin-top: var(--s-12);
  max-width: 20ch;
  font-size: clamp(36px, 5.6vw, 80px);
  line-height: 1.05;
  letter-spacing: -0.05em;
  text-wrap: balance;
  color: var(--fg);
}
/* desktop: more air between the Box Set copy and the statement */
@media (min-width: 900px) {
  .solution__body p.solution__statement { margin-top: calc(var(--s-20) * 2); margin-bottom: var(--s-20); }
}
/* phones: 48px left it pressed against the Box Set copy above */
@media (max-width: 899px) {
  .solution__body p.solution__statement { margin-top: calc(var(--s-20) + var(--s-4)); }
}
/* US map under the statement (usmap.js), on its own light grey ground: the
   outline fades in once loaded, then black marks pop up from Atlanta
   outwards as it scrolls */
.usmap { width: min(100%, 980px); margin-block: var(--s-12) var(--s-20); aspect-ratio: 975 / 610; }
@media (max-width: 899px) { .usmap { margin-bottom: var(--s-8); } }   /* phones: less air under it */
.usmap svg { display: block; width: 100%; height: 100%; overflow: visible; }
.usmap__land { opacity: 0; transition: opacity .8s var(--ease); }
.usmap.is-ready .usmap__land { opacity: 1; }
.usmap__nation { fill: rgba(19, 15, 12, .04); stroke: rgba(19, 15, 12, .3); stroke-width: 1; stroke-linejoin: round; }
/* one line weight for the coast/border and the state lines */
.usmap__states { fill: none; stroke: rgba(19, 15, 12, .3); stroke-width: 1; stroke-linejoin: round; }
.usmap__mark use { transform: scale(0) rotate(var(--tilt, 0deg)); opacity: 0; transition: transform .45s cubic-bezier(.3, 1.6, .5, 1), opacity .2s; }
.usmap__mark.is-on use { transform: scale(1) rotate(0deg); opacity: 1; }
.usmap__mark use { color: #130F0C; }
/* under the pointer a mark turns into the 8-bit heart: the W. shrinks away
   and the heart pops in a size up (only once the mark has landed) */
.usmap__mark { cursor: default; }
.usmap__mark.is-on .usmap__h { transform: scale(.3); opacity: 0; transition: transform .22s steps(3), opacity .12s; }
.usmap__mark.is-on.is-hot .usmap__w { transform: scale(.5); opacity: 0; transition: transform .2s, opacity .12s; }
.usmap__mark.is-on.is-hot .usmap__h { transform: scale(1.35); opacity: 1; }
@media (prefers-reduced-motion: reduce) { .usmap__mark use { transition: none; } }
.sw__break { display: block; height: .45em; }
.sw__word { color: var(--fg-35); transition: color .35s var(--ease); }
.sw__word.is-lit { color: var(--fg); }
@media (prefers-reduced-motion: reduce) { .sw__word { color: inherit; transition: none; } }

/* The box's own title, sitting directly above the 3D stage. It is a 16ch block
   inside .wrap, which is a plain block container — so justify-self has nothing
   to act on and the centring has to come from auto margins. With only
   justify-self the heading rendered hard left of centre in Safari; Chrome
   happened to look right, which is what made this hard to see. */
.boxstage__head {
  margin-top: var(--s-20);
  max-width: 16ch;
  margin-inline: auto;
  justify-self: center;
  text-align: center;
}
@media (max-width: 700px) {
  /* Two empty blue bands (above the box, and between box and body) cut by ~1/3 */
  /* Match the dive's top margin so the blue bands read equal. */
  .boxstage__head { margin-top: var(--s-20); }
  /* The dive's last ~2/3 screen on phones was empty blue: the berg holds near
     the top while the sticky stage runs out. Pull the Box Set up over that
     stretch, layered above the still-stuck stage, so the headline lands a
     short breath under the berg's tip. */
  /* only 22%: at the end of the dive the berg reaches ~79% down the screen,
     and the headline has to land under it, never across it */
  /* no pull-up any more: overlapping the stuck stage let the rising bubbles
     run behind the headline. The title now starts where the dive ends. */
  .b3v--home { margin-bottom: 0; }
  .boxstage__head,
  .b3v--home ~ .boxstage { position: relative; z-index: 1; }
  /* Gap under the Box Set headline — the stage's own top margin was a full
     s-20 of empty blue before the lid; cut by ~1/3 to match the head hold. */
  /* Half the empty blue under Box Set (was 2/3 of s-20). */
  .boxstage { margin-top: calc(var(--s-20) * 1 / 2); }
  .solution__body { padding-block: calc(var(--s-20) * 2 / 3); }
  .problem.section { padding-block: var(--s-14); }
}

/* trademark mark, matched to the one printed on the box lid */
.tm {
  font-size: 0.3em;
  vertical-align: 0.9em;
  letter-spacing: 0;
  /* the last 5px by hand: vertical-align is in the mark's own em, which is
     small enough that nudging it there moves in jumps rather than pixels */
  position: relative;
  top: -5px;
}
.solution__head { text-align: center; margin-bottom: var(--s-4); }
/* Centered over the orbit; margin-inline centers the .h2-lg max-width block. */
.deckrail__head {
  text-align: center;
  margin-inline: auto;
  margin-bottom: var(--s-4);
  /* ~30%+ air under solution body before Explore topics (was flush via body pad only). */
  margin-top: calc(var(--s-14) + var(--s-6));
}
@media (max-width: 700px) {
  .deckrail__head { margin-top: calc(var(--s-20) + var(--s-4)); }
}
/* No frozen white pin — that left "Get the Whisper…" white-on-pale and the
   solid CTA looking like a ghost when how's white arrived early. Headline,
   body, and .btn--solid all follow paint()'s --fg/--bg together. */
.deckpitch {
  margin-top: var(--s-20);
}
/* phones: Get the deck sat 32px above How we work and its ? and ! */
@media (max-width: 899px) { .deckpitch { margin-bottom: var(--s-20); } }

/* ── How we work ────────────────────────────────────────────────────────── */

.how .h1 { margin-bottom: var(--gap-title-block); max-width: 18ch; }
/* chrome hand: a square stage, centred, the canvas fills it */
.handstage {
  width: min(289px, 59.5vw);   /* 15% under the old 340px / 70vw */
  aspect-ratio: 1;
  margin: 0 auto var(--s-6);
  cursor: grab;
  touch-action: pan-y;
  opacity: 0;
  transition: opacity .6s var(--ease);
}
.handstage.is-on { opacity: 1; }
/* the chrome hand and the glass hand, side by side */
.handpair { display: flex; justify-content: center; align-items: center; gap: clamp(0px, 2vw, 32px); margin-bottom: var(--s-6); }
.handpair .handstage { margin: 0; width: min(289px, 44vw); }
.handstage { position: relative; }
/* hearts thrown out of the hand on a click (script.js) */
.handheart {
  position: absolute; width: 26px; height: 24px; margin: -12px 0 0 -13px;
  pointer-events: none; z-index: 2; opacity: 0;
  animation: handHeart 1.2s cubic-bezier(.2, .7, .3, 1) forwards;
}
.handheart svg { display: block; width: 100%; height: 100%; filter: drop-shadow(0 4px 6px rgba(0, 0, 0, .22)); }
/* the 3D chrome heart: a strip of frames turning a full circle, played
   while it flies (the fly is on transform, the turn on background) */
.handheart--3d {
  width: 34px; height: 34px; margin: -17px 0 0 -17px;
  background-repeat: no-repeat; background-size: calc(var(--frames) * 100%) 100%;
  filter: drop-shadow(0 5px 8px rgba(0, 0, 0, .22));
  animation: handHeart 1.2s cubic-bezier(.2, .7, .3, 1) forwards, heartTurn var(--spin, 1s) steps(calc(var(--frames) - 1)) infinite;
}
@keyframes heartTurn { from { background-position: 0 0; } to { background-position: 100% 0; } }
@keyframes handHeart {
  0%   { opacity: 0; transform: translate(0, 0) scale(.2) rotate(0deg); }
  12%  { opacity: 1; }
  70%  { opacity: 1; }
  100% { opacity: 0; transform: translate(var(--dx), var(--dy)) scale(var(--s)) rotate(var(--rot)); }
}
.handstage.is-dragging { cursor: grabbing; }
.handstage__canvas { display: block; width: 100%; height: 100%; }
/* same big centred title as Partner with Whisper (.cine__title) */
.how__title { text-align: center; margin-bottom: var(--gap-title-block); }
@media (max-width: 899px) { .how__title { margin-bottom: calc(var(--gap-title-block) + var(--s-6)); } }   /* phones: a little more air under it */
/* phones: no section hairline over the chrome hand — it sat as a stray rule
   just above the hand once the section above ran out (desktop keeps it) */
/* a touch less room over the chrome hand than a section's usual top */
@media (max-width: 899px) { .section.how { border-top: 0; padding-top: var(--s-6); } }
@media (min-width: 900px) { .section.how { padding-top: calc(var(--s-20) - var(--s-4)); } }
@media (min-width: 1024px) { .section.how { padding-top: calc(var(--s-27) - var(--s-4)); } }

/* Full-bleed bands: each step is a row with the numeral in the
   margin, so the section reads as three chapters rather than three cards. The
   numeral is set large and at full strength — it is the chapter number, and at
   this size it carries the sequence on its own rather than waiting for a
   hover to earn its contrast. Hairlines span the full row, above the first
   and below the last, so the three chapters read as a ruled list. */
.bands { list-style: none; margin: 0; padding: 0; display: grid; }
.band { border-top: 1px solid var(--rule); }
.band:last-child { border-bottom: 1px solid var(--rule); }
.band__inner {
  display: grid;
  gap: var(--s-2) var(--s-8);
  padding-block: var(--s-14);
  align-items: start;
}
@media (min-width: 768px) { .band__inner { grid-template-columns: 180px 1fr 1fr; } }
/* Desktop: the numeral sits in a black disc, white figures. Phones keep
   the big white numeral over the photo (below). */
@media (min-width: 768px) {
  .band .band__no {
    display: inline-grid;
    place-items: center;
    width: clamp(114px, 10.4vw, 156px);
    aspect-ratio: 1;
    border-radius: 50%;
    background: #130F0C;
    color: #FFFFFF;
    font-size: clamp(54px, 5.6vw, 82px);
    line-height: 1;
    top: 0;
  }
}
.band__no {
  font-size: clamp(48px, 6vw, 96px);
  line-height: .82;
  letter-spacing: -0.05em;
  font-variant-numeric: tabular-nums;
  color: var(--fg);
  /* optical, not metric: the numeral's half-leading drops its cap a couple of
     pixels below the title's, and the eye reads the cap line, not the box */
  position: relative;
  top: -2px;
}
.band__inner h3 {
  font-size: clamp(28px, 3.8vw, 48px);
  line-height: 1.08;
  letter-spacing: -0.04em;
  font-weight: 400;
}
.band__inner p { color: var(--fg-60); line-height: 1.6; max-width: 46ch; }

/* A photograph carried along under the pointer while a row is hovered — the
   ‘follows the cursor’ option from tests/hover.html. The row is wide and quiet,
   so the picture arrives where the reader is already looking and leaves with
   them. It is decorative: empty alt, never focusable, and pointer-events:none
   so it can never intercept anything meant for the row. --mx/--my/--tilt are
   written by script.js; the transition on transform is what makes the frame lag
   behind the pointer rather than being pinned to it. */
.band { position: relative; }
.band__inner { position: relative; }
.pop {
  position: absolute;
  top: 0;
  left: 0;
  width: 260px;
  z-index: 3;
  opacity: 0;
  transform: translate3d(calc(var(--mx, 0px) - 50%), calc(var(--my, 0px) - 50%), 0)
             rotate(var(--tilt, 0deg));
  transition: opacity .22s var(--ease), transform .5s var(--ease);
  pointer-events: none;
}
.band:hover .pop { opacity: 1; }
/* near the row's left/right edges it fades and shrinks away (script.js) */
.band:hover .pop.is-edge { opacity: 0; }
.band:hover .pop.is-edge img { transform: scale(.86); }
/* One frame with no transform transition, set by script the moment the pointer
   enters: the frame is placed where the cursor already is and grows from there,
   instead of travelling in from wherever it was last left. */
.pop.is-placing { transition: opacity .22s var(--ease); }

/* the growth lives on the image, so it still reads as opening out of the
   cursor while the frame itself is busy trailing the pointer */
.pop img {
  display: block;
  width: 100%;
  aspect-ratio: 4 / 3;
  object-fit: cover;
  border-radius: var(--r-md);
  box-shadow: 0 18px 40px -18px rgba(0, 0, 0, .55);
  transform: scale(.86);
  transform-origin: center;
  transition: transform .44s var(--ease-pop, cubic-bezier(.22, 1.28, .38, 1));
}
.band:hover .pop img { transform: scale(1); }
/* over the body copy the reader is reading, not looking: the picture shrinks
   out of the way of the lines under the cursor, and grows back off them */
@media (hover: hover) and (min-width: 768px) {
  /* desktop only: on a phone the photo IS the tile, and a tap counts as
     hover, so this shrank the photo inside its card */
  .band:has(.band__inner > p:hover) .pop img { transform: scale(.5); }
}
/* Phone: How bands as a snap rail of image-bg tiles — photo fills the tile,
   big 01/02/03 pinned top-left over the photo; title + body on the bottom
   scrim. Desktop (≥768) keeps the three-column hover-pop row. */
@media (max-width: 767px) {
  .bands {
    display: flex;
    gap: var(--s-4);
    width: 100vw;
    max-width: 100vw;
    overflow-x: auto;
    overflow-y: hidden;
    overscroll-behavior-x: contain;
    scroll-snap-type: x mandatory;
    scroll-padding-inline: var(--pad);
    padding-inline: var(--pad);
    /* Top pad would stack on --gap-title-block and overshoot overheard */
    padding-block: 0 var(--s-4);
    scrollbar-width: none;
    -webkit-overflow-scrolling: touch;
  }
  .bands::-webkit-scrollbar { display: none; }
  .band {
    flex: 0 0 min(300px, 82vw);
    width: min(300px, 82vw);
    scroll-snap-align: start;
    border: 0;
  }
  .band:last-child { border-bottom: 0; }
  .band__inner {
    position: relative;
    display: grid;
    grid-template-columns: 1fr;
    grid-template-areas:
      "spacer"
      "title"
      "copy";
    grid-template-rows: 1fr auto auto;
    align-content: end;
    align-items: start;
    gap: var(--s-2);
    height: var(--tile-h);
    min-height: var(--tile-h);
    padding: var(--s-6);
    border-radius: var(--r-md);
    overflow: hidden;
    color: #FFFFFF;
    --fg: #FFFFFF;
    --bg: #130F0C;
    isolation: isolate;
    /* .wrap pad would inset the tile — cancel it so the photo goes edge to edge */
    width: 100%;
    max-width: none;
    margin: 0;
    box-sizing: border-box;
  }
  .band__inner::after {
    content: "";
    position: absolute;
    inset: 0;
    z-index: 1;
    pointer-events: none;
    background: linear-gradient(
      180deg,
      rgba(19, 15, 12, .28) 0%,
      rgba(19, 15, 12, .2) 28%,
      rgba(19, 15, 12, .45) 55%,
      rgba(19, 15, 12, .82) 100%
    );
  }
  /* Chapter numeral owns the top-left of the photo — not the title row. */
  .band__no {
    position: absolute;
    top: var(--s-5);
    left: var(--s-5);
    z-index: 2;
    margin: 0;
    font-size: clamp(72px, 22vw, 96px);
    line-height: .82;
    letter-spacing: -0.05em;
    color: #FFFFFF;
    text-shadow: 0 2px 18px rgba(19, 15, 12, .45);
    pointer-events: none;
  }
  .band__inner h3 {
    grid-area: title;
    position: relative;
    z-index: 2;
    align-self: end;
    color: #FFFFFF;
  }
  .band__inner p {
    grid-area: copy;
    position: relative;
    z-index: 2;
    max-width: none;
    color: color-mix(in srgb, #FFFFFF 82%, transparent);
  }
  .pop {
    position: absolute;
    inset: 0;
    z-index: 0;
    width: 100%;
    max-width: none;
    height: 100%;
    opacity: 1;
    transform: none;
    transition: none;
    pointer-events: none;
  }
  .pop img {
    display: block;
    width: 100%;
    height: 100%;
    object-fit: cover;
    aspect-ratio: auto;
    transform: none;
    border-radius: 0;
    box-shadow: none;
  }
}
/* Phones: the How tiles take the What you can expect card's look — the same
   78vw 4:5 card, 28px corners and lift, a dark fade at the foot with a soft
   wash under the numeral, and the same title / body sizes. */
@media (max-width: 767px) {
  .band { flex: 0 0 78vw; width: 78vw; }
  .band__inner {
    height: auto; min-height: 0; aspect-ratio: 4 / 5;
    border-radius: 28px;
    box-shadow: 0 22px 40px -14px rgba(19, 15, 12, .4);
  }
  .bands { padding-bottom: var(--s-12); }
  .band__inner::after {
    background:
      linear-gradient(to top, rgba(10, 8, 6, .92) 0%, rgba(10, 8, 6, .72) 48%, rgba(10, 8, 6, 0) 78%),
      linear-gradient(to bottom, rgba(10, 8, 6, .38) 0%, rgba(10, 8, 6, 0) 34%);
  }
  .band__inner h3 { font-size: 30px; line-height: 1.1; letter-spacing: -0.035em; }
  .band__inner p { margin-top: 0; font-size: 14px; line-height: 1.5; color: rgba(255, 255, 255, .8); }
}
@media (prefers-reduced-motion: reduce) and (min-width: 768px) {
  .pop { display: none; }
}

.steps { display: grid; gap: var(--s-6); }
@media (min-width: 900px) { .steps { grid-template-columns: repeat(3, 1fr); } }

.step {
  border: 0;
  border-radius: var(--r-md);
  padding: var(--s-8);
  display: grid;
  align-content: start;
  background: color-mix(in srgb, var(--fg) 7%, var(--bg));
  transition: background-color .3s var(--ease);
}
.step:hover { background: color-mix(in srgb, var(--fg) 11%, var(--bg)); }

.step__no {
  display: block;
  font-size: clamp(40px, 5vw, 64px);
  line-height: 0.9;
  letter-spacing: -0.04em;
  color: var(--fg);
  font-variant-numeric: tabular-nums;
  margin-bottom: var(--s-8);
}

.step h3 {
  font-size: clamp(22px, 2.4vw, 28px);
  line-height: 1.2;
  letter-spacing: -0.01em;
  font-weight: 400;
  margin-bottom: var(--s-4);
}
.step p { color: var(--fg-60); line-height: 1.6; max-width: 40ch; }

/* ── A gift for you ─────────────────────────────────────────────────────── */

.gift { padding-block: var(--s-6) var(--s-14); }
.how { padding-bottom: var(--s-8); }
.how + .section { border-top: 0; }
/* Tan partner ground: the default section hairline sat in the beige when
   gatherings shared #DFD1CB. Expect is a light-blue stop again, so it keeps
   the default .section hairline at that colour edge. Leave the .section rule. */
.partner { border-top: 0; }
/* Cinematic partner: the ground goes near-black here (data-ground), so the
   stage is full-bleed by construction and paint() resolves the ink to white. */
.cine { text-align: center; }
.cine__title {
  /* a notch heavier than regular: this face ships no medium cut */
  font-weight: 400;
  -webkit-text-stroke: 0.012em currentColor;
  font-size: clamp(52px, 8vw, 140px);
  line-height: .96;
  letter-spacing: -.05em;
}
.cine__lede {
  margin: var(--s-6) auto 0;
  max-width: 46ch;
  font-size: clamp(17px, 1.35vw, 20px);
  line-height: 1.5;
  color: var(--fg-60);
}
.cine__video.video {
  margin: clamp(40px, 5vw, 72px) auto 0;
  max-width: 1100px;
  border-radius: var(--r-md);
  overflow: hidden;
}
.cine__quote { margin: clamp(40px, 5vw, 64px) auto 0; }
.cine__quote blockquote {
  max-width: 24ch;
  margin-inline: auto;
  font-size: clamp(26px, 2.8vw, 42px);
  line-height: 1.12;
  letter-spacing: -.035em;
}
.cine__quote figcaption { margin-top: var(--s-4); font-size: 14px; color: var(--fg-60); }
.cine__paths {
  display: flex;
  justify-content: center;
  flex-wrap: wrap;
  gap: var(--s-3);
  margin-top: var(--s-12);
}
.cine__path {
  display: inline-flex;
  align-items: center;
  gap: 10px;
  padding: 16px 26px;
  border-radius: 999px;
  box-shadow: inset 0 0 0 1.5px color-mix(in srgb, var(--fg) 50%, transparent);
  font-size: 17px;
  transition: background .2s var(--ease), color .2s var(--ease);
}
.cine__path:hover { background: var(--fg); color: var(--bg); }
.cine__path .arrow { transition: translate .25s var(--ease); }
.cine__path .arrow--e { margin-left: 0; }   /* the pill's flex gap spaces it */
.cine__path:hover .arrow { translate: 4px 0; }
.partner--cine .qticker, .partner--cine .icard { margin-top: var(--s-20); }
.gift__copy { display: grid; gap: var(--s-2); }
.gift__kicker {
  font-size: var(--t-caption);
  line-height: 19.5px;
  color: var(--fg-50);
}
.gift__line {
  font-size: var(--t-body);
  line-height: 1.55;
  color: var(--fg-60);
  max-width: 58ch;
}

/* ── Video (click-to-load) ──────────────────────────────────────────────── */

.video {
  margin-top: var(--s-14);
  margin-inline: auto;
  max-width: 420px;
  aspect-ratio: var(--r, 9 / 16);
  border: 0;
  border-radius: var(--r-md);
  position: relative;
  background: color-mix(in srgb, var(--fg) 7%, var(--bg));
  overflow: hidden;
}
.video--wide { max-width: 1000px; }

.video__btn {
  position: absolute; inset: 0;
  width: 100%; height: 100%;
  background: rgba(0, 0, 0, 0);
  border: 0; padding: 0;
  cursor: pointer;
  display: grid;
  place-content: center;
}
.video__btn::before {
  content: "";
  position: absolute; inset: 0;
  /* Poster + a soft centre scrim so the white Play control stays legible. */
  background-image:
    radial-gradient(ellipse 70% 70% at 50% 50%, rgba(0, 0, 0, .30), rgba(0, 0, 0, 0) 70%),
    var(--thumb);
  background-size: cover;
  background-position: center;
  transition: transform .4s var(--ease);
}
/* Hover zooms the poster layer only — the Play control stays put (its own
   scale is never touched), and .video's overflow: hidden crops the growth. */
.video__btn:hover::before { transform: scale(1.06); }
.video__btn:active::before { transform: scale(1.04); }

/* Play control: big white triangle + the word "Play" in the display face,
   sized off the box width (cqi) so it holds its proportion at every size. */
.video { container-type: inline-size; }
.video__play {
  position: relative;
  z-index: 1;
  transform: none;
  display: flex;
  /* Baseline-align so the (empty) triangle box sits on the text baseline and
     its top meets the cap-height of "Play" — centring the line box would leave
     the letters riding above the triangle because of descender space. */
  align-items: baseline;
  gap: .3em;
  font-family: var(--font);
  font-weight: 500;
  font-size: clamp(2.5rem, 12vw, 8rem);
  font-size: clamp(2.5rem, 13cqi, 8.5rem);
  line-height: 1;
  letter-spacing: -.02em;
  color: #fff;
  /* Optical centring: the triangle carries more visual mass than the letters,
     so the geometric centre reads left-heavy — nudge the group a hair right.
     Vertically, split the difference between the cap-height block and the
     descender of "y" so the mass sits just above true centre. */
  left: .06em;
  top: .06em;
}
.video__play::before {
  content: "";
  width: .68em; height: .74em;
  background: currentColor;
  clip-path: polygon(0 0, 100% 50%, 0 100%);
}
.video__play::after { content: "Play"; }

.video iframe { position: absolute; inset: 0; width: 100%; height: 100%; border: 0; }
/* Click shield over the playing embed (script.js): swallows the pointer so
   YouTube's hover chrome never appears; shows our Play only while paused. */
.video__shield {
  position: absolute;
  inset: 0;
  z-index: 2;
  display: grid;
  place-items: center;
  border: 0;
  padding: 0;
  background: transparent;
  cursor: pointer;
  transition: background .25s var(--ease);
}
.video__shield .video__play { opacity: 0; transition: opacity .25s var(--ease); }
.video.is-paused .video__shield { background: rgba(0, 0, 0, .35); }
.video.is-paused .video__shield .video__play { opacity: 1; }

/* HD inner viewport: YouTube keys quality off the iframe's pixel size, not
   the CSS layout box. Keep a real 1280×720 (or 720×1280) player and scale it
   into the overflow-hidden 16:9 box. Transform runs after click only.
   scale() needs a unitless factor: length/length, not length/number. */
.video__embed {
  position: absolute;
  top: 0;
  left: 0;
  width: calc(var(--yt-w, 1280) * 1px);
  height: calc(var(--yt-h, 720) * 1px);
  transform: scale(calc(100cqw / (var(--yt-w, 1280) * 1px)));
  transform-origin: top left;
}
.video .video__embed iframe {
  inset: 0;
  width: 100%;
  height: 100%;
  /* overscan: YouTube's title bar and logo sit in the outer ~9% — push them
     past .video's rounded clip so only the picture shows */
  transform: scale(1.2);
  transform-origin: center;
}

/* ── Photo wall (after What you can expect) ─────────────────────────────── */
.wall { border-top: 0; padding-top: 0; }
.wall__grid { display: flex; align-items: flex-start; column-gap: var(--s-6); }
.wall__col { flex: 1 1 0; min-width: 0; display: flex; flex-direction: column; gap: var(--s-6); }
.wall__item {
  margin: 0;
  border-radius: 28px;
  overflow: hidden;
  background: color-mix(in srgb, var(--fg) 8%, transparent);
}
.wall__item img { display: block; width: 100%; height: 100%; object-fit: cover; }
/* mixed shapes (set as tile heights by script.js), so the wall never
   settles into one rhythm */
.wall__shuffle {
  display: flex; align-items: center; gap: 8px;
  margin: var(--s-6) auto 0;   /* under the wall, centred */
  padding: 10px 18px; border: 0; border-radius: var(--r-pill);
  background: #130F0C; color: #fff; font: inherit; font-size: 15px; cursor: pointer;
  transition: transform .2s var(--ease);
}
.wall__shuffle:hover { transform: scale(1.04); }
.wall__shuffle svg { width: 16px; height: 16px; }
.wall__shuffle.is-spun svg { animation: wallspin .5s var(--ease); }
/* With a mouse, no button in the open: over the photos the cursor itself
   becomes a little "Shuffle" pill, and a click shuffles. The button stays
   for keyboards (it shows when focused) and for touch screens. */
.wall__cursor { position: fixed; left: 0; top: 0; z-index: 60; display: flex; align-items: center; gap: 6px; padding: 8px 14px; border-radius: var(--r-pill); background: #130F0C; color: #fff; font-size: 14px; pointer-events: none; opacity: 0; transform: translate(-50%, -50%) scale(.6); transition: opacity .18s var(--ease), transform .18s var(--ease); }
.wall__cursor svg { width: 14px; height: 14px; }
.wall__cursor.is-on { opacity: 1; transform: translate(-50%, -50%) scale(1); }
.wall__cursor.is-on.is-press { transform: translate(-50%, -50%) scale(.85); }
@media (hover: hover) and (pointer: fine) {
  .wall__grid { cursor: none; }
  .wall__grid * { cursor: none; }
  .wall__shuffle:not(:focus-visible) { position: absolute; width: 1px; height: 1px; padding: 0; overflow: hidden; clip-path: inset(50%); white-space: nowrap; }
}
@keyframes wallspin { to { transform: rotate(360deg); } }
@media (max-width: 599px) {
  .wall__grid { column-gap: var(--s-3); }
  .wall__col { gap: var(--s-3); }
  .wall__item { border-radius: 18px; }
}

/* ── Quotes ─────────────────────────────────────────────────────────────── */

/* Photo show above “Overheard at school”. Scroll-driven: .show is a tall
   track; .show__pin sticks to the screen while it passes. As the frame
   arrives it grows from an inset card to the full window (--grow, 0→1, set
   by script); after that, scrolling steps through the photos (--i), each
   one filling the frame. */
.show {
  position: relative;
  /* one screen of travel to grow, ~60vh per photo, then 60vh to shrink away */
  height: calc(100svh + var(--n, 6) * 60svh + 60svh);
  margin-bottom: calc(var(--s-8) * 2);
}
/* phones: the frame is a short card centred in a full-height pin, which
   left about half a screen of empty page under the How we work cards; pull
   the show up by that much. The frame then grows upward and "How are you?"
   rides its top corner, so the gap has to survive that rise — s-8 alone let
   the cards kiss, and overlap on a tall phone. */
@media (max-width: 699px) {
  .show {
    margin-top: calc((46svh - 100dvh) / 2 + var(--s-8) + var(--s-14) + var(--s-2));
    /* a bigger gap under it than above: the frame settling small and the
       Overheard title arriving read as crowded at s-8 (there is no s-16) */
    margin-bottom: calc((46svh - 100dvh) / 2 + var(--s-14));
  }
}
.show__pin {
  position: sticky;
  top: 0;
  /* dvh, not svh: svh is the screen with Safari's bars showing, so once they
     tuck away the pin was shorter than the screen and the frame, centred in
     it, rode high. dvh follows the bars, so the frame stays mid-screen. */
  height: 100svh;
  height: 100dvh;
  display: grid;
  place-items: center;
}
.show__viewport {
  position: relative;
  overflow: hidden;
  /* small card → the full width of the page's margins (the .wrap), never
     edge to edge */
  width: calc(64% + 36% * var(--grow, 1));
  height: calc(56svh + 30svh * var(--grow, 1));
  border-radius: var(--r-md);
  background: #130F0C;
}
/* phones: the frame is narrow, so 86% of the screen's height made a tall
   portrait strip; it tops out around 70% instead */
@media (max-width: 699px) {
  /* full size: the whole screen less the bar at the top and the Donate
     pill at the foot (64px each: --s-6 from the edge + the 40px pill or
     logo row) and the site's own --pad inside each, so the frame keeps the
     page's gutter from both */
  .show { --show-full: calc(100dvh - 2 * (var(--s-6) + 40px + var(--pad))); }
  .show__viewport { height: calc(46svh + (var(--show-full) - 46svh) * var(--grow, 1)); }
}
.show__scroller { position: absolute; inset: 0; }
.show__slide {
  position: absolute;
  inset: 0;
  margin: 0;
  opacity: 0;
  transition: opacity .6s var(--ease);
}
.show__slide.is-on { opacity: 1; z-index: 1; }
/* the outgoing photo holds full opacity underneath while the next fades in
   over it, so the frame never dips to the page colour between photos */
.show__slide.is-prev { opacity: 1; transition: none; }
.show__slide video,
.show__slide img {
  display: block;
  width: 100%;
  height: 100%;
  object-fit: cover;
}
.show__dots {
  position: absolute;
  left: 50%;
  bottom: var(--s-6);
  z-index: 1;
  display: flex;
  gap: var(--s-2);
  padding: var(--s-2) var(--s-3);
  border-radius: var(--r-pill);
  background: rgba(19, 15, 12, .35);
  backdrop-filter: blur(10px);
  -webkit-backdrop-filter: blur(10px);
  transform: translateX(-50%);
}
.show__dots i {
  width: 6px;
  height: 6px;
  border-radius: 50%;
  background: rgba(255, 255, 255, .45);
  transition: background-color .3s var(--ease), width .3s var(--ease);
}
.show__dots i.is-on { width: 18px; border-radius: 3px; background: #FFFFFF; }
/* the photos step on a vertical scroll, so the dots stand up the right
   edge and the current one stretches downward — a sideways row read as
   something to swipe. Phones first; desktop the same now too. */
.show__dots {
  left: auto;
  right: var(--s-3);
  top: 50%;
  bottom: auto;
  flex-direction: column;
  padding: var(--s-3) var(--s-2);
  transform: translateY(-50%);
  transition: opacity .3s var(--ease);
}
@media (min-width: 700px) { .show__dots { right: var(--s-5); } }
.show__dots i { transition: background-color .3s var(--ease), height .3s var(--ease); }
.show__dots i.is-on { width: 6px; height: 18px; }
.show.is-past .show__dots { opacity: 0; }
/* no scroll choreography: a plain full-width frame, photos step on scroll */
@media (prefers-reduced-motion: reduce) {
  .show__viewport { width: 100%; height: 80svh; border-radius: var(--r-md); }
  .show__slide { transition: none; }
}

.railhead {
  display: flex;
  align-items: end;
  justify-content: space-between;
  gap: var(--s-8);
  margin-bottom: var(--gap-title-block);
}
.railhead .h1 { margin-bottom: 0; }
/* Overheard titled like How we work: the big centred .cine__title, with the
   arrows parked at the right edge on its baseline, and a tighter gap to the
   quotes than the usual title-to-block space */
.railhead:has(.railhead__title) { position: relative; display: block; text-align: center; margin-bottom: var(--s-8); }
.railhead__title { margin: 0 auto; text-wrap: balance; }
.railhead:has(.railhead__title) .railnav { position: absolute; right: 0; bottom: 0; }

.railnav { display: flex; gap: var(--s-3); flex: none; }
.railnav__btn {
  width: 52px;
  height: 52px;
  display: grid;
  place-content: center;
  background: rgba(0, 0, 0, 0);
  border: 1px solid var(--rule-strong);
  border-radius: var(--r-pill);
  cursor: pointer;
  transition: border-color .2s var(--ease), opacity .2s var(--ease), transform .2s var(--ease);
}
.railnav__btn:hover { border-color: var(--fg); }
.railnav__btn:active { transform: scale(.94); }
.railnav__btn[disabled] { opacity: .3; cursor: default; border-color: var(--rule); }
.railnav__btn[disabled]:hover { border-color: var(--rule); }
.railnav__btn svg { width: 20px; height: 20px; }
.railnav__btn path {
  fill: none;
  stroke: currentColor;
  stroke-width: 3;
  stroke-linecap: butt;
  stroke-linejoin: miter;
}
@media (max-width: 700px) { .railnav { display: none; } }

/* Full-bleed drag rail: cards align to the content edge, nothing is clipped
   by the container padding on either side. */
.rail {
  --rail-gutter: max(var(--pad), calc((100vw - var(--wrap)) / 2 + var(--pad)));
  width: 100vw;
  margin-left: calc(50% - 50vw);
  overflow-x: auto;
  overscroll-behavior-x: contain;
  scroll-snap-type: x mandatory;
  scroll-padding-inline: var(--rail-gutter);
  scrollbar-width: none;
  cursor: grab;
}
.rail::-webkit-scrollbar { display: none; }
.rail.is-dragging { cursor: grabbing; scroll-snap-type: none; }
.rail.is-dragging .quote,
.rail.is-dragging .rail__track > * { pointer-events: none; }

.rail__track {
  display: flex;
  gap: var(--s-6);
  width: max-content;
  padding-inline: var(--rail-gutter);
  /* Room for .quote--ask:hover translateY(-4px) — overflow-x stays auto. */
  padding-top: var(--s-3);
  padding-bottom: var(--s-2);
}

/* Portrait rather than square: a card the shape of a page reads as something
   set down on the rail, and the attribution has somewhere to sit far from the
   quote instead of crowding under it. */
.quote {
  /* Stepped down from 380×500 — the rail was reading as a wall of plaques
     rather than a set of cards you glance across. */
  flex: 0 0 min(300px, 72vw);
  scroll-snap-align: start;
  border: 0;
  border-radius: var(--r-md);
  padding: var(--s-8) var(--s-6);
  display: grid;
  gap: var(--s-5);
  grid-template-rows: 1fr auto;
  min-height: 468px;
  /* Black cards + white type on every ground — the rail reads as plaques. */
  background: #130F0C;
  color: #FFFFFF;
  --fg: #FFFFFF;
  --bg: #130F0C;
  transition: background-color .3s var(--ease), transform .3s var(--ease);
}
/* Same footprint as How tiles + Bartow cards on phones */
@media (max-width: 767px) {
  .quote,
  .quote--ask {
    /* Taller than --tile-h so long quotes aren't clipped; How/Bartow stay 420. */
    height: 468px;
    min-height: 468px;
    box-sizing: border-box;
    overflow: hidden;
  }
}
.quote:hover { background: #1c1814; }
/* Quote on a plaque, not a headline. Size follows the ~252–300px card —
   the previous vw clamp read as display type on desktop. */
.overheard .quote blockquote {
  font-family: var(--font);
  font-size: 1.125rem;
  line-height: 1.4;
  letter-spacing: -0.02em;
}
@media (max-width: 767px) {
  .overheard .quote blockquote {
    font-size: 1.25rem;
    line-height: 1.4;
  }
}
.quote blockquote {
  font-family: var(--font-quote);
  font-size: clamp(18px, 2.1vw, 24px);
  line-height: 1.3;
  letter-spacing: -0.008em;
}
.quote figcaption {
  font-size: var(--t-caption);
  line-height: 19.5px;
  display: flex;
  align-items: center;
  gap: var(--s-3);
  color: color-mix(in srgb, #FFFFFF 72%, transparent);
}
/* Portrait slot — a solid disc until real photography lands here. Each one
   carries its own --av so the rail reads as six people rather than six
   identical dots: the page's own orange, blue and yellow, plus three deeps
   that are only ever this size. */
.quote__av {
  flex: 0 0 auto;
  width: 36px;
  height: 36px;
  border-radius: var(--r-pill);
  background: var(--av, var(--fg));
}

/* The invitation that closes the rail. White sheet + dark ink — exception to
   the black plaques — with a real site CTA instead of tracked all-caps. */
.quote--ask {
  background: #FFFFFF;
  color: #130F0C;
  --fg: #130F0C;
  --bg: #FFFFFF;
  text-decoration: none;
  transition: background-color .3s var(--ease), transform .3s var(--ease),
              box-shadow .3s var(--ease);
  box-shadow: inset 0 0 0 1px color-mix(in srgb, #130F0C 10%, transparent);
}
.quote--ask:hover {
  background: #FFFFFF;
  transform: translateY(-4px);
  box-shadow: inset 0 0 0 1px color-mix(in srgb, #130F0C 14%, transparent),
              0 12px 28px -18px rgba(0, 0, 0, .35);
}
.quote__ask {
  font-family: var(--font-quote);
  font-size: clamp(18px, 2.1vw, 24px);
  line-height: 1.3;
  letter-spacing: -0.008em;
  text-wrap: pretty;
}
/* Matches .btn--solid voice: sentence case pill, not uppercase EMAIL US → */
.quote__go {
  display: inline-flex;
  align-items: center;
  gap: var(--s-2);
  justify-self: start;
  padding: 12px 20px;
  border-radius: var(--r-pill);
  background: #130F0C;
  color: #FFFFFF;
  font-size: var(--t-body);
  line-height: 1.2;
  letter-spacing: -0.01em;
  text-transform: none;
  transition: transform .2s var(--ease), opacity .2s var(--ease);
}
.quote__go svg {
  width: 16px;
  height: 16px;
  fill: none;
  stroke: currentColor;
  stroke-width: 2.4;
  transition: transform .3s var(--ease);
}
.quote--ask:hover .quote__go svg { transform: translateX(3px); }

/* ── Partner ────────────────────────────────────────────────────────────── */

.pull {
  margin-top: var(--s-20);
  display: grid;
  gap: var(--s-6);
}
.pull--tight { margin-top: var(--s-12); }

/* Video + quote side by side */
.feature {
  margin-top: var(--s-20);
  display: grid;
  gap: var(--s-12);
  align-items: center;
}
.feature > * { min-width: 0; }
@media (min-width: 1000px) {
  .feature { grid-template-columns: 1.15fr 0.85fr; gap: var(--s-14); }
}
.feature .video { margin: 0; max-width: none; width: 100%; }
.pull--beside {
  margin-top: 0;
  border-top: 0;
  padding-top: 0;
}
.pull--beside blockquote { max-width: 20ch; }

.feature--story { align-items: center; }
@media (min-width: 900px) {
  .feature--story { grid-template-columns: 0.85fr 1.15fr; }
}
.photo--story {
  position: relative;
  aspect-ratio: 4 / 3;
  margin: 0;
  padding: 0;
  overflow: hidden;
}
.photo--story img {
  position: absolute;
  inset: 0;
  display: block;
  width: 100%;
  height: 100%;
  object-fit: cover;
}
/* Desktop: pair is the feature grid. Phone: contents so each card snaps alone
   — photo card first in markup, so the image leads the quote in the rail. */
.story__pair { margin-top: 0; }
.story__pair > .story__card { min-width: 0; }
@media (max-width: 899px) {
  .story__pair { display: contents; }
}

.pull blockquote {
  font-size: clamp(22px, 3.4vw, 40px);
  line-height: 1.22;
  letter-spacing: -0.02em;
  max-width: 24ch;
  text-wrap: balance;
}
.pull--alt blockquote {
  font-size: clamp(18px, 2.2vw, 24px);
  line-height: 1.42;
  max-width: 48ch;
}
.pull figcaption {
  font-size: var(--t-caption);
  line-height: 19.5px;
}

.paths { margin-top: var(--s-20); display: grid; gap: var(--s-6); }
@media (min-width: 768px) { .paths { grid-template-columns: 1fr 1fr; } }

.path {
  border: 0;
  border-radius: var(--r-md);
  padding: var(--s-12) var(--s-8) var(--s-8);
  display: grid;
  gap: var(--s-4);
  align-content: start;
  background: color-mix(in srgb, var(--fg) 7%, var(--bg));
  transition: background-color .3s var(--ease);
}
.path:hover { background: color-mix(in srgb, var(--fg) 11%, var(--bg)); }
.path h3 {
  font-size: clamp(22px, 2.4vw, 28px);
  line-height: 1.2;
  font-weight: 400;
  letter-spacing: -0.01em;
}
.path p { color: var(--fg-60); max-width: 38ch; }
.path .btn { justify-self: start; margin-top: var(--s-4); }
/* Modest mark, not a hero illustration — viewBox 24 keeps stroke weight */
.path__icon {
  width: 28px;
  height: 28px;
  color: var(--fg);
  margin-bottom: var(--s-2);
  overflow: visible;
}
/* Phone: the path tiles were ~square shells — pull padding/gaps in so icon,
   heading, body, and button sit denser without touching the 28px marks. */
@media (max-width: 767px) {
  .path {
    padding: var(--s-6) var(--s-5) var(--s-5);
    gap: var(--s-2);
  }
  .path__icon { margin-bottom: 0; }
  .path .btn { margin-top: var(--s-2); }
  /* Lead-in to the video was a full s-20 (80px) under the partner lede */
  .partner .feature { margin-top: var(--s-8); }
}

/* ── What you can expect ────────────────────────────────────────────────── */

/* ── What you can expect ─────────────────────────────────────────────────
   The five steps as an index, with one landscape frame beside it: the list is
   the control, the frame is the result. Step changes slide the outgoing card
   out and the incoming card in (direction follows the list), so the stage
   reads as one object turning over rather than as a hard cut. */
/* the intro split has to share the stepper's column ruler, or the body copy's
   left edge lands a few px off the photo frame below it */
@media (min-width: 900px) {
  .expect .split { grid-template-columns: 1fr 1fr; gap: var(--s-12); }
}

.gath {
  margin-top: var(--s-14);
  display: grid;
  gap: var(--s-12);
}
@media (min-width: 900px) { .gath { grid-template-columns: 1fr 1fr; align-items: start; } }

.gath__index { list-style: none; margin: 0; padding: 0; display: grid; gap: var(--s-2); }
.gath__item button {
  display: flex;
  align-items: center;
  gap: var(--s-4);
  width: 100%;
  background: none;
  border: 0;
  padding: var(--s-2) 0;
  color: var(--fg-35);
  font: inherit;
  font-size: clamp(26px, 3.6vw, 44px);
  line-height: 1.1;
  letter-spacing: -0.026em;
  text-align: left;
  cursor: pointer;
  transition: color .3s var(--ease);
}
.gath__item button:hover { color: var(--fg-60); }
.gath__item.is-on button { color: var(--fg); }
/* The step number as a counter rather than a caption: a filled disc the same
   height as the label beside it (Greeting / Story). font-size % is of the
   button; width/height em are of that shrunk size — 1/0.48 restores a full
   title-em diameter (a custom-prop 1em was re-resolving on the child and
   collapsing the circle again). Digits sit at ~half the title so 01 reads
   inside the disc rather than as a micro caption. */
.gath__no {
  flex: 0 0 auto;
  display: grid;
  place-items: center;
  font-size: 48%;
  width: 2.083333em;
  height: 2.083333em;
  border-radius: var(--r-pill);
  background: var(--fg);
  color: var(--bg);
  line-height: 1;
  letter-spacing: 0;
  /* Optical centring. Digits carry no descender, so the ink sits about .08em
     below the middle of the line box — measured, not guessed. Half of this
     padding is what lifts it back onto the disc's true centre. */
  padding-bottom: .16em;
  /* Disc sat a hair above the label's visual midline; 2px drops it onto the word. */
  transform: translateY(2px);
  font-variant-numeric: tabular-nums;
  transition: background-color .3s var(--ease), opacity .3s var(--ease);
  /* the inactive steps sit back with their titles */
  opacity: .38;
}
.gath__item button:hover .gath__no { opacity: .65; }
.gath__item.is-on .gath__no { opacity: 1; }

/* the stage holds every frame stacked, so switching costs no layout */
/* The cards are stacked in one grid cell rather than positioned absolutely, so
   the stage takes the height of the tallest of them. Absolute cards were pinned
   to the stage's own height and the caption ran back up under the photograph
   whenever the frame was taller than that. */
.gath__stage { position: relative; display: grid; overflow: hidden; }
.gath__card {
  grid-area: 1 / 1;
  margin: 0;
  display: grid;
  grid-template-rows: auto auto;
  align-content: start;
  gap: var(--s-6);
  opacity: 0;
  transform: translateY(14px);
  pointer-events: none;
  z-index: 0;
  transition:
    opacity .42s var(--ease),
    transform .5s cubic-bezier(.22, 1.15, .36, 1);
}
.gath__card.is-on {
  opacity: 1;
  transform: translateY(0);
  pointer-events: auto;
  z-index: 2;
}
/* Parked just off-stage so the enter transition has somewhere to come from.
   transition:none holds the pose for one frame; JS drops the class after. */
.gath__card.is-from-down {
  transition: none;
  opacity: 0;
  transform: translateY(22px);
}
.gath__card.is-from-up {
  transition: none;
  opacity: 0;
  transform: translateY(-22px);
}
.gath__card.is-out-up {
  opacity: 0;
  transform: translateY(-22px);
  z-index: 1;
}
.gath__card.is-out-down {
  opacity: 0;
  transform: translateY(22px);
  z-index: 1;
}
.gath__img {
  position: relative;
  aspect-ratio: 4 / 3;
  border-radius: var(--r-md);
  overflow: hidden;
  background: color-mix(in srgb, var(--fg) 7%, var(--bg));
}
.gath__img img {
  position: absolute;
  inset: 0;
  display: block;
  width: 100%;
  height: 100%;
  max-width: none;
  max-height: none;
  object-fit: cover;
  object-position: 20% 40%;
}
.gath__img img[src*="greeting-mic"] { object-position: 50% 40%; }
.gath__img img[src*="story-reading"] { object-position: 50% 30%; }
.gath__img img[src*="icebreaker-card"] { object-position: 50% 55%; }
.gath__img img[src*="conversation-cards"] { object-position: 50% 50%; }
.gath__img img[src*="action-writing"] { object-position: 40% 50%; }
/* ── Phone: the step's detail as a card at the foot of the screen ────────
   On a narrow screen the stage would sit a scroll below the list it belongs
   to, so tapping a step changes something the reader cannot see. Instead the
   detail rides at the bottom of the viewport while the section is on screen —
   the list stays under the thumb, and the card updates in place.

   It needs no new markup and no new state: the stage already holds every card
   with .is-on on the current one, so this is the same object re-placed. */
@media (max-width: 899px) {
  .expect .gath__stage {
    position: fixed;
    left: var(--s-3);
    right: var(--s-3);
    bottom: var(--s-3);
    z-index: 40;
    min-height: 0;
    border-radius: var(--r-md);
    /* Latest ask: black card + white type on phones — reads as a discrete
       note over the grey gatherings ground rather than another pale panel */
    background: #130F0C;
    color: #FFFFFF;
    --fg: #FFFFFF;
    --bg: #130F0C;
    box-shadow: 0 20px 50px -18px rgba(0, 0, 0, .55);
    /* ~1.5× the earlier phone card: the 120px strip was too thin to read the
       note against, and the thumbnail read as a stamp rather than a frame */
    padding: var(--s-5);
    /* Fully below the viewport at rest — slides up when the section is being
       read, and slides back down when the reader leaves, rather than a 12px
       fade that barely read as motion. */
    opacity: 0;
    transform: translateY(calc(100% + var(--s-3)));
    pointer-events: none;
    transition:
      opacity .35s var(--ease),
      transform .55s cubic-bezier(.22, 1.2, .36, 1);
  }
  .expect.is-near .gath__stage {
    opacity: 1;
    transform: translateY(0);
    pointer-events: auto;
  }
  /* The sticky Donate chip shares this corner — tuck it away while the stage
     owns the foot, so the two never stack. */
  body:has(.expect.is-near) .sticky-donate {
    opacity: 0;
    pointer-events: none;
    transform: translateY(10px);
    transition: opacity .25s var(--ease), transform .25s var(--ease);
  }

  /* the card lies down: portrait thumb beside the note — narrower than the
     old 144 square so the caption gets the width, taller so it fills the
     card's own height rather than sitting as a stamp. */
  .expect .gath__card {
    grid-template-rows: none;
    grid-template-columns: 140px 1fr;
    gap: var(--s-5);
    align-items: stretch;
  }
  .expect .gath__img {
    aspect-ratio: auto;
    height: 100%;
    min-height: 148px;
    border-radius: var(--r-sm);
  }
  .expect .gath__card figcaption {
    align-content: center;
  }
  .expect .gath__label {
    /* Quiet against the dark card — sits under the large index labels, not with them */
    color: rgba(255, 255, 255, .55);
  }
  .expect .gath__note {
    font-size: 15px;
    line-height: 1.45;
    color: #FFFFFF;
  }

  /* No spacer between the step list and Volunteer — 156px here was the
     "way too much space" void. The fixed stage overlays the viewport foot;
     scrollspy no longer needs document height to reach Action. */
  .expect.is-near .gath { padding-bottom: 0; }
  .expect .cta-row { margin-top: var(--s-14); margin-bottom: var(--s-6); }

  /* Phone: type stays large; spacing lands between the old s-5/s-4 ladder
     (too loose) and the 0/2px pass (rows nearly touching). */
  .expect .gath__index { gap: var(--s-2); }
  .expect .gath__item button {
    /* ~40px at 375 — was reading as a caption next to the foot card */
    font-size: clamp(38px, 10.5vw, 48px);
    padding: var(--s-2) 0;
    gap: var(--s-4);
  }
}

.gath__card figcaption {
  display: grid;
  gap: var(--s-2);
  align-content: start;
  max-width: 46ch;
}
/* Small step title on the card — index + name, quieter than the list labels */
.gath__label {
  font-size: var(--t-caption);
  line-height: 1.3;
  letter-spacing: 0.01em;
  color: var(--fg-50);
  font-variant-numeric: tabular-nums;
}
@media (min-width: 900px) {
  .gath__label { display: none; }
}
.gath__note {
  font-size: var(--t-body);
  line-height: 1.6;
  color: var(--fg-60);
}
@media (prefers-reduced-motion: reduce) {
  .gath__card,
  .gath__card.is-from-down,
  .gath__card.is-from-up,
  .gath__card.is-out-up,
  .gath__card.is-out-down { transition: none; transform: none; }
  .expect .gath__stage {
    transition: opacity .3s var(--ease);
    transform: none;
  }
  .expect.is-near .gath__stage { transform: none; }
}

/* archived treatment, kept for tests/expect.html option 12 */
.agenda { margin-top: var(--s-14); }

.agenda__row {
  display: grid;
  gap: var(--s-4) var(--s-8);
  padding-block: var(--s-8);
  border-top: 1px solid var(--rule);
  align-items: start;
}
.agenda__row:last-child { border-bottom: 1px solid var(--rule); }
@media (min-width: 768px) {
  .agenda__row {
    /* every row the same height, whatever the copy runs to */
    grid-template-columns: 200px minmax(150px, 220px) 1fr;
    min-height: 214px;
  }
}

.agenda__img {
  margin: 0;
  aspect-ratio: 4 / 3;
  border: 0;
  border-radius: var(--r-md);
  background: color-mix(in srgb, var(--fg) 7%, var(--bg));
  display: grid;
  place-content: center;
}
@media (max-width: 767px) { .agenda__img { max-width: none; width: 100%; } }
.agenda__row h3 {
  font-size: clamp(20px, 2.2vw, 24px);
  line-height: 1.25;
  font-weight: 400;
  letter-spacing: -0.01em;
}
.agenda__row p { color: var(--fg-60); line-height: 1.6; max-width: 60ch; }

/* ── Story / results ────────────────────────────────────────────────────── */

/* Desktop held photo: left column pins (photo + quote) while the narrative
   scrolls on the right. Phones keep the swipe rail, so this block is hidden
   under 900px. */
.story__held { display: none; }
@media (min-width: 900px) {
  .story .story__rail,
  .story .story__donate { display: none; }

  .story__held {
    display: grid;
    grid-template-columns: 0.9fr 1.1fr;
    gap: var(--s-14);
    align-items: start;
  }
  /* Clear the sticky nav; the right column supplies the travel height. */
  .held__stick { position: sticky; top: 96px; }
  .held__shot {
    position: relative;
    margin: 0;
    aspect-ratio: 4 / 5;
    border-radius: var(--r-md);
    overflow: hidden;
    background: color-mix(in srgb, var(--fg) 7%, var(--bg));
  }
  .held__shot img {
    position: absolute;
    inset: 0;
    width: 100%;
    height: 100%;
    object-fit: cover;
    display: block;
    opacity: 0;
  }
  .held__shot img:first-child { opacity: 1; }
  /* David portraits sit in a 4:5 frame — keep his face and the table group. */
  .held__shot img[src*="david-listen"] { object-position: 50% 18%; }
  .held__shot img[src*="david-tables"] { object-position: 48% 22%; }
  .held__shot img[src*="david-circle"] { object-position: 50% 28%; }
  /* Landscape cafeteria assembly — keep the crowd, not the empty tables. */
  .held__shot img[src*="david-assembly"] { object-position: 50% 62%; }
  .held__quote { margin: var(--s-6) 0 0; }
  .held__quote blockquote {
    font-family: var(--font);
    font-size: clamp(19px, 2vw, 24px);
    font-weight: 400;
    font-style: normal;
    line-height: 1.35;
    letter-spacing: -0.01em;
    color: var(--fg);
  }
  .held__quote figcaption {
    margin-top: var(--s-3);
    font-size: var(--t-caption);
    line-height: 19.5px;
    color: var(--fg-50);
  }
  .held__flow > .h1 { margin: 0 0 var(--s-8); max-width: 16ch; }
  .held__flow p { color: var(--fg-60); line-height: 1.6; max-width: 58ch; }
  .held__flow p + p { margin-top: var(--s-8); }
  .held__turn {
    color: var(--fg) !important;
    font-size: clamp(21px, 2.4vw, 30px);
    line-height: 1.2 !important;
    letter-spacing: -0.02em;
    max-width: 24ch !important;
  }
  .held__results {
    margin-top: var(--s-14);
    display: grid;
    grid-template-columns: 1fr 1fr;
    column-gap: var(--s-8);
    row-gap: var(--s-3);
  }
  .held__resultsTitle {
    grid-column: 1 / -1;
    white-space: nowrap;
    max-width: none !important;
    font-size: var(--t-micro) !important;
    line-height: 1.4;
    letter-spacing: .14em;
    text-transform: uppercase;
    color: var(--fg-50) !important;
    margin: 0;
  }
  .held__results span {
    display: block;
    font-size: clamp(38px, 5vw, 64px);
    line-height: .92;
    letter-spacing: -0.04em;
    color: var(--fg);
    font-variant-numeric: tabular-nums;
  }
  .held__results p { margin-top: var(--s-2); max-width: 26ch; color: var(--fg-60); }
  .held__flow .cta-row--left { margin-top: var(--s-14); }
}

.story__body p { color: var(--fg-60); }
.story__body p + p { margin-top: var(--s-5); }
/* Section title above the held block — type from .h1, left with wrap content. */
.story__head {
  display: flex;
  align-items: center;
  gap: 0.28em;
  margin: 0 0 var(--s-8);
  text-align: left;
  color: var(--fg);
}
.story__eye {
  width: 0.88em;
  height: 0.88em;
  flex: 0 0 auto;
  display: block;
  color: inherit;
  /* Lashes sit in the top of the viewBox — drop the glyph onto the word. */
  transform: translateY(0.1em);
}
@media (min-width: 900px) {
  .story__head { margin: 0 0 var(--s-12); }
}
.story__turn {
  color: var(--fg) !important;
  font-size: clamp(18px, 2vw, 22px);
  line-height: 1.4;
  padding-block: var(--s-3);
  margin: 0;
}

/* Desktop: vertical beats; open card keeps the old two-column split. */
.story__rail {
  display: grid;
  gap: var(--s-12);
}
.story__card { margin: 0; min-width: 0; }
.story__card--open {
  position: relative;
  display: grid;
  gap: var(--s-8);
  overflow: hidden;
  background: #000000;
  color: #FFFFFF;
  --fg: #FFFFFF;
  --bg: #000000;
  border-radius: var(--r-md);
  padding: var(--s-8);
}
.story__card--open .story__body p {
  color: color-mix(in srgb, #FFFFFF 82%, transparent);
}
@media (min-width: 900px) {
  .story__card--open {
    grid-template-columns: 1fr 1fr;
    gap: var(--s-12) var(--s-14);
    align-items: start;
    padding: var(--s-12);
  }
  .story__card--cta .cta-row { margin-top: 0; }
  .story__card--results .results { margin-top: 0; }
}

/* Phone: swipeable story beats — same copy, one card per narrative step. */
@media (max-width: 899px) {
  .story__rail {
    display: flex;
    gap: var(--s-5);
    width: 100vw;
    max-width: 100vw;
    margin-left: calc(50% - 50vw);
    padding-inline: var(--pad);
    padding-bottom: var(--s-2);
    overflow-x: auto;
    overflow-y: hidden;
    overscroll-behavior-x: contain;
    scroll-snap-type: x mandatory;
    scroll-padding-inline: var(--pad);
    scrollbar-width: none;
    -webkit-overflow-scrolling: touch;
  }
  .story__rail::-webkit-scrollbar { display: none; }
  /* One footprint for every beat: equal flex basis + stretch so open, photo,
     quote, results, and CTA share width and height. Overflow clips long copy
     rather than letting one tile set a tall empty shell for the rest. */
  .story__rail { align-items: flex-start; }
  .story__card {
    flex: 0 0 min(320px, 84vw);
    width: min(320px, 84vw);
    scroll-snap-align: start;
    display: flex;
    flex-direction: column;
    justify-content: flex-end;
    gap: var(--s-4);
    padding: var(--s-5);
    border-radius: var(--r-md);
    /* White tiles on the peach Bartow ground — not a grey mix of --fg. */
    background: #FFFFFF;
    color: #130F0C;
    --fg: #130F0C;
    --bg: #FFFFFF;
    box-shadow: inset 0 0 0 1px color-mix(in srgb, #130F0C 8%, transparent);
    height: var(--tile-h);
    min-height: var(--tile-h);
    overflow: hidden;
    box-sizing: border-box;
  }
  .story__card--open {
    display: flex;
    flex-direction: column;
    justify-content: flex-start;
    /* Override the white tile paint from sibling cards */
    background: #000000;
    color: #FFFFFF;
    --fg: #FFFFFF;
    --bg: #000000;
    box-shadow: none;
    padding: var(--s-6);
  }
  .story__card--open .story__tail {
    margin-top: auto;
    display: grid;
    gap: var(--s-4);
  }
  .story__card--open .h1 { font-size: clamp(28px, 8vw, 40px); }
  .story__card--turn { justify-content: center; }
  .story__card--turn .story__turn {
    font-size: clamp(22px, 6vw, 28px);
    line-height: 1.3;
    padding-block: 0;
  }
  .story__card--photo { padding: 0; justify-content: stretch; }
  .story__card--photo .photo--story {
    border-radius: var(--r-md);
    width: 100%;
    height: 100%;
    aspect-ratio: auto;
    flex: 1;
  }
  .story__card--photo img[src*="david-listen"] { object-position: 50% 18%; }
  .story__card--photo img[src*="david-tables"] { object-position: 48% 22%; }
  .story__card--photo img[src*="david-circle"] { object-position: 50% 28%; }
  .story__card--photo img[src*="david-assembly"] { object-position: 50% 62%; }
  .story__card--quote { justify-content: center; }
  .story__card--quote .pull--beside blockquote { max-width: none; }
  .story__card--results {
    justify-content: flex-start;
    height: auto;
    min-height: var(--tile-h);
    padding: var(--s-6) var(--s-6);
    gap: var(--s-5);
  }
  .story__card--results .results { margin-top: 0; }
  .story__card--results .results__title {
    margin-bottom: var(--s-5);
    line-height: 1.3;
  }
  .story__card--results .results__grid { gap: var(--s-6); }
  .story__card--results .result { padding-top: var(--s-5); }
  .story__card--results .result__label {
    max-width: none;
    line-height: 1.35;
    margin-top: var(--s-3);
  }
  .story__card--cta {
    align-items: center;
    justify-content: center;
  }
  .story__card--cta .cta-row { margin: 0; }
}

.results { margin-top: var(--s-20); }
.results__title {
  font-size: clamp(20px, 2.4vw, 28px);
  line-height: 1.25;
  letter-spacing: -0.01em;
  color: var(--fg);
  margin-bottom: var(--s-8);
}
.results__grid { display: grid; gap: var(--s-12); }
@media (min-width: 768px) { .results__grid { grid-template-columns: 1fr 1fr; gap: var(--s-14); } }

.result { border-top: 1px solid var(--rule); padding-top: var(--s-6); }
.result__num {
  font-size: clamp(48px, 8vw, 96px);
  line-height: 0.95;
  letter-spacing: -0.04em;
  color: var(--fg);
  font-variant-numeric: tabular-nums;
}
.result__label {
  margin-top: var(--s-4);
  font-size: var(--t-h2);
  line-height: 28px;
  color: var(--fg-60);
  max-width: 30ch;
}

/* ── FAQ ────────────────────────────────────────────────────────────────── */

.faq .h1 { margin-bottom: var(--gap-title-block); }
.faq__list { border-top: 1px solid var(--rule); }

.qa { border-bottom: 1px solid var(--rule); }
.qa summary {
  list-style: none;
  cursor: pointer;
  padding-block: var(--s-6);
  min-height: 44px;
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: var(--s-8);
  font-size: clamp(17px, 1.9vw, 21px);
  line-height: 1.35;
  transition: opacity .2s var(--ease);
}
.qa summary::-webkit-details-marker { display: none; }
.qa summary:hover { opacity: .7; }
.qa summary i { position: relative; width: 16px; height: 16px; flex: none; }
.qa summary i::before,
.qa summary i::after {
  content: ""; position: absolute; background: var(--fg);
  transition: transform .3s var(--ease);
}
.qa summary i::before { left: 0; right: 0; top: 7.5px; height: 1px; }
.qa summary i::after  { top: 0; bottom: 0; left: 7.5px; width: 1px; }
.qa[open] summary i::after { transform: scaleY(0); }
.qa__body { padding-bottom: var(--s-8); }
/* full strength, not the 60% every other secondary paragraph takes: on the
   FAQ's orange a dimmed white drops close to the ground it sits on */
.qa__body p { color: var(--fg); line-height: 1.6; max-width: 68ch; }
.qa[open] .qa__body { animation: fadeUp .35s var(--ease); }
@keyframes fadeUp { from { opacity: 0; transform: translateY(6px); } }
@media (prefers-reduced-motion: reduce) { .qa[open] .qa__body { animation: none; } }

/* ── Forms ──────────────────────────────────────────────────────────────── */

.contact__inner { display: grid; gap: var(--s-14); align-items: start; }
@media (min-width: 900px) { .contact__inner { grid-template-columns: 0.85fr 1.15fr; } }
/* one section, two asks: the switch sits above, and each pane is the old
   copy | form grid */
.contact__inner--tabs { display: block; }
.contact__inner--tabs .pane { display: grid; gap: var(--s-14); align-items: start; }
@media (min-width: 900px) { .contact__inner--tabs .pane { grid-template-columns: 0.85fr 1.15fr; } }
.contact__inner--tabs .pane[hidden] { display: none; }
.contact__inner--tabs .pane.is-shown { animation: paneIn .35s var(--ease); }
@keyframes paneIn { from { opacity: 0; transform: translateY(6px); } }
.anchor { display: block; position: relative; top: -120px; visibility: hidden; }
/* the segmented switch: a dark pill slides to the side that's open */
.seg {
  position: relative; display: inline-grid; grid-auto-flow: column; grid-auto-columns: 1fr;
  padding: 4px; margin-bottom: var(--s-12); border-radius: var(--r-pill);
  background: color-mix(in srgb, var(--fg) 8%, transparent);
}
.seg button {
  position: relative; z-index: 1; padding: 12px 24px; border: 0; border-radius: var(--r-pill);
  background: none; color: var(--fg); font: inherit; font-size: 16px; white-space: nowrap; cursor: pointer;
  transition: color .25s var(--ease);
}
.seg button[aria-selected="true"] { color: #FFFFFF; }   /* white on the dark pill whatever the ground is mid-fade */
.seg button:focus-visible { outline: 2px solid var(--fg); outline-offset: 2px; }
.seg__thumb {
  position: absolute; top: 4px; bottom: 4px; left: 4px; width: calc(50% - 4px);
  border-radius: var(--r-pill); background: var(--fg);
  transition: translate .35s cubic-bezier(.3, 1.3, .5, 1);
}
.seg[data-i="1"] .seg__thumb { translate: 100% 0; }
@media (prefers-reduced-motion: reduce) { .seg__thumb { transition: none; } .contact__inner--tabs .pane.is-shown { animation: none; } }

/* the volunteer form explains the role before asking for details */
.contact__lede {
  margin-top: var(--s-6);
  color: var(--fg-60);
  line-height: 1.6;
  max-width: 46ch;
}

.form { display: grid; gap: var(--s-6); }
@media (min-width: 600px) { .form { grid-template-columns: 1fr 1fr; } }
.field { display: grid; gap: var(--s-2); }
.field--full { grid-column: 1 / -1; }

.field label,
.signup label {
  font-size: var(--t-caption);
  line-height: 19.5px;
  color: var(--fg-60);
}

.field input,
.field textarea,
.signup input {
  font-family: var(--font);
  font-size: var(--t-body);
  line-height: 24px;
  font-weight: 400;
  color: var(--fg);
  appearance: none;
  -webkit-appearance: none;
  /* Slight ink wash, not opaque white — ground color shows through as it shifts. */
  background: color-mix(in srgb, var(--fg) 8%, transparent);
  border: 1px solid var(--rule-strong);
  border-radius: var(--r-sm);
  padding: var(--s-3) var(--s-4);
  min-height: 44px;
  width: 100%;
  transition: border-color .2s var(--ease);
}
.field textarea { resize: vertical; }
.field input:focus,
.field textarea:focus,
.signup input:focus { outline: none; border-color: var(--fg); }
.field [aria-invalid="true"],
.signup [aria-invalid="true"] { border-color: var(--accent-yellow); }

.form__actions { display: flex; flex-wrap: wrap; align-items: center; gap: var(--s-6); }
.form__status { font-size: var(--t-caption); line-height: 19.5px; color: var(--fg-60); }
.form__status[data-error] { color: var(--accent-red); }

.social { display: flex; gap: var(--s-4); }
.social a {
  display: grid;
  place-content: center;
  width: 40px;
  height: 40px;
  /* Same token as Shop: global `a { color: var(--fg) }` would stick on the
     reveal foot, where --fg is pinned white while `color` keyframes. */
  color: inherit;
}
.social svg {
  width: 22px;
  height: 22px;
  color: inherit;
}
.social svg :not(.fill) { fill: none; stroke: currentColor; stroke-width: 1.6; }
.social svg .fill { fill: currentColor; stroke: none; }

/* ── Phone cord between two sections (phone-cord.js) ───────────────────────
   An overlay in page coordinates; it never takes clicks itself — the script
   grabs the cord from document pointer events when you press near it. */
.cordlayer { position: absolute; z-index: 40; pointer-events: none; }
.cordlayer canvas { display: block; }
html.is-cord-hover, html.is-cord-hover * { cursor: grab !important; }
html.is-cord-drag, html.is-cord-drag * { cursor: grabbing !important; user-select: none !important; }

/* ── TV static under the footer (tv-static.js) ───────────────────────────
   A full-width band of live static meeting the bottom of the page. Pixels
   stay hard when the small canvas is scaled up. */
.tvmark {
  position: relative;
  z-index: 2;
  /* the page's gutter under the footer bar; Donate and back to top lift
     above the bar instead (.footdock.on-foot), so no extra room is held */
  margin-top: var(--pad);
  height: clamp(160px, 24vw, 340px);
  overflow: hidden;
  cursor: crosshair;
  background: #111;
}
/* closed until forced open (tv-static.js sets --tv-h); the art keeps its
   full height inside so the canvas never resizes mid-reveal */
.tvmark { height: var(--tv-h, 0px); }
.tvmark.is-shut .tvmark__art { visibility: hidden; }
.tvmark__art { position: absolute; left: 0; right: 0; top: 0; width: 100%; height: clamp(160px, 24vw, 340px); }
.footer:has(.tvmark) { padding-bottom: 0 !important; }
.tvmark canvas { display: block; width: 100%; height: 100%; image-rendering: pixelated; }

/* Interface-sounds mute toggle (ui-sounds.js), beside the socials. The slash
   draws itself across the speaker when muted; the sound waves fade out. */
.soundtoggle {
  display: grid; place-content: center; width: 40px; height: 40px; padding: 0;
  border: 0; border-radius: 10px; background: none; color: inherit; cursor: pointer;
  transition: background-color .15s var(--ease), transform .1s var(--ease);
}
.soundtoggle:hover { background: color-mix(in srgb, currentColor 10%, transparent); }
.soundtoggle:active { transform: scale(.96); }
.soundtoggle:focus-visible { outline: 2px solid currentColor; outline-offset: 2px; }
.soundtoggle svg { width: 22px; height: 22px; fill: none; stroke: currentColor; stroke-width: 1.6; stroke-linecap: round; stroke-linejoin: round; overflow: visible; }
.soundtoggle .sound-slash { stroke-dasharray: 1; stroke-dashoffset: 1; transition: stroke-dashoffset .25s var(--ease); }
.soundtoggle mask .sound-slash { stroke: black; stroke-width: 4; }
.soundtoggle .sound-wave { transition: opacity .2s var(--ease), transform .25s var(--ease); transform-origin: 11px 12px; }
.soundtoggle.is-muted .sound-slash { stroke-dashoffset: 0; }
.soundtoggle.is-muted .sound-wave { opacity: 0; transform: scale(.7); }
@media (prefers-reduced-motion: reduce) { .soundtoggle, .soundtoggle * { transition: none !important; } }

/* ── Footer ─────────────────────────────────────────────────────────────── */

/* Default in-flow foot (donate + simple pages). Homepage reveal is
   .footer--reveal below — scoped so fixed positioning does not leak. */
.footer {
  position: relative;
  z-index: 1;
  border-top: 0;
  display: flex;
  flex-direction: column;
  justify-content: flex-end;
  margin-top: 0;
  /* Bottom matches .wrap's horizontal gutter (--pad) so the logo/socials
     sit in an even frame. Top stays the larger section inset. */
  padding: var(--s-14) 0 var(--pad);
  background: transparent;
  color: #FFFFFF;
  --fg: #FFFFFF;
  --bg: #130F0C;
  /* paint() rewrites body --fg from the white sheet; inherited --fg-60
     would stay dark-on-charcoal. Pin the muted on-dark mix locally. */
  --fg-60: color-mix(in srgb, #FFFFFF 62%, transparent);
  overflow: visible;
}
/* Keep body on the live --bg token (paint()). A pinned charcoal here made
   iOS Safari’s bottom chrome band stay black while .ground / content colour
   stopped above it. Footer charcoal still arrives via the ground ladder. */
body:has(.footer--reveal) {
  background: var(--bg);
}
@media (min-width: 1024px) {
  .footer { padding: var(--s-20) 0 var(--pad); }
}

/* isolation:isolate still traps nav/Donate backdrop-filter. Main stays above
   the footer; no page-sized wash lives behind it. The foot's own box is
   pulled 140px under the sheet so only the corner wedges show the bands. */
body:has(.footer--reveal) main {
  position: relative;
  z-index: 2;
  isolation: isolate;
  margin-bottom: 0;
}

.footer--reveal {
  position: relative;
  z-index: 1;
  /* Clip the scrolling strip to this box — never the document. */
  overflow: hidden;
  /* Pull the clipped box up under the sheet radius (wedges only). */
  margin-top: -140px;
  /* Visible top inset doubled (--s-8 → 64px). 140px is the sheet tuck. */
  padding-top: calc(var(--s-8) * 2 + 140px);
  background: #000000;
  color: #FFFFFF;
  --fg: #FFFFFF;
  --bg: #000000;
  --fg-60: color-mix(in srgb, #FFFFFF 62%, transparent);
}
/* tests/footers.html still uses these for its wash option */
@keyframes footerWashUp {
  from { transform: translateY(0); }
  to { transform: translateY(-50%); }
}
/* Same 34s linear clock as the strip. Window slides down the gradient
   (strip moves up): black / blue / olive (white) → tan / lavender (ink)
   → orange / black (white). */
@keyframes footerInk {
  0%, 36% { color: #FFFFFF; }
  42%, 68% { color: #130F0C; }
  74%, 100% { color: #FFFFFF; }
}
@media (min-width: 1024px) {
  .footer--reveal { padding-top: calc(var(--s-8) * 2 + 140px); }
}

.footer__inner {
  display: grid;
  gap: var(--s-14);
  width: 100%;
  overflow: visible;
  position: relative;
  z-index: 2;
}
@media (min-width: 1024px) {
  .footer__inner { gap: var(--s-20); }
}

/* Digital-locker copy stacked above the Mailchimp pill — single column. */
.footer__signup {
  display: grid;
  gap: var(--s-8);
  align-items: stretch;
  justify-items: start;
  min-width: 0;
}
.footer__signup > * { min-width: 0; max-width: 100%; }
.footer__signup .signup { width: min(100%, 36rem); }
/* Wide screens: copy left, email pill on the right, tops aligned. */
@media (min-width: 900px) {
  .footer__signup {
    grid-template-columns: minmax(0, 1fr) minmax(0, 32rem);
    align-items: start;
    column-gap: var(--s-12);
  }
  .footer__signup .signup { justify-self: end; width: 100%; }
}
.footer__copy .h2-lg { max-width: 28ch; }
.footer__aka {
  margin-top: var(--s-4);
  font-size: var(--t-caption);
  line-height: 19.5px;
  color: var(--fg-60);
}
@media (max-width: 599px) { .footer__aka { margin-top: var(--s-2); } }

/* Inherit the footer's white ink. Signup stays a white pill + charcoal button.
   Re-declare on the <a> / svg — inherited color on .social was overwritten
   by `a { color: var(--fg) }`. */
.footer--reveal .footer__copy,
.footer--reveal .footer__brand,
.footer--reveal .footer__links,
.footer--reveal .footer__links a,
.footer--reveal .social,
.footer--reveal .social a,
.footer--reveal .social svg {
  color: inherit;
}
.footer--reveal .footer__aka {
  color: color-mix(in srgb, currentColor 72%, transparent);
}
.footer--reveal .signup {
  isolation: isolate;
  position: relative;
  z-index: 3;
}
.footer--reveal .signup__field {
  background: #FFFFFF;
}
.footer--reveal .signup__field input {
  color: #130F0C;
}
.footer--reveal .signup__field input::placeholder {
  color: color-mix(in srgb, #130F0C 45%, transparent);
}
.footer--reveal .signup__go {
  background: #130F0C;
  color: #FFFFFF;
}
.footer--reveal .form__status {
  color: inherit;
}
.footer--reveal .form__status[data-error] {
  color: var(--accent-red);
}
.footer--reveal .footer__links a {
  opacity: 1;
}
.footer--reveal .footer__links a:hover { opacity: .7; }

/* Wordmark + socials under the signup. */
.footer__bar {
  display: flex;
  /* when the row wraps on a phone, the wordmark takes the bottom line —
     it always sits in the footer's bottom-left corner */
  /* one row, wordmark left and Shop + socials right, at every width */
  flex-wrap: nowrap;
  /* bottom-aligned: the wordmark's outline, the link text and the icon
     glyphs all end on one line */
  align-items: flex-end;
  justify-content: space-between;
  gap: var(--s-4);
  width: 100%;
}
/* the icon buttons carry 9px of tap padding under the glyph, the wordmark
   link 8px; let it hang so the visible shapes share one bottom line */
.footer__bar .social,
.footer__bar .soundtoggle { margin-bottom: -9px; }
.footer__bar .footer__brand { margin-bottom: -8px; }
/* the narrowest phones: a smaller wordmark and tighter icons keep the row whole */
@media (max-width: 360px) {
  .footer__bar .logo--footer { height: 26px; width: calc(26px * 351.4 / 92.3); }
  .footer__bar .social { gap: var(--s-2); }
}
.footer__bar-end {
  display: flex;
  align-items: flex-end;
  flex-wrap: nowrap;
  flex: 0 0 auto;
  justify-content: flex-end;
  gap: var(--s-4);
  width: auto;
  /* parks Shop + socials at the right end of the row */
  margin-left: auto;
}
/* two links now (Studio, Shop): space them like the social icons beside */
.footer__links { display: flex; align-items: center; gap: var(--s-6); }
.footer__links a {
  font-size: 15px;
  line-height: 1;
  letter-spacing: -0.01em;
  color: inherit;
  opacity: .72;
  transition: opacity .25s var(--ease);
}
.footer__links a:hover { opacity: 1; }
/* Phones and tablets (where the sticky Donate chip lives): two rows.
   Donate on the left with Wiki / Studio / Shop on the right, then the
   wordmark with the socials. The sticky chip fades as this Donate arrives
   (.footdock.is-lifted), so it seems to settle into the footer. */
.footer__donate { display: none; }
@media (max-width: 899px) {
  .footer__bar {
    display: grid;
    grid-template-columns: auto 1fr auto auto;
    grid-template-areas: "donate links links links" "brand . social sound";
    align-items: end;
    row-gap: var(--s-6);
    column-gap: var(--s-2);
  }
  .footer__bar-end { display: contents; }
  .footer__donate {
    grid-area: donate; justify-self: start; align-self: center;
    display: inline-flex; align-items: center; min-height: 40px; padding: 0 var(--s-6);
    border-radius: var(--r-pill); font-size: var(--t-body); line-height: 1; color: #FFFFFF;
    background: color-mix(in srgb, #FFFFFF 10%, transparent);
    box-shadow: inset 0 0 0 1px color-mix(in srgb, #FFFFFF 10%, transparent);
  }
  .footer__bar .footer__links { grid-area: links; justify-self: end; align-self: center; }
  .footer__bar .footer__brand { grid-area: brand; }
  .footer__bar .social { grid-area: social; }
  .footer__bar .soundtoggle { grid-area: sound; }
  .footdock.is-lifted .sticky-donate { opacity: 0; transform: translateY(12px); pointer-events: none; }
}

/* Opaque white for Ramp (covers sticky charcoal foot). Soft top fade lets
   paint()'s lavender→white crossfade show through — no hard lilac cut.
   Bottom radius + charcoal shadow (follows the curve) kills the light AA
   hairline under the corners without a full-width rectangular edge. */
.contact--sheet {
  position: relative;
  z-index: 2;
  /* clear over the page ground (lavender by the time it's read) and solid
     only along the foot, where its rounded corners lift off the footer: a
     transparent → white fade at the top lay over the FAQ's orange */
  background: linear-gradient(
    to bottom,
    transparent 0,
    transparent calc(100% - 160px),
    #FFFFFF calc(100% - 160px)
  );
  color: #130F0C;
  --fg: #130F0C;
  --bg: #FFFFFF;
  border-top: 0;
  border-radius: 0 0 48px 48px;
  padding-block: var(--s-20);
  margin-bottom: 0;
}
@media (min-width: 1024px) {
  .contact--sheet {
    border-radius: 0 0 64px 64px;
    padding-block: var(--s-27);
  }
}
body:has(.footer--reveal) .contact--sheet {
  box-shadow: none;
}
.contact--sheet .contact__lede,
.contact--sheet .field label { color: color-mix(in srgb, #130F0C 62%, transparent); }
.contact--sheet > .wrap { position: relative; z-index: 1; }
/* desktop: Whisper.exe hangs a fixed distance under whichever intro is
   open (script.js moves it into that pane's copy), so the longer Volunteer
   intro can't run onto it and the section doesn't grow to make room */
@media (min-width: 900px) {
  .contact--sheet .contact__copy { position: relative; }
  .contact__copy > .sprinkle--win95 { top: calc(100% + var(--s-12)); bottom: auto; left: var(--s-12); }
}

/* Sheet pins --fg dark, but paint() keeps rewriting body --fg to white on the
   charcoal ground — so var(--rule-strong) on inputs was ~white-on-white. Pin
   the field chrome to the sheet ink so Name/Email/Message stay visible. */
.contact--sheet .field input,
.contact--sheet .field textarea {
  color: #130F0C;
  border-color: color-mix(in srgb, #130F0C 28%, transparent);
  background: color-mix(in srgb, #130F0C 8%, transparent);
}
.contact--sheet .field input:focus,
.contact--sheet .field textarea:focus {
  border-color: #130F0C;
}
.contact--sheet .field input::placeholder,
.contact--sheet .field textarea::placeholder {
  color: color-mix(in srgb, #130F0C 40%, transparent);
}
/* the icons carry their own 8px of optical padding, so the row is pulled back by
   that much to keep the right edge flush with the page's gutter */
.footer__bar .social { margin-right: -8px; }

.signup { display: grid; gap: var(--s-3); max-width: 520px; }
.signup__row { display: flex; flex-wrap: wrap; gap: var(--s-3); }
.signup input { flex: 1 1 240px; width: auto; }
/* the honeypot has to be in the DOM and reachable by the post, just never seen */
.signup__bot { position: absolute; left: -5000px; }

/* ── Signup pill (footer newsletter) ────────────────────────────────────── */

.signup--lg { max-width: none; width: 100%; min-width: 0; }

/* Single pill with the button sunk into its right end, rather than a field and a
   button sitting side by side. No stroke — the light fill is the field. */
.signup__field {
  display: flex;
  align-items: center;
  gap: var(--s-2);
  width: 100%;
  max-width: 100%;
  /* Grid/flex kids default to min-width:auto (content size). The placeholder +
     Sign up button were wider than the phone column, so the pill ran past the
     gutter and html's overflow-x:clip ate the right end. */
  min-width: 0;
  /* Right inset is two steps wider than top/bottom so the Sign up pill's
     right curve matches the top/bottom gap optically (equal padding reads
     cramped against the outer rim). Left stays a step wider than top —
     the email text also carries its own padding-inline. */
  padding: var(--s-2) var(--s-4) var(--s-2) var(--s-3);
  border: none;
  outline: none;
  box-shadow: none;
  border-radius: var(--r-pill);
  background: rgba(0, 0, 0, 0);
}

/* the input gives up its own chrome — the wrapper is the field now */
.signup__field input {
  flex: 1 1 0;
  width: auto;
  min-width: 0;
  min-height: 40px;
  border: 0;
  border-radius: 0;
  background: none;
  /* the placeholder is this field's label, so it is set at reading size and given
     a proper gutter rather than being tucked against the pill's edge */
  font-size: var(--t-h2);
  padding-inline: var(--s-5);
}
.signup__field input:focus { outline: none; border: 0; }
.signup__field input::placeholder { color: var(--fg-35); }
/* the wrapper shows the error state, so the input must not draw a second one */
.signup__field input[aria-invalid="true"] { border: 0; }

.signup__go {
  flex: 0 0 auto;
  max-width: 100%;
  font: inherit;
  font-size: var(--t-body);
  line-height: 22px;
  min-height: 40px;
  padding: calc(var(--s-2) + 1px) var(--s-6) calc(var(--s-2) - 1px);   /* the face sits its letters a hair high: 1px more on top centres them */
  border: 0;
  border-radius: var(--r-pill);
  background: var(--fg);
  color: var(--bg);
  cursor: pointer;
  transition: opacity .18s var(--ease);
}
.signup__go:hover { opacity: .8; }
.signup__go:active { opacity: .6; }
.signup__go:disabled { opacity: .55; cursor: wait; }
@media (max-width: 899px) {
  /* Keep the whole pill inside the wrap gutter — never clip the right rim. */
  .footer .signup,
  .footer .signup__field { min-width: 0; max-width: 100%; }
  .footer .signup__field {
    padding: var(--s-2) var(--s-4) var(--s-2) var(--s-2);
    gap: var(--s-2);
  }
  .footer .signup__field input {
    padding-inline: var(--s-3);
    font-size: var(--t-body);
  }
  .footer .signup__go { padding-inline: var(--s-5); }
}


/* ── Quote ticker (partner) ─────────────────────────────────────────────── */

/* Two copies of the line sit end to end. The track is max-content; translating
   it by -50% lands the second copy exactly where the first started, so the
   loop has no seam. Type matches the live split-head h2 (.h1): Heros, not the
   Garamond the lined card used. */
.qticker {
  margin: var(--s-20) calc(var(--pad) * -1) 0;
  overflow: hidden;
  padding-block: var(--s-6);
  mask-image: linear-gradient(90deg, transparent, #000 6%, #000 94%, transparent);
}
.qticker__track {
  display: flex;
  width: max-content;
  will-change: transform;
  animation: qtickerRoll 63.6s linear infinite;
}
.qticker__item {
  flex: none;
  margin: 0;
  padding-right: 1.2em;
  white-space: nowrap;
  font-family: var(--font);
  /* same size as Gary's quote just above (.cine__quote) */
  font-size: clamp(26px, 2.8vw, 42px);
  line-height: 1.12;
  letter-spacing: -.035em;
  font-weight: 400;
}
.qticker__who { color: var(--fg-50); }

@keyframes qtickerRoll {
  to { transform: translateX(-50%); }
}

@media (prefers-reduced-motion: reduce) {
  .qticker {
    margin-inline: 0;
    overflow: visible;
    mask-image: none;
  }
  .qticker__track { animation: none; display: block; width: auto; }
  .qticker__item {
    white-space: normal;
    padding-right: 0;
    line-height: 1.2;
    text-wrap: pretty;
  }
  .qticker__item + .qticker__item { display: none; }
  .qticker__who {
    display: block;
    margin-top: var(--s-4);
    color: var(--fg-50);
  }
}

/* ── Donate page ────────────────────────────────────────────────────────── */

/* Standalone page, so it gets a fixed dark ground rather than joining the
   homepage's scrolling ladder — a donor who arrived here has already decided,
   and colour changing under the form would only be noise. */
/* Light page, on the site's paper ground. The --fg mixes are restated here
   because they resolve where they are declared, not where they are used. */
.donatepage {
  --fg: #130F0C;
  --bg: #EBEEF3;
  --fg-60: color-mix(in srgb, #130F0C 62%, transparent);
  --fg-50: color-mix(in srgb, #130F0C 52%, transparent);
  --fg-35: color-mix(in srgb, #130F0C 35%, transparent);
  --rule: color-mix(in srgb, #130F0C 16%, transparent);
  --rule-strong: color-mix(in srgb, #130F0C 32%, transparent);
  background: var(--bg);
  color: var(--fg);
}
.donatepage .donate.section { border-top: 0; }

.donate__inner { display: grid; gap: var(--s-14); align-items: start; }
@media (max-width: 899px) {
  /* Widget first, closer to the header — the stacked copy was pushing Donorbox
     below the fold. Keep the lede; just don't make people scroll to give. */
  .donatepage .donate.section {
    padding-top: var(--s-6);
    padding-bottom: var(--s-14);
    border-top: 0;
  }
  .donate__inner { gap: var(--s-8); }
  .donate__form { order: -1; }
}
@media (min-width: 900px) {
  /* the form leads on the right; the argument sits beside it, not above it, so
     both are on screen together without scrolling. Column is the 425px widget
     plus a 1px white ring on the wrapper. */
  .donate__inner { grid-template-columns: 1fr minmax(250px, 427px); gap: var(--s-20); }
}

.donate__lede {
  margin-top: var(--s-6);
  font-size: clamp(16px, 1.7vw, 20px);
  line-height: 1.55;
  color: var(--fg-60);
  max-width: 46ch;
}
.donate__lede--follow { margin-top: var(--s-6); }
.donate__helps {
  margin-top: var(--s-5);
  max-width: 46ch;
  font-size: clamp(16px, 1.7vw, 20px);
  line-height: 1.45;
  color: var(--fg-60);
  list-style-type: disc;
  list-style-position: outside;
  padding-inline-start: 1.25em;
}
.donate__helps li + li { margin-top: var(--s-2); }
.donate__helps li::marker { color: currentColor; }

.donate__facts {
  display: grid;
  gap: var(--s-8);
  margin-top: var(--s-14);
  padding-top: var(--s-8);
  border-top: 1px solid var(--rule);
}
.donate__facts li { display: grid; gap: var(--s-1); }
.donate__num {
  font-size: clamp(32px, 4vw, 44px);
  line-height: 1;
  letter-spacing: -.02em;
  font-variant-numeric: tabular-nums;
}
.donate__lab { font-size: var(--t-caption); line-height: 19.5px; color: var(--fg-60); max-width: 34ch; }

.donate__note {
  margin-top: var(--s-12);
  font-size: var(--t-micro);
  line-height: 1.5;
  color: var(--fg-35);
  max-width: 44ch;
}
.donate__note a { text-decoration: underline; }

/* Donorbox's card is ~8px rounded inside a square iframe. Clipping this
   wrapper at --r-md (16px) left dark page-ground in the four corners, between
   the widget and the frame. Clip at --r-sm to match the card, and keep a 1px
   white sheet around the iframe (we cannot style inside it) so the edge reads
   even. Height is set by widget.js. */
.donate__form {
  width: 100%;
  max-width: 427px;
  justify-self: start;
}

/* Donorbox's frame, unchanged inside; we can only frame it */
.give__frame {
  padding: 1px;
  border-radius: 16px;
  overflow: hidden;
  background: #FFFFFF;
  box-shadow: 0 20px 50px -24px rgba(0, 0, 0, .6);
}
.give__frame iframe {
  display: block;
  width: 100%;
  max-width: 425px;
  border: 0;
  border-radius: 15px;
}

.donate-other { border-top: 1px solid var(--rule); }
/* Same foot as the homepage: the last section is the sheet, its rounded
   bottom lifting off the black footer tucked underneath. */
.donatepage .donate-other {
  position: relative;
  z-index: 2;
  background: var(--bg);
  border-radius: 0 0 48px 48px;
}
@media (min-width: 1024px) {
  .donatepage .donate-other { border-radius: 0 0 64px 64px; }
}
.donatepage .path { background: #FFFFFF; }
.donate-other .h2-lg { margin-bottom: var(--s-12); }

/* ── Schools page ───────────────────────────────────────────────────────── */

/* The page a principal, counselor or funder is sent to. It borrows the Donate
   page's fixed dark ground and chrome, and reads top to bottom in the order a
   school asks: what is it, how does a season run, who does what, what do we
   get, does it work, how do we start. */
.schoolspage .section + .section { border-top: 1px solid var(--rule); }
.schools-hero.section { border-top: 0; }

.schools__kicker { font-size: var(--t-caption); line-height: 19.5px; color: var(--fg-60); }
.schools__title { margin-top: var(--s-4); max-width: 14ch; }
.schools__lede {
  margin-top: var(--s-6);
  font-size: clamp(16px, 1.7vw, 20px);
  line-height: 1.55;
  color: var(--fg-60);
  max-width: 52ch;
}
.schools__ctas { display: flex; flex-wrap: wrap; gap: var(--s-3); margin-top: var(--s-8); }
.schoolspage .schools__sub { margin-top: var(--s-20); }   /* the Schools page only — the homepage uses .schools__sub too */
.schools__note { margin-top: var(--s-8); font-size: var(--t-caption); line-height: 1.5; color: var(--fg-60); max-width: 60ch; }
.schools__more { margin-top: var(--s-8); color: var(--fg-60); line-height: 1.6; max-width: 60ch; }
.schools__more a,
.schools-kit a,
.schoolspage .contact__lede a { text-decoration: underline; text-underline-offset: 3px; }

/* Answers the team still owes, marked loudly so none ships as if it were
   copy. Remove the class (and the brackets) as each one is filled in. */
.tbd {
  color: #FFD84D;
  background: color-mix(in srgb, #FFD84D 14%, transparent);
  border-radius: 4px;
  padding: 0 .25em;
  box-decoration-break: clone;
  -webkit-box-decoration-break: clone;
}

.glance {
  display: grid;
  gap: var(--s-6) var(--s-8);
  margin-top: var(--s-8);
}
@media (min-width: 600px) { .glance { grid-template-columns: 1fr 1fr; } }
@media (min-width: 900px) { .glance { grid-template-columns: repeat(3, 1fr); } }
.glance div { display: grid; gap: var(--s-1); padding-top: var(--s-4); border-top: 1px solid var(--rule); }
.glance dt { font-size: var(--t-caption); line-height: 19.5px; color: var(--fg-60); }
.glance dd { margin: 0; font-size: clamp(18px, 2vw, 22px); line-height: 1.35; }

.themes { display: grid; gap: var(--s-3); margin-top: var(--s-12); }
@media (min-width: 600px) { .themes { grid-template-columns: 1fr 1fr; } }
@media (min-width: 900px) { .themes { grid-template-columns: repeat(3, 1fr); } }
.themes li {
  display: grid;
  gap: var(--s-1);
  padding: var(--s-6);
  border-radius: var(--r-md);
  background: color-mix(in srgb, var(--fg) 7%, var(--bg));
}
.themes__no { font-size: var(--t-caption); line-height: 19.5px; color: var(--fg-60); }
.themes b { font-weight: 400; font-size: clamp(22px, 2.4vw, 28px); line-height: 1.2; }
.themes__shift { color: var(--fg-60); }

/* numbered rows: the counter is the step number, so the markup stays a plain list */
.runs { counter-reset: run; margin-top: var(--s-8); border-top: 1px solid var(--rule); }
.runs li {
  counter-increment: run;
  display: grid;
  grid-template-columns: 3ch 1fr;
  gap: var(--s-1) var(--s-4);
  padding-block: var(--s-5);
  border-bottom: 1px solid var(--rule);
}
.runs li::before {
  content: counter(run, decimal-leading-zero);
  grid-row: span 2;
  color: var(--fg-60);
  font-variant-numeric: tabular-nums;
  line-height: 1.5;
}
.runs b { font-weight: 400; font-size: clamp(18px, 2vw, 22px); line-height: 1.35; }
.runs span { color: var(--fg-60); line-height: 1.55; max-width: 60ch; }
@media (min-width: 900px) {
  .runs li { grid-template-columns: 3ch minmax(200px, 1fr) 2fr; align-items: baseline; }
  .runs li::before { grid-row: auto; }
}

.roles { display: grid; gap: var(--s-6); margin-top: var(--s-12); }
@media (min-width: 768px) { .roles { grid-template-columns: 1fr 1fr; } }
.roles ul { color: var(--fg-60); line-height: 1.55; list-style: disc; padding-inline-start: 1.1em; }
.roles li + li { margin-top: var(--s-2); }

.kit { margin-top: var(--s-8); border-top: 1px solid var(--rule); }
.kit li {
  display: grid;
  gap: var(--s-1) var(--s-8);
  padding-block: var(--s-5);
  border-bottom: 1px solid var(--rule);
}
@media (min-width: 900px) { .kit li { grid-template-columns: minmax(240px, 1fr) 2fr; align-items: baseline; } }
.kit b { font-weight: 400; font-size: clamp(18px, 2vw, 22px); line-height: 1.35; }
.kit span { color: var(--fg-60); line-height: 1.55; max-width: 60ch; }

.schools__facts { margin-top: var(--s-8); }
@media (min-width: 600px) { .schools__facts { grid-template-columns: 1fr 1fr; } }
@media (min-width: 900px) { .schools__facts { grid-template-columns: repeat(4, 1fr); } }
.schools__quote { margin-top: var(--s-14); max-width: 36ch; }
.schools__quote p { font-size: clamp(22px, 2.6vw, 30px); line-height: 1.3; letter-spacing: -0.01em; }
.schools__quote footer { margin-top: var(--s-4); font-size: var(--t-caption); color: var(--fg-60); }

.field select {
  font-family: var(--font);
  font-size: var(--t-body);
  line-height: 24px;
  color: var(--fg);
  appearance: none;
  -webkit-appearance: none;
  background: color-mix(in srgb, var(--fg) 8%, transparent);
  border: 1px solid var(--rule-strong);
  border-radius: var(--r-sm);
  padding: var(--s-3) var(--s-4);
  min-height: 44px;
  width: 100%;
}
.field select option { color: #000000; }

/* ── The room, below the footer ─────────────────────────────────────────── */

/* [hidden] has to be able to win: .room sets a background and padding, and the
   UA sheet's bare display:none loses to any later class rule */
.room[hidden] { display: none; }

/* The site's own dark ground, not a blacker black — this reads as the bottom of
   the page, not as a panel dropped onto it. */
.room {
  /* --bg is rewritten per-scroll by the ground, so the literal is what pins this
     to the page's base dark rather than to whatever colour scrolled past last */
  background: #130F0C;
  border-top: 1px solid rgba(255, 255, 255, .1);
  padding-block: var(--s-12);
}
/* no title, no hint, no legend — the field is the whole thing, and a visitor who
   clicks it finds out what it does faster than a caption could tell them */
.room__inner { display: grid; }

/* A real grid, so every dot lands on a shared ruler and the field reads as a
   room of desks rather than as scattered points. auto-fill rather than a fixed
   column count: the room fills whatever width it is given and reflows on its
   own at every size, so there is no breakpoint to keep in sync. */
.room__board {
  display: grid;
  grid-template-columns: repeat(auto-fill, minmax(38px, 1fr));
  grid-auto-rows: 38px;
  place-items: center;
  gap: 0;
}
@media (min-width: 768px) {
  .room__board { grid-template-columns: repeat(auto-fill, minmax(48px, 1fr)); grid-auto-rows: 48px; }
}

.room__dot {
  width: min(16px, 100%);
  aspect-ratio: 1;
  border: 0;
  padding: 0;
  border-radius: 50%;
  background: transparent;
  /* everyone starts as an outline: present in the room, not yet known */
  box-shadow: inset 0 0 0 1.5px rgba(255, 255, 255, .5);
  cursor: pointer;
  transition: background-color .32s var(--ease), box-shadow .32s var(--ease), transform .32s var(--ease);
}
.room__dot:hover { transform: scale(1.2); }
/* filled dots keep their colour — the room has been changed, not briefly lit */
.room__dot[data-fill] {
  background: var(--fill);
  box-shadow: inset 0 0 0 1.5px var(--fill);
}

@media (prefers-reduced-motion: reduce) {
  .room__dot { transition: none; }
  .room__dot:hover { transform: none; }
}

/* ── Reveal ─────────────────────────────────────────────────────────────── */

.reveal {
  opacity: 0;
  transform: translateY(16px);
  transition: opacity .7s var(--ease) var(--d, 0ms), transform .7s var(--ease) var(--d, 0ms);
}
.reveal.is-in { opacity: 1; transform: none; }
/* Focus pull (after tractorbeam.com): the block itself stays put and each
   word comes out of a blur instead — opacity, a 0.22em blur and a small rise,
   staggered 40ms a word. Armed by the same .is-in as every other reveal. */
.reveal.focuspull { opacity: 1; transform: none; transition: none; }
/* The padding (taken back by the margin) gives each word's box room for
   descenders: a filtered box is clipped to itself in Safari, and at the
   headline's 1.05 line-height the tail of the p in "planet" fell outside it.
   The animation fills backwards only, so no filter is left on the word once
   it has landed. */
.focuspull__word { display: inline-block; opacity: 0; padding-block: .15em; margin-block: -.15em; }
.focuspull.is-in .focuspull__word {
  opacity: 1;
  animation: focus-pull .52s cubic-bezier(.16, 1, .3, 1) calc(var(--i) * 40ms) backwards;
}
/* slower and softer, picked on tests/boxhead.html (02): the Box Set head */
.focuspull--slow.is-in .focuspull__word { animation-duration: .8s; animation-delay: calc(var(--i) * 90ms); animation-name: focus-pull-slow; }
@keyframes focus-pull-slow {
  0%   { opacity: 0; filter: blur(14px); transform: translate3d(0, .2em, 0); }
  60%  { opacity: 1; }
  100% { opacity: 1; filter: blur(0); transform: none; }
}
@keyframes focus-pull {
  0%   { opacity: 0; filter: blur(.22em); transform: translate3d(0, .06em, 0); }
  55%  { opacity: 1; }
  100% { opacity: 1; filter: blur(0); transform: none; }
}
@media (prefers-reduced-motion: reduce) { .focuspull__word { opacity: 1; animation: none !important; } }
/* Phone Bartow rail: overflow clips off-screen cards, so IntersectionObserver
   treated each swipe as a new arrival. Content is already in the card. */
.story__rail .reveal {
  opacity: 1;
  transform: none;
  animation: none;
  transition: none;
}

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

/* ── Sticky folder button → test pages ──────────────────────────────────── */
/* Centred along the bottom edge now, since back-to-top has taken the corner.
   Always display:none until script.js adds .is-unlocked after fff (or a
   restored unlock). Never put display:grid on the base rule — that outranked
   the UA [hidden] style and showed the icon on Vercel before deferred JS. */
.folder {
  position: fixed;
  left: 50%;
  transform: translateX(-50%);
  bottom: var(--s-6);
  z-index: 70;
  /* Same 44 disc and soft glass as .burger / .stepper — opaque pills with a
     hard rule read as a third chrome language against the frosted nav. */
  width: 44px;
  height: 44px;
  display: none;
  place-content: center;
  border: 0;
  border-radius: var(--r-pill);
  background: color-mix(in srgb, var(--fg) 8%, transparent);
  backdrop-filter: blur(12px) saturate(1.3);
  -webkit-backdrop-filter: blur(12px) saturate(1.3);
  box-shadow: inset 0 0 0 1px color-mix(in srgb, var(--fg) 6%, transparent);
  transition: background-color .2s var(--ease), transform .2s var(--ease),
              box-shadow .2s var(--ease);
}
.folder.is-unlocked {
  display: grid;
}
/* the translate has to be restated in every transform, or hovering would drop the
   centring and the button would jump to the right */
.folder:hover {
  background: color-mix(in srgb, var(--fg) 10%, transparent);
  box-shadow: inset 0 0 0 1px color-mix(in srgb, var(--fg) 12%, transparent);
  transform: translateX(-50%) translateY(-2px);
}
.folder:active { transform: translateX(-50%) translateY(0); }
.folder svg { width: 18px; height: 18px; fill: var(--fg); }
.folder[hidden] { display: none; }

/* ── Back to top ────────────────────────────────────────────────────────── */

/* Only appears once there is something to come back from, so it is never a
   button that does nothing. Same 44 glass disc as .burger and .folder. */
.totop {
  position: fixed;
  /* the menu disc's twin: the same gap from the screen's right edge as the
     burger (the .wrap gutter), and its gap from the bottom matches the
     burger's from the top (--pad). 36px, not the 44 rest size: this only
     shows once the page has scrolled, and by then the burger has compacted
     to 36 (--nav-disc-rest minus --nav-disc-delta). */
  right: calc(max(0px, (100vw - var(--wrap)) / 2) + var(--pad));
  bottom: var(--pad);
  z-index: 70;
  width: 36px;
  height: 36px;
  display: grid;
  place-content: center;
  border: 0;
  border-radius: var(--r-pill);
  background: color-mix(in srgb, var(--fg) 8%, transparent);
  backdrop-filter: blur(12px) saturate(1.3);
  -webkit-backdrop-filter: blur(12px) saturate(1.3);
  box-shadow: inset 0 0 0 1px color-mix(in srgb, var(--fg) 6%, transparent);
  opacity: 0;
  transform: translateY(8px);
  pointer-events: none;
  /* visibility:hidden while idle so backdrop-filter is not sampled off-stage */
  visibility: hidden;
  transition: opacity .25s var(--ease), transform .25s var(--ease),
              background-color .2s var(--ease), box-shadow .2s var(--ease),
              visibility 0s linear .25s;
}
.totop.is-in {
  opacity: 1;
  transform: none;
  pointer-events: auto;
  visibility: visible;
  transition: opacity .25s var(--ease), transform .25s var(--ease),
              background-color .2s var(--ease), box-shadow .2s var(--ease),
              visibility 0s;
}
.totop:hover {
  opacity: 1;
  background: color-mix(in srgb, var(--fg) 10%, transparent);
  box-shadow: inset 0 0 0 1px color-mix(in srgb, var(--fg) 12%, transparent);
  transform: translateY(-2px);
}
.totop svg { width: 16px; height: 16px; fill: none; stroke: var(--fg); stroke-width: 2; }
/* a small label on hover (mouse only): reads the button's own aria-label */
.totop::after {
  content: attr(aria-label);
  position: absolute; right: calc(100% + 10px); top: 50%;
  padding: 6px 10px; border-radius: 8px;
  font-size: 13px; line-height: 1; white-space: nowrap;
  color: var(--bg, #000); background: var(--fg);
  opacity: 0; translate: 4px -50%; pointer-events: none;
  transition: opacity .18s var(--ease), translate .18s var(--ease);
}
@media (hover: hover) {
  .totop:hover::after, .totop:focus-visible::after { opacity: 1; translate: 0 -50%; }
}

@media (prefers-reduced-motion: reduce) {
  .totop { transition: none; }
}

/* ── Card deck rail ─────────────────────────────────────────────────────── */

.deckrail {
  /* A scroll container clips at its padding edge, so the shadows need room
     inside it. The negative margins hand that room back to the layout —
     offset by the gap we actually want above and below the cards. */
  padding-block: 80px;
  /* the gap the cards actually want under their heading is closer to 24px than
     the 56 this started with — the padding above is only shadow room */
  margin-top: calc(var(--s-6) - 80px);
  margin-bottom: calc(var(--s-8) - 80px);
}
.deckrail .rail__track {
  align-items: flex-start;
  perspective: 1200px;
  padding-block: 0;
  gap: var(--s-5);
}

.deckcard {
  position: relative;
  flex: 0 0 236px;
  scroll-snap-align: start;
  margin: 0;
  transform-style: preserve-3d;
  transform:
    perspective(900px)
    rotateX(var(--rx, 0deg))
    rotateY(var(--ry, 0deg))
    translateZ(var(--tz, 0px));
  /* Transform only — animating filter:drop-shadow re-blurs every frame. */
  transition: transform .5s var(--ease);
}
.deckcard.is-tilting { transition: transform .08s linear; }

.deckcard__img {
  width: 100%;
  aspect-ratio: 5 / 7;
  object-fit: cover;
  border-radius: 10px;
  display: block;
  user-select: none;
  -webkit-user-drag: none;
  transform: translateZ(30px);
  box-shadow: 0 8px 20px -6px rgba(0, 0, 0, .28);
  transition: box-shadow .35s var(--ease);
}
.deckcard:hover .deckcard__img {
  box-shadow: 0 16px 32px -8px rgba(0, 0, 0, .38);
}


/* ── Conversations box (CSS 3D) ─────────────────────────────────────────────
   Panel proportions come straight from the print files:
   lid 723×600, front/back 725×434, sides 594×428  →  W 724 · D 597 · H 431 */

.boxstage {
  /* unitless companions: CSS sqrt() only accepts plain numbers, so the
     rotation-diagonal math below has to be done unit-free and re-dimensioned */
  --box-w-n: 313;
  --box-d-n: 259;
  --box-w: calc(var(--box-w-n) * 1px);
  --box-d: calc(var(--box-d-n) * 1px);
  --box-h: 187px;
  --hw: calc(var(--box-w) / 2);
  --hd: calc(var(--box-d) / 2);
  --hh: calc(var(--box-h) / 2);
  position: relative;
  display: grid;
  justify-items: center;
  margin-top: var(--s-20);
  /* Room above/below the scene for the chat ring so bubbles don't collide
     with the Box Set title or the solution lede. */
  padding-block: var(--s-8) var(--s-12);
  /* Opaque fill: main is transparent for the footer reveal, so without this
     the chat pads can show the fixed charcoal foot through them. */
  background: var(--bg);
}

/* ── iMessage ring around the Conversations box ─────────────────────────
   Questions land as received (grey) bubbles; answers as sent (blue). They
   sit in the blue field around the 3D box and pop in as the stage scrolls
   through the viewport. Pointer lean/magnetism is driven from JS on
   .boxstage. Empty stage space stays pointer-events: none so the 3D box
   keeps drag; each Q&A pair is pointer-auto so it can be grabbed as one. */
.boxchats {
  position: absolute;
  inset: 0;
  z-index: 2;
  pointer-events: none;
  overflow: visible;
  perspective: 720px;
}
.imsg-pair {
  position: absolute;
  left: var(--x, 0%);
  top: var(--y, 0%);
  z-index: 2;
  display: flex;
  flex-direction: column;
  align-items: flex-start;
  gap: 6px;
  width: max-content;
  max-width: min(220px, 48vw);
  pointer-events: auto;
  cursor: grab;
  touch-action: none;
  user-select: none;
  -webkit-user-select: none;
}
.imsg-pair.is-dragging { cursor: grabbing; z-index: 6; }
/* On a touch screen the bubbles step out of the way: a drag on them spins
   the box underneath (and a swipe past them scrolls the page) instead of
   picking the bubble up. Dragging bubbles stays a mouse nicety. */
@media (pointer: coarse) {
  .imsg-pair { pointer-events: none; touch-action: auto; }
  /* the stage spins the box on a sideways drag; up and down still scroll */
  .boxstage { touch-action: pan-y; }
}
.imsg-pair .imsg--me { align-self: flex-end; margin-inline-start: 28px; }
.imsg {
  /* --imx/--imy magnetic float; --itx/--ity tilt; --ims proximity scale */
  --imx: 0px;
  --imy: 0px;
  --itx: 0deg;
  --ity: 0deg;
  --ims: 1;
  position: relative;
  /* fit-content, not max-content: inside the narrower pair a max-content
     bubble got squeezed while its text kept the full line, and the ends of
     the answers were cut off */
  width: fit-content;
  max-width: min(200px, 42vw);
  margin: 0;
  padding: 7px 13px 8px;
  font-size: 14px;
  line-height: 1.3;
  border-radius: 18px;
  box-shadow: 0 10px 22px -14px rgba(0, 0, 0, .35);
  transform: translateY(14px) scale(0.86) rotate(var(--r, 0deg));
  opacity: 0;
  transition:
    opacity .45s var(--ease),
    transform .55s cubic-bezier(.22, 1.35, .36, 1);
  /* flat, not preserve-3d: with the tilt in 3D the bubble's plane could cut
     through the box scene behind it and slice off the ends of its words */
  transform-style: flat;
}
.imsg.is-in {
  opacity: 1;
  transform:
    translate3d(var(--imx), var(--imy), 0)
    rotateX(var(--itx))
    rotateY(var(--ity))
    rotate(var(--r, 0deg))
    scale(var(--ims));
}
/* While the pointer is over the stage, JS eases the custom props each frame —
   drop the transform transition so it doesn't fight the rAF follow.
   will-change only while tracking — permanent promotion on every bubble was
   keeping eight composited layers alive down the page. */
.boxchats.is-tracking .imsg.is-in {
  transition: opacity .45s var(--ease);
  will-change: transform;
}
/* Real Messages bubbles: the system font, full 18px rounding and the
   curled tail at the bottom corner (a clipped pseudo-element in the
   bubble's own colour, so it works on any ground). Received on the left,
   grey; sent on the right, blue. */
.imsg {
  font-family: -apple-system, BlinkMacSystemFont, "SF Pro Text", "Helvetica Neue", Arial, sans-serif;
  letter-spacing: -0.015em;
  --tail: #E9E9EB;
}
.imsg { isolation: isolate; }
/* z-index -1 puts the tail under the words but still over the page: it is
   solid bubble colour, and painted on top it covered the first letter of a
   received bubble's last line and the last letter of a sent one — on phones,
   where the padding is tighter, visibly ("proud" read "roud"). */
.imsg::before {
  content: "";
  position: absolute;
  z-index: -1;
  bottom: 0;
  width: 20px;
  height: 20px;
  background: var(--tail);
  clip-path: path('M0 0 H9 C9 9 12 15 20 20 C12.5 20.6 5.5 18.8 0 15.5 Z');
}
/* Received — the deck's question */
.imsg--them {
  background: #E9E9EB;
  color: #000000;
}
.imsg--them::before { left: -6px; transform: scaleX(-1); }
/* Sent — a student's possible answer */
.imsg--me {
  --tail: #0A84FF;
  background: #0A84FF;
  color: #FFFFFF;
}
.imsg--me::before { right: -6px; }
/* Phone: two Q&A pairs max, parked in top/bottom padding bands so they never
   sit on the 3D box face. (Desktop keeps the full 4-pair ring.)
   !important beats the inline --x/--y/--r from markup — without it, mobile
   overrides lose to style="" and bubbles keep desktop coords on the box. */
@media (max-width: 699px) {
  .boxstage {
    /* Tall pads ≈ one bubble stack above + below the scene */
    padding-block: 108px 112px;
  }
  .imsg {
    max-width: min(200px, 70vw);
    font-size: 12px;
    padding: 7px 11px;
  }
  .imsg-pair { max-width: min(220px, 76vw); }
  .imsg-pair:nth-child(n + 3) { display: none; }

  /* Top band — one Q&A stack above the box; bottom band shifted right */
  .imsg-pair:nth-child(1) { --x: 5% !important;  --y: 2% !important; }
  /* anchored by its right edge: from left: 40% it ran off a 320px screen */
  .imsg-pair:nth-child(2) { --y: 79% !important; left: auto; right: 5%; }
  .imsg-pair:nth-child(1) .imsg--them { --r: -2deg !important; }
  .imsg-pair:nth-child(1) .imsg--me { --r: 2deg !important; }
  .imsg-pair:nth-child(2) .imsg--them { --r: 2deg !important; }
  .imsg-pair:nth-child(2) .imsg--me { --r: -2deg !important; }
}
@media (prefers-reduced-motion: reduce) {
  .imsg {
    transition: opacity .3s var(--ease);
    transform: rotate(var(--r, 0deg));
  }
  .imsg.is-in { transform: rotate(var(--r, 0deg)); }
  .boxchats.is-tracking .imsg.is-in { transition: opacity .3s var(--ease); }
}

.boxstage__scene {
  position: relative;
  z-index: 1;
  width: 100%;
  max-width: 640px;
  height: 340px;
  display: grid;
  place-items: center;
  perspective: 1600px;
  perspective-origin: 50% 46%;
  touch-action: none;
  cursor: grab;
  user-select: none;
}
.boxstage__scene.is-dragging { cursor: grabbing; }

.box3d {
  position: relative;
  width: var(--box-w);
  height: var(--box-h);
  transform-style: preserve-3d;
  transform: rotateX(var(--rx, -18deg)) rotateY(var(--ry, -32deg));
  will-change: transform;
}

.face {
  position: absolute;
  margin: 0;
  overflow: hidden;
  background: #FDFDFC;
  backface-visibility: hidden;
  box-shadow: inset 0 0 0 1px rgba(21, 21, 21, .06);
}
.face img { width: 100%; height: 100%; object-fit: cover; display: block; }
.face::after { content: ""; position: absolute; inset: 0; pointer-events: none; }

.face--top,
.face--bottom { width: var(--box-w); height: var(--box-d); left: 0; top: calc(var(--hh) - var(--hd)); }
.face--front,
.face--back   { width: var(--box-w); height: var(--box-h); left: 0; top: 0; }
.face--left,
.face--right  { width: var(--box-d); height: var(--box-h); left: calc(var(--hw) - var(--hd)); top: 0; }

.face--top    { transform: rotateX(90deg)  translateZ(var(--hh)); }
.face--bottom { transform: rotateX(-90deg) translateZ(var(--hh)); }
.face--front  { transform: translateZ(var(--hd)); }
.face--back   { transform: rotateY(180deg) translateZ(var(--hd)); }
.face--right  { transform: rotateY(90deg)  translateZ(var(--hw)); }
.face--left   { transform: rotateY(-90deg) translateZ(var(--hw)); }

/* directional light: the lid catches it, the underside is darkest */
.face--top::after    { background: linear-gradient(160deg, rgba(255,255,255,.55), rgba(21,21,21,.04)); }
.face--front::after  { background: rgba(21, 21, 21, .07); }
.face--back::after   { background: rgba(21, 21, 21, .12); }
.face--right::after  { background: rgba(21, 21, 21, .12); }
.face--left::after   { background: rgba(21, 21, 21, .05); }
.face--bottom::after { background: rgba(21, 21, 21, .22); }

@media (max-width: 700px) {
  .boxstage { --box-w-n: 242; --box-d-n: 199; --box-h: 144px; }
  /* Slightly shorter scene so the padded chat bands stay in view on ~390 phones */
  .boxstage__scene { height: 270px; }
}

/* ── A gift for you — card fan ──────────────────────────────────────────── */

/* No containing panel — the fan and the copy sit on the ground. A tinted box
   around them made the offer look like a card of its own on a page that already
   has enough of those. */
.giftfan {
  display: flex;
  align-items: center;
  justify-content: center;
  gap: var(--s-12);
  width: 100%;
  background: transparent;
  border: 0;
  border-radius: 0;
  padding: 0;
  color: inherit;
  text-decoration: none;
  /* Beat the global a:hover fade so the fan and CTA don't dim as one lump. */
  opacity: 1;
}
.giftfan:hover,
.giftfan:focus-visible { opacity: 1; }

/* the fan sets the row's height, so it and the art scale together */
.giftfan__fan {
  position: relative;
  width: 164px;
  height: 172px;
  flex: none;
}
.giftfan__fan img {
  position: absolute;
  left: 0;
  top: 0;
  width: 110px;
  border-radius: 8px;
  transform-origin: 50% 100%;
  /* Waiting faces sit in the back of the pile until JS promotes them. */
  transform: rotate(-5deg);
  opacity: 0;
  z-index: 0;
  pointer-events: none;
  transition: transform .45s var(--ease), opacity .28s var(--ease);
  filter: drop-shadow(0 5px 14px rgba(0, 0, 0, .28));
}
.giftfan__fan img.is-fan {
  opacity: 1;
  z-index: calc(1 + var(--i, 0));
  transform: rotate(calc((var(--i) - 1) * 5deg)) translateX(calc(var(--i) * 12px));
}
.giftfan:hover .giftfan__fan img.is-fan:not(.is-deal),
.giftfan:focus-visible .giftfan__fan img.is-fan:not(.is-deal),
.giftfan.is-shuffling .giftfan__fan img.is-fan:not(.is-deal) {
  transform: rotate(calc((var(--i) - 1) * 13deg)) translateX(calc(var(--i) * 26px)) translateY(-6px);
}
.giftfan__fan img.is-deal {
  opacity: 0;
  z-index: 9;
  transform: rotate(18deg) translateX(64px) translateY(-14px);
}

.giftfan__copy { display: grid; gap: var(--s-2); }
.giftfan__kicker { font-size: var(--t-caption); line-height: 19.5px; color: var(--fg-50); }
.giftfan__line {
  font-size: clamp(18px, 2vw, 22px);
  line-height: 1.4;
  max-width: 34ch;
}
.giftfan__cta { margin-top: var(--s-4); justify-self: start; }
.giftfan:hover .giftfan__cta,
.giftfan:focus-visible .giftfan__cta { opacity: .8; }

@media (max-width: 700px) {
  /* Quiet tile on the white How stretch. A small fan beside the copy, as on
     desktop, so the offer takes a short band of the screen, not a whole one. */
  .giftfan {
    gap: var(--s-4);
    padding: var(--s-5);
    border-radius: var(--r-md);
    background: color-mix(in srgb, var(--fg) 6%, var(--bg));
  }
  .giftfan__fan { zoom: .6; }
  .giftfan__copy { gap: var(--s-1); }
  .giftfan__line { font-size: 16px; line-height: 1.35; }
  .giftfan__cta { margin-top: var(--s-3); min-height: 38px; }
}
/* too narrow for side by side: stack, but keep it compact */
@media (max-width: 359px) {
  .giftfan { flex-direction: column; text-align: center; gap: var(--s-3); }
  .giftfan__copy { justify-items: center; }
  .giftfan__cta { justify-self: center; }
}
@media (prefers-reduced-motion: reduce) {
  .giftfan__fan img { transition: none; }
}


/* Donate under the Bartow rail (not a swipe card). */
.story__donate { margin-top: var(--s-10, 40px); justify-content: flex-start; }
@media (max-width: 899px) {
  .story__donate { margin-top: var(--s-8); justify-content: center; }
}
/* ── 3D orbiting conversation-card ring ─────────────────────────────────── */

.orbit {
  --orbit-r: 340px;
  --card-w: 150px;
  --card-h: calc(var(--card-w) * 7 / 5);
  position: relative;
  margin-block: var(--s-6);
}
/* .reveal fades the whole block (opacity + translate). Either one flattens
   preserve-3d, and backface-hidden faces then stay blank after the fade.
   The faces fade themselves in once their art is ready. */
.orbit.reveal {
  opacity: 1;
  transform: none;
  transition: none;
}

.orbit--tight {
  --orbit-r: 220px;
  --card-w: 128px;
}

.orbit--mid {
  --orbit-r: 280px;
  --card-w: 136px;
}

/* Clip lives on the wrapper, not on the perspective element. overflow
   other than visible on the same box as perspective drops the faces. */
.orbit__clip {
  position: relative;
  overflow: hidden;
}
.orbit__stage {
  position: relative;
  height: min(520px, 70vh);
  perspective: 1400px;
  perspective-origin: 50% 46%;
  overflow: visible;
  /* pan-y lets the page scroll vertically through the stage; horizontal
     flicks are claimed in JS only after intent is clear. */
  touch-action: pan-y;
  cursor: grab;
  user-select: none;
}
.orbit__stage.is-dragging {
  /* Once we're spinning the ring, lock touch to the gesture. */
  touch-action: none;
  cursor: grabbing;
}
.orbit__stage--aim { cursor: crosshair; }
.orbit__stage--pick { cursor: default; }
.orbit__stage--pick.is-dragging { cursor: grabbing; }

.orbit__ring {
  position: absolute;
  inset: 0;
  transform-style: preserve-3d;
  transform: rotateX(var(--rx, 8deg)) rotateY(var(--ry, 0deg));
}

/* Each slot is planted on the ring: yaw by index, then push out along Z.
   Cards face outward — so the far side of the orbit shows their backs. */
.orbit__slot {
  position: absolute;
  left: 50%;
  top: 50%;
  width: var(--card-w);
  height: var(--card-h);
  margin: calc(var(--card-h) / -2) calc(var(--card-w) / -2);
  transform-style: preserve-3d;
  transform:
    rotateY(calc(var(--i) * 360deg / var(--n)))
    translateZ(var(--orbit-r));
  /* Keep the cylinder plane clickable. Cards pitch a few degrees inside the
     slot, so the visual top often misses .orbit__card and lands here. */
  /* Kill inspector-/UA-looking blue rims; focus lives on the card when zoomed. */
  outline: none;
  border: 0;
  box-shadow: none;
  -webkit-tap-highlight-color: transparent;
}

.orbit__card {
  position: relative;
  width: 100%;
  height: 100%;
  transform-style: preserve-3d;
  /* A little lean so the ring reads as a shallow cylinder, not a flat dial. */
  transform:
    rotateX(calc(2deg + var(--tilt-x, 0deg)))
    rotateY(var(--tilt-y, 0deg))
    translateZ(var(--lift, 0px))
    scale(var(--zoom, 1));
  /* No filter transitions — drop-shadow on spinning 3D cards was a major
     paint cost; hover/focus deepen .orbit__face box-shadow instead. */
  transition:
    transform .4s cubic-bezier(.22, 1.25, .36, 1),
    opacity .35s var(--ease, cubic-bezier(.16, 1, .3, 1));
  cursor: inherit;
  outline: none;
  -webkit-tap-highlight-color: transparent;
}
.orbit__card:focus { outline: none; }
.orbit__card:focus-visible {
  outline: 2px solid var(--fg);
  outline-offset: 4px;
}

.orbit__face {
  position: absolute;
  inset: 0;
  border-radius: 10px;
  overflow: hidden;
  /* Flatten each face so backface-visibility can cull the reverse side. */
  transform-style: flat;
  backface-visibility: hidden;
  -webkit-backface-visibility: hidden;
  outline: none;
  box-shadow:
    0 18px 40px -16px rgba(0, 0, 0, .55),
    0 0 0 1px rgba(255, 255, 255, .06);
  background: transparent;
  -webkit-tap-highlight-color: transparent;
  opacity: 0;
  transition: box-shadow .35s var(--ease, cubic-bezier(.16, 1, .3, 1));
}
/* Opacity fade only once the ring is on screen. A transition that
   finishes while the stage is offscreen or under a hidden ancestor
   leaves the compositor holding opacity 0. */
.orbit.is-shown .orbit__face {
  transition:
    box-shadow .35s var(--ease, cubic-bezier(.16, 1, .3, 1)),
    opacity .3s var(--ease, cubic-bezier(.16, 1, .3, 1));
}
/* shown once its image has loaded (orbit-ring.js) — no dark flash */
.orbit__face.is-loaded { opacity: 1; }
.orbit__face img {
  display: block;
  width: 100%;
  height: 100%;
  object-fit: cover;
  pointer-events: none;
  user-select: none;
  -webkit-user-drag: none;
  /* Avoid image paint leaking through the culled face. */
  backface-visibility: hidden;
  -webkit-backface-visibility: hidden;
}
/* Front faces out (camera / ring exterior). Tiny Z offset kills z-fight. */
.orbit__face--front {
  transform: translate3d(0, 0, 0.5px);
}
/* Back faces in (ring center). Far side of the orbit shows these. */
.orbit__face--back {
  transform: rotateY(180deg) translate3d(0, 0, 0.5px);
  /* Inward faces sit in the ring’s volume and steal clicks from the fan.
     Rear cards (backs toward camera) still hit via the slot; JS then
     ignores the back hemisphere. */
  pointer-events: none;
}

/* ── Mode: per-card hover lift ──────────────────────────────────────────── */
.orbit[data-orbit-mode="lift"] .orbit__card {
  pointer-events: auto;
  cursor: pointer;
}
.orbit[data-orbit-mode="lift"] .orbit__card.is-hot {
  --lift: 56px;
  z-index: 2;
}
.orbit[data-orbit-mode="lift"] .orbit__card.is-hot .orbit__face {
  box-shadow:
    0 28px 48px -14px rgba(0, 0, 0, .55),
    0 0 0 1px rgba(255, 255, 255, .08);
}

/* ── Mode: magnetic aim ─────────────────────────────────────────────────── */
.orbit[data-orbit-mode="magnet"] .orbit__card {
  pointer-events: auto;
  cursor: pointer;
}
.orbit[data-orbit-mode="magnet"] .orbit__slot.is-aimed .orbit__card {
  --lift: 28px;
}
.orbit[data-orbit-mode="magnet"] .orbit__slot.is-aimed .orbit__face {
  box-shadow:
    0 22px 40px -14px rgba(0, 0, 0, .5),
    0 0 0 1px rgba(255, 255, 255, .08);
}

/* ── Mode: throw + idle drift + quiet hover tilt ────────────────────────── */
.orbit[data-orbit-mode="throw"] .orbit__stage {
  cursor: grab;
}
.orbit[data-orbit-mode="throw"] .orbit__stage.is-dragging { cursor: grabbing; }
.orbit[data-orbit-mode="throw"] .orbit__card {
  pointer-events: auto;
  cursor: grab;
}
.orbit[data-orbit-mode="throw"] .orbit__card.is-hot {
  --lift: 28px;
  z-index: 2;
  cursor: grab;
}
.orbit[data-orbit-mode="throw"] .orbit__card.is-hot .orbit__face {
  box-shadow:
    0 22px 36px -14px rgba(0, 0, 0, .48),
    0 0 0 1px rgba(255, 255, 255, .08);
}

/* ── Mode: steer / tilt — aiming cursors already on the stage ───────────── */
.orbit[data-orbit-mode="tilt"] .orbit__stage {
  cursor: move;
}

.orbit__controls {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: var(--s-4);
  margin-top: var(--s-6);
}
.orbit__hint {
  margin: 0;
  font-size: var(--t-caption);
  line-height: 19.5px;
  color: var(--fg-50);
}

@media (max-width: 699px) {
  .orbit { --orbit-r: 210px; --card-w: 110px; }
  .orbit--tight { --orbit-r: 170px; --card-w: 96px; }
  .orbit--mid { --orbit-r: 190px; --card-w: 104px; }
  .orbit__stage { height: min(420px, 62vh); }
  .orbit[data-orbit-mode="lift"] .orbit__card.is-hot { --lift: 36px; }
}

@media (prefers-reduced-motion: reduce) {
  .orbit__stage { cursor: default; }
  .orbit__card {
    transition: none;
    --lift: 0px !important;
    --tilt-x: 0deg !important;
    --tilt-y: 0deg !important;
  }
}


/* Live homepage conversation-topics orbit
   Default orientation: fronts outside (toward camera), blue backs inside. */
.orbit--live {
  --orbit-r: 300px;
  --card-w: 140px;
  width: 100vw;
  max-width: 100vw;
  margin-left: calc(50% - 50vw);
  /* the cards sit mid-stage with ~70px of empty stage over them, so the
     ring is pulled up into the heading's space: ~50px heading to cards */
  margin-top: calc(var(--s-4) - 40px);
  margin-bottom: var(--s-8);
  overflow: hidden;
}
.orbit--live .orbit__stage {
  height: min(480px, 64vh);
}
.orbit--live .orbit__controls { display: none; }

/* Click-to-zoom: one card forward, neighbors quiet; spin pauses in JS. */
.orbit__close {
  position: absolute;
  top: var(--s-4);
  right: var(--s-4);
  z-index: 6;
  width: 40px;
  height: 40px;
  display: grid;
  place-items: center;
  margin: 0;
  padding: 0;
  border: 0;
  border-radius: 999px;
  background: color-mix(in srgb, var(--fg) 12%, transparent);
  color: var(--fg);
  cursor: pointer;
  opacity: 0;
  pointer-events: none;
  transform: scale(0.92);
  transition: opacity .28s var(--ease, cubic-bezier(.16, 1, .3, 1)),
              transform .28s var(--ease, cubic-bezier(.16, 1, .3, 1)),
              background-color .2s var(--ease, cubic-bezier(.16, 1, .3, 1));
}
.orbit__close svg {
  width: 18px;
  height: 18px;
  fill: none;
  stroke: currentColor;
  stroke-width: 2;
  stroke-linecap: round;
}
.orbit__close:hover { background: color-mix(in srgb, var(--fg) 18%, transparent); }
.orbit.is-focused .orbit__close {
  opacity: 1;
  pointer-events: auto;
  transform: none;
}
.orbit.is-focused .orbit__stage { cursor: pointer; }
.orbit.is-focused .orbit__slot.is-focus {
  z-index: 4;
}
.orbit.is-focused .orbit__slot.is-focus .orbit__card {
  --lift: 36px;
  --zoom: 1.32;
  --tilt-x: 0deg;
  --tilt-y: 0deg;
  cursor: default;
  z-index: 4;
}
.orbit.is-focused .orbit__slot.is-focus .orbit__face {
  box-shadow:
    0 28px 52px -12px rgba(0, 0, 0, .55),
    0 0 0 1px rgba(255, 255, 255, .1);
}
/* Keep neighbors solid — emphasize the pick with scale/z only, not washout. */
.orbit.is-focused .orbit__slot.is-dim {
  opacity: 1;
  pointer-events: none;
}
.orbit.is-focused .orbit__slot.is-dim .orbit__card {
  --lift: 0px;
  --zoom: 0.94;
}

@media (max-width: 699px) {
  /* Fit 2–3 cards in ~390px: radius/card under half-viewport after perspective.
     Stage soft-clips far edges; 6-card thinned deck stays in JS. */
  .orbit--live {
    --orbit-r: 132px;
    --card-w: 126px;
    width: 100%;
    max-width: 100%;
    margin-left: 0;
    margin-right: 0;
    overflow: hidden;
  }
  .orbit--live .orbit__stage {
    height: min(400px, 58vh);
    perspective: 900px;
    perspective-origin: 50% 48%;
    overflow: hidden;
  }
  .orbit.is-focused .orbit__slot.is-focus .orbit__card {
    --lift: 20px;
    --zoom: 1.16;
  }
}

@media (prefers-reduced-motion: reduce) {
  .orbit.is-focused .orbit__slot.is-focus .orbit__card,
  .orbit.is-focused .orbit__slot.is-dim .orbit__card,
  .orbit__close {
    transition: none;
  }
}

/* The floating Donate / folder / back-to-top discs ride in this dock
   (script.js moves them in): zero height, sticky to the bottom of the
   screen, last thing in <main>. It holds them 24px off the screen's foot
   and, once the white sheet's end scrolls up, 24px off the sheet's edge —
   positioned by the browser as it scrolls, so they never lag or shake. */
/* fixed, not sticky at the end of <main>: sticky let go where the sheet
   ends, so Donate and back-to-top rode off the screen at the footer */
.footdock {
  position: fixed;
  left: 0;
  right: 0;
  bottom: 0;
  z-index: 70;
  /* a real box around the buttons, not zero height with them hanging out
     of it: iPhone Safari didn't paint the overhang (Donate) until the first
     scroll. The box itself lets taps through. */
  height: 120px;
  pointer-events: none;
}
.footdock .sticky-donate,
.footdock .folder,
.footdock .totop { position: absolute; }
/* the dock lets taps through, so Donate takes its own back (back to top
   keeps its rule: none while hidden, auto once .is-in) */
.footdock .sticky-donate,
.footdock .folder { pointer-events: auto; }
/* over the black footer: white ink on the glass (script.js sets .on-foot) */
.footdock.on-foot { --fg: #FFFFFF; --bg: #000000; color: #FFFFFF; }
/* …and they rise above the logo + links row rather than sitting on it
   (--bar-h is the row's height, measured by script.js) */
.footdock { transition: translate .4s var(--ease); }
.footdock.is-lifted { translate: 0 calc(-1 * (var(--bar-h, 52px) + var(--s-4))); }
@media (prefers-reduced-motion: reduce) { .footdock { transition: none; } }

/* ── Moving between the site, the Wiki and Studio ─────────────────────
   A cross-page view transition (Chrome, Edge, Safari 18.2+; elsewhere the
   page just changes). The brand carries over: the site's full wordmark
   shrinks and slides into the apps' w. mark (and grows back out of it on
   the way home), the wordmark fading as the mark comes in, with a small
   settle. The app's name slides in beside the mark, and the menu disc
   glides from its place in one bar to its place in the other. */
@view-transition { navigation: auto; }
::view-transition-group(whisper-brand) { animation-duration: .6s; animation-timing-function: cubic-bezier(.32, 1.12, .42, 1); }
::view-transition-old(whisper-brand),
::view-transition-new(whisper-brand) { height: 100%; object-fit: contain; }
/* old and new on the same clock: they add up to a whole mark the whole
   way, rather than both being half gone in the middle */
::view-transition-old(whisper-brand) { animation: vtBrandOut .3s ease both; }
::view-transition-new(whisper-brand) { animation: vtBrandIn .3s ease both; }
/* between the Wiki and Studio the name just crossfades where it stands —
   no stretch, no slide (arriving from the site, brand-morph.js brings it in) */
::view-transition-group(app-name) { animation-duration: .3s; }
::view-transition-old(app-name), ::view-transition-new(app-name) { width: auto; height: 100%; object-fit: none; object-position: left center; }
::view-transition-old(app-name) { animation: vtBrandOut .18s ease both; }
::view-transition-new(app-name) { animation: vtBrandIn .26s ease both; }
::view-transition-group(menu-disc) { animation-duration: .6s; animation-timing-function: cubic-bezier(.32, 1.12, .42, 1); }
::view-transition-old(menu-disc), ::view-transition-new(menu-disc) { height: 100%; object-fit: contain; }
::view-transition-old(menu-disc) { animation: vtBrandOut .3s ease both; }
::view-transition-new(menu-disc) { animation: vtBrandIn .3s ease both; }
@keyframes vtBrandOut { to { opacity: 0; } }
@keyframes vtBrandIn { from { opacity: 0; } }
@media (prefers-reduced-motion: reduce) {
  ::view-transition-group(*), ::view-transition-old(*), ::view-transition-new(*) { animation: none !important; }
}

/* brand morph (brand-morph.js): while the logo and the menu disc are being
   drawn in from the other side, the real ones wait hidden and sit out the
   view transition */
html.is-brand-morph .nav__brand .logo,
html.is-brand-morph .top__mark,
html.is-brand-morph .sbar__logo,
html.is-brand-morph .burger,
html.is-brand-morph .amenu__btn { visibility: hidden; view-transition-name: none; }
html.is-brand-morph .top__name,
html.is-brand-morph .sbar__name { view-transition-name: none; }
/* the drawing is its own layer of the page change, at full strength from
   the first frame: the page crossfades under it, it doesn't fade itself */
::view-transition-group(brand-morph),
::view-transition-new(brand-morph),
::view-transition-old(brand-morph) { animation: none; }

/* ── Sprinkles: picks from the objects library, dropped around the
   homepage at half their library size. The 3D ones share one fixed WebGL
   canvas (objects3d.js) that draws only inside their boxes; drag to spin. */
.obj3d-tile { position: absolute; inset: 0; width: 100%; height: 100%; display: block; pointer-events: none; }
.section:has(> .sprinkle) { position: relative; }
.sprinkle { position: absolute; z-index: 3; width: clamp(110px, 10vw, 150px); aspect-ratio: 1; display: grid; place-items: center; pointer-events: none; }
.sprinkle[data-3d] { pointer-events: auto; cursor: grab; touch-action: pan-y; }
.sprinkle[data-3d]:active { cursor: grabbing; }
.sprinkle--voxel { top: calc(var(--s-6) + 330px); left: -14px; }   /* Overheard, in the left margin beside the photo show */
/* centred on the rule where Solution starts, in the empty padding either
   side of it: lower down, the headline runs on one line at wide screens
   and went straight under the eye */
.sprinkle--eye { top: calc(clamp(110px, 10vw, 150px) / -2); left: 38%; }
.sprinkle--howru { top: var(--s-6); right: 3%; width: clamp(213px, 18.75vw, 288px); aspect-ratio: 1.7; }
.sprinkle--cry { bottom: var(--s-8); left: 4%; }
.sprinkle--clippy { top: var(--s-6); right: 4%; width: 230px; aspect-ratio: auto; }
.sprinkle--pxheart { top: -27px; left: calc(50% - 27px); width: 54px; }   /* centred on the rule between FAQ and Volunteer */
.sprinkle--win95 { bottom: calc(var(--s-8) + 40px); left: calc(4% + 40px); width: 190px; aspect-ratio: auto; }
.sprinkle .px { width: 100%; image-rendering: pixelated; filter: drop-shadow(2px 0 #fff) drop-shadow(-2px 0 #fff) drop-shadow(0 2px #fff) drop-shadow(0 -2px #fff); }
.sprinkle .win95 { width: 100%; background: #c0c0c0; box-shadow: inset -1px -1px #0a0a0a, inset 1px 1px #fff, inset -2px -2px #808080, inset 2px 2px #dfdfdf; padding: 2px; font-family: 'MS Sans Serif', Tahoma, sans-serif; font-size: 11px; color: #000; letter-spacing: 0; line-height: 1.25; transform: rotate(-3deg); }
.sprinkle .win95__bar { display: flex; justify-content: space-between; align-items: center; padding: 2px 3px; background: linear-gradient(90deg, #000080, #1084d0); color: #fff; font-weight: 700; }
.sprinkle .win95__bar b { background: #c0c0c0; color: #000; width: 13px; height: 11px; display: grid; place-items: center; font-size: 8px; box-shadow: inset -1px -1px #0a0a0a, inset 1px 1px #fff; }
.sprinkle .win95__body { display: flex; gap: 8px; align-items: center; padding: 10px 8px; }
.sprinkle .win95__icon { flex: none; width: 22px; height: 22px; border-radius: 50%; background: #fff; border: 2px solid #000080; color: #000080; display: grid; place-items: center; font-weight: 700; font-size: 13px; }
.sprinkle .win95__btns { display: flex; gap: 6px; justify-content: center; padding-bottom: 7px; }
.sprinkle .win95__btns button { font: inherit; color: inherit; border: 0; cursor: pointer; }
.sprinkle .win95__btns button:active { box-shadow: inset 1px 1px #0a0a0a, inset -1px -1px #fff, inset 2px 2px #808080; }
.sprinkle .win95__btns span, .sprinkle .win95__btns button { min-width: 52px; padding: 2px 6px; text-align: center; background: #c0c0c0; box-shadow: inset -1px -1px #0a0a0a, inset 1px 1px #fff, inset -2px -2px #808080, inset 2px 2px #dfdfdf; }
.sprinkle .clippy { display: grid; justify-items: end; gap: 4px; width: 100%; }
.sprinkle .clippy__bubble { background: #ffffe1; color: #000; border: 1px solid #000; border-radius: 8px; padding: 7px 9px; font-family: Tahoma, sans-serif; font-size: 11px; line-height: 1.3; letter-spacing: 0; position: relative; }
.sprinkle .clippy__bubble::after { content: ""; position: absolute; right: 22px; bottom: -8px; border: 8px solid transparent; border-top-color: #000; border-bottom: 0; }
.sprinkle .clippy__btns { display: flex; gap: 6px; margin-top: 8px; }
.sprinkle .clippy__btns button { flex: 1; padding: 3px 8px; font: inherit; font-size: 11px; color: #000; background: #ece9d8; border: 1px solid #000; border-radius: 3px; cursor: pointer; box-shadow: inset -1px -1px #a8a69c, inset 1px 1px #fff; }
.sprinkle .clippy__btns button:active { box-shadow: inset 1px 1px #a8a69c; }
.sprinkle .clippy__clip { border: 0; background: none; padding: 0; cursor: pointer; font-size: 34px; line-height: 1; animation: sprinkle-wobble 2.2s ease-in-out infinite; transform-origin: 50% 90%; }
@keyframes sprinkle-wobble { 0%, 100% { transform: rotate(-6deg); } 50% { transform: rotate(6deg); } }
/* every sprinkle answers a click */
.sprinkle--pxheart, .sprinkle--win95 { pointer-events: auto; }
.sprinkle--clippy { pointer-events: auto; }
.sprinkle { cursor: grab; }
.sprinkle.is-moving { cursor: grabbing; z-index: 50; }
.sprinkle.is-moving * { cursor: grabbing; }
.sprinkle button { cursor: pointer; }
.sprinkle .win95 { transition: transform .35s var(--ease), opacity .35s var(--ease); }
.sprinkle .win95.is-min { transform: translate(-30%, 60%) scale(.1); opacity: 0; }
.sprinkle .win95__bar b { cursor: pointer; }
.sprinkle .clippy.is-hop .clippy__clip { animation: sprinkle-hop .5s var(--ease); }
@keyframes sprinkle-hop { 40% { transform: translateY(-14px) rotate(-12deg); } 70% { transform: translateY(0) rotate(8deg); } }
.sprinkle .px.is-pop { animation: sprinkle-pop .4s var(--ease); }
@keyframes sprinkle-pop { 40% { transform: scale(1.4); } }
.pxspark { position: absolute; left: 50%; top: 40%; width: 18px; image-rendering: pixelated; pointer-events: none; animation: pxspark .9s cubic-bezier(.2,.7,.3,1) forwards; }
@keyframes pxspark { to { transform: translate(var(--dx), var(--dy)) scale(.6); opacity: 0; } }
/* shuffle: tiles swap one after another, quickly */
.wall__item { transition: opacity .22s var(--ease), transform .3s var(--ease); }
.wall__item.is-out { opacity: 0; transform: scale(.94); }
/* phones: the sprinkles sit in desktop margins a phone doesn't have, so
   each gets a corner of its own here, kept small: the eye top-right of the
   problem section, the crying-laughing face beside the Volunteer button,
   the pixel heart on the FAQ rule, Clippy in room made for him under the
   last question, Whisper.exe beside Send on the contact sheet, and the
   voxel heart and "How are you?" riding the carousel (.sprinkle--m) */
@media (max-width: 899px) {
  .sprinkle { display: none; }
  .sprinkle--eye, .sprinkle--cry, .sprinkle--pxheart, .sprinkle--clippy, .sprinkle--win95 { display: grid; }
  /* Clippy sits above the questions on phones, so the room is held at the top */
  .faq { padding-top: calc(var(--s-20) + 130px); }
  .sprinkle--clippy { top: var(--s-8); bottom: auto; right: var(--s-4); width: 200px; }
  .sprinkle--win95 { left: auto; right: var(--s-4); bottom: calc(var(--s-20) - var(--s-4)); width: 168px; }
  .sprinkle--eye { width: 76px; top: var(--s-3); left: auto; right: var(--s-4); }
  .sprinkle--cry { width: 72px; bottom: var(--s-6); left: var(--s-4); }
  .sprinkle--m { display: grid; z-index: 4; }
  /* placed off the frame's own height (the .show__viewport rule below), so
     they hug its corners as it grows */
  .show__pin { --fh: calc(46svh + (var(--show-full) - 46svh) * var(--grow, 1)); }
  /* over the frame's top-right corner and its bottom-left, kept inside the
     gutter bands so they clear the menu disc and the Donate pill */
  .sprinkle--m-howru { width: 148px; aspect-ratio: 1.7; right: var(--s-3); top: calc(50% - var(--fh) / 2 + 10px); }
  .sprinkle--m-voxel { width: 64px; left: var(--s-5); top: calc(50% + var(--fh) / 2 - 56px); }
}
/* How we work: a ? and a ! either side of the chrome hand, staggered */
.sprinkle--mk { width: clamp(96px, 9vw, 132px); }
.sprinkle--mk-q1 { top: var(--s-12); left: 16%; }
.sprinkle--mk-x7 { top: calc(var(--s-12) + 230px); left: 7%; }
.sprinkle--mk-x9 { top: calc(var(--s-12) + 20px); right: 16%; }
.sprinkle--mk-q8 { top: calc(var(--s-12) + 250px); right: 8%; }
@media (max-width: 899px) {
  .sprinkle--mk { display: grid; width: 62px; }
  .sprinkle--mk-q1 { top: var(--s-8); left: var(--s-4); }
  .sprinkle--mk-x7 { top: calc(var(--s-8) + 150px); left: var(--s-2); }
  .sprinkle--mk-x9 { top: calc(var(--s-8) + 10px); right: var(--s-4); }
  .sprinkle--mk-q8 { top: calc(var(--s-8) + 160px); right: var(--s-2); }
}
/* the carousel's stickers work against the frame: big while it's a small
   card, shrinking as it grows to full size, and growing back as it shrinks
   away (--grow, script.js), each about the frame corner it sits on */
.sprinkle--m-howru, .sprinkle--m-voxel { scale: calc(1.7 - 0.7 * var(--grow, 1)); }
.sprinkle--m-howru { transform-origin: 25% 75%; }
.sprinkle--m-voxel { transform-origin: 75% 25%; }
/* wider screens: the voxel heart stays in the margin, but "How are you?"
   rides the frame's top-right corner too (it floated loose in the section's
   corner), off the frame's own size so it follows the frame as it grows */
@media (min-width: 900px) {
  .sprinkle--m { display: none; }
  .sprinkle--howru { display: none; }
  .show__pin { --fw: calc(64% + 36% * var(--grow, 1)); --fh: calc(56svh + 30svh * var(--grow, 1)); }
  .sprinkle--m-howru {
    display: grid; z-index: 4;
    width: clamp(213px, 18.75vw, 288px); aspect-ratio: 1.7;
    right: calc((100% - var(--fw)) / 2 - clamp(40px, 4vw, 64px));
    top: calc(50% - var(--fh) / 2 - clamp(70px, 6.5vw, 100px));
  }
  /* and the voxel heart over its bottom-left corner */
  .sprinkle--voxel { display: none; }
  .sprinkle--m-voxel {
    display: grid; z-index: 4;
    width: clamp(110px, 10vw, 150px);
    left: calc((100% - var(--fw)) / 2 - clamp(36px, 3.5vw, 56px));
    top: calc(50% + var(--fh) / 2 - clamp(70px, 6.5vw, 100px));
  }
}
@media (prefers-reduced-motion: reduce) { .sprinkle .clippy__clip { animation: none; } }

/* ── Bartow case study, as a preview (index #stories) ───────────────────
   After wearecollins.com: the story stops mid-sentence and the reader
   chooses to open the rest in a sheet (story-sheet.js). */
/* Label on the left, everything else in the wide right column (after
   wearecollins.com). The title runs a size up from .h1 — it is the name of
   the place, set like a cover — and the story under it reads like a pull
   quote, so the cut-off is a sentence you want to finish. */
.brief { display: grid; gap: var(--s-4); }
@media (min-width: 900px) { .brief { grid-template-columns: 1fr 2.2fr; gap: var(--s-14); } }
.brief__kicker { font-size: var(--t-caption); line-height: 19.5px; color: var(--fg-60); padding-top: var(--s-4); }
.brief__title {
  font-size: clamp(48px, 7vw, 104px);
  line-height: 1;
  letter-spacing: -0.05em;
  font-weight: 400;
}
.brief__sub { margin-top: var(--s-4); font-size: clamp(16px, 1.7vw, 20px); line-height: 1.55; color: var(--fg-60); }
.brief__lede {
  margin-top: var(--gap-title-block);
  color: var(--fg);
  font-size: clamp(26px, 3.2vw, 44px);
  line-height: 1.18;
  letter-spacing: -0.035em;
  max-width: 30ch;
  text-wrap: pretty;
}
.brief__ctas { display: flex; flex-wrap: wrap; gap: var(--s-3); margin-top: var(--s-8); }
.brief__ctas .btn span, .ssheet .btn span { margin-left: var(--s-2); }

/* ── What you can expect (index #gatherings), tests/expect4.html 03 ─────
   Titled like How we work (.cine__title, centred), then the five steps as
   a sideways rail that snaps card to card. The rail starts on the page's
   left gutter and runs off the right edge, so the next card always peeks. */
.xp__title { text-align: center; margin-bottom: var(--s-6); }
/* sized like the other section intros (.cine__lede under Partner with Whisper) */
.xp__intro { max-width: 46ch; margin: 0 auto var(--gap-title-block); text-align: center; font-size: clamp(17px, 1.35vw, 20px); line-height: 1.5; color: var(--fg-60); }
.xp__rail-wrap { --gutter: max(var(--pad), (100vw - var(--wrap)) / 2 + var(--pad)); }
.xp__rail {
  list-style: none; margin: 0;
  display: grid; grid-auto-flow: column; grid-auto-columns: clamp(250px, 27vw, 340px); gap: var(--s-4);
  overflow-x: auto; overscroll-behavior-x: contain;
  scroll-snap-type: x mandatory; scroll-padding-inline: var(--gutter);
  padding: 0 var(--gutter) var(--s-4);
  scrollbar-width: none;
}
.xp__rail::-webkit-scrollbar { display: none; }
/* each card is the photo itself: the number disc in the top corner, the
   step and its line in white over a dark fade at the foot
   (tests/expect5.html 02) */
.xp__card {
  scroll-snap-align: start;
  position: relative; aspect-ratio: 4 / 5;
  border-radius: 28px; overflow: hidden;
  background: #130F0C;
}
.xp__card img { position: absolute; inset: 0; width: 100%; height: 100%; object-fit: cover; }
.xp__card::after {
  content: ""; position: absolute; inset: 0;
  background: linear-gradient(to top, rgba(10, 8, 6, .92) 0%, rgba(10, 8, 6, .72) 48%, rgba(10, 8, 6, 0) 78%);
}
/* the step number in a white disc with black figures, scaled to the card */
.xp__no {
  --d: 56px;
  position: absolute; top: var(--s-4); left: var(--s-4); z-index: 1;
  display: inline-grid; place-items: center;
  width: var(--d); aspect-ratio: 1;
  border-radius: 50%; background: #FFFFFF; color: #130F0C;
  font-size: 24px; line-height: 1; letter-spacing: -0.04em; font-variant-numeric: tabular-nums;
}
/* Centring the line box isn't centring the figures: Heros's hhea metrics
   (1148 / -284) put its line box's middle well below the digits, so where a
   browser uses them (Safari) the numbers sat low. Trimmed to cap height and
   baseline, the box is the figures themselves, padded to the middle of the
   disc. Browsers without text-box keep the grid centring above. */
@supports (text-box: trim-both cap alphabetic) {
  .xp__no {
    display: block; box-sizing: border-box; height: var(--d);
    text-align: center; text-box: trim-both cap alphabetic;
    padding-top: calc((var(--d) - .729em) / 2);
  }
}
.xp__over { position: absolute; left: 0; right: 0; bottom: 0; z-index: 1; padding: var(--s-6); color: #FFFFFF; }
.xp__over h3 { font-weight: 400; font-size: clamp(30px, 2.6vw, 38px); line-height: 1.1; letter-spacing: -0.035em; }
.xp__over p { margin-top: var(--s-2); font-size: 14px; line-height: 1.5; color: rgba(255, 255, 255, .8); }
/* the step number as the big white numeral on the photo, like the How we
   work cards on phones — not a small disc */
.xp__card .xp__no {
  --d: auto;
  display: block; width: auto; height: auto; aspect-ratio: auto;
  padding: 0; border-radius: 0; background: none;
  top: var(--s-5); left: var(--s-5);
  color: #FFFFFF;
  font-size: clamp(64px, 6vw, 96px); line-height: .82; letter-spacing: -0.05em;
  text-box: normal;
  text-shadow: 0 2px 22px rgba(10, 8, 6, .35);
}
@media (max-width: 767px) { .xp__card .xp__no { font-size: clamp(72px, 22vw, 96px); } }   /* the How we work size on phones */
/* a soft dark wash at the top so the numeral holds on bright photos */
.xc__front { --top-wash: linear-gradient(to bottom, rgba(10, 8, 6, .38) 0%, rgba(10, 8, 6, 0) 34%); }
/* ── the cards in 3D (script.js): a front and a back with a few px of
   card-stock edge between, easing toward the pointer on a spring and
   flipping on click. The light is three quiet layers on the front: a
   pastel foil under the text that only shows where the light falls, a fine
   foil grain, and a soft glare that grows as the card tips. */
.xp__rail { padding-block: var(--s-6) var(--s-12); margin-block: calc(-1 * var(--s-6)) calc(-1 * var(--s-8)); }  /* room to tilt without clipping */
.xc { overflow: visible; background: none; border-radius: 0; perspective: 1100px; cursor: pointer; }
.xc::after { display: none; }
.xc:focus-visible { outline: none; }
.xc:focus-visible .xc__face { box-shadow: 0 0 0 3px var(--fg); }
.xc__body {
  position: absolute; inset: 0; transform-style: preserve-3d;
  transform: rotateX(var(--rx, 0deg)) rotateY(calc(var(--ry, 0deg) + var(--flip, 0deg)));
  transition: transform .95s cubic-bezier(.3, 1.25, .45, 1);
}
.xc.is-live .xc__body { transition: none; }
.xc.is-flipped { --flip: 180deg; }
/* no filter on the 3D body: a filter flattens it */
.xc__face {
  position: absolute; inset: 0; border-radius: 28px; overflow: hidden;
  backface-visibility: hidden; -webkit-backface-visibility: hidden;
  background: #130F0C; box-shadow: 0 22px 40px -14px rgba(19, 15, 12, .4);
}
.xc__front { transform: translateZ(2px); }
.xc__front > img { z-index: 0; }
.xc__front::after {
  content: ""; position: absolute; inset: 0; z-index: 2;
  background: linear-gradient(to top, rgba(10, 8, 6, .92) 0%, rgba(10, 8, 6, .72) 48%, rgba(10, 8, 6, 0) 78%), var(--top-wash);
}
.xc__front .xp__over, .xc__front .xp__no { z-index: 4; }
.xc__front::before {
  content: ""; position: absolute; inset: 0; z-index: 1; pointer-events: none;
  opacity: calc(var(--glow, 0) * .55);
  background:
    repeating-linear-gradient(-45deg, rgba(255, 255, 255, .07) 0 1px, transparent 1px 3px),
    repeating-linear-gradient(110deg,
      hsl(330 65% 78%) 0%, hsl(40 70% 80%) 8%, hsl(160 55% 78%) 16%,
      hsl(200 70% 80%) 24%, hsl(265 55% 82%) 32%, hsl(330 65% 78%) 40%);
  background-size: 100% 100%, 260% 260%;
  background-position: 0 0, var(--fx, 50%) var(--fy, 50%);
  mix-blend-mode: color-dodge; filter: saturate(.8);
  -webkit-mask-image: radial-gradient(farthest-corner circle at var(--gx, 50%) var(--gy, 50%), #000 0%, rgba(0, 0, 0, .55) 30%, rgba(0, 0, 0, .12) 65%, transparent 90%);
  mask-image: radial-gradient(farthest-corner circle at var(--gx, 50%) var(--gy, 50%), #000 0%, rgba(0, 0, 0, .55) 30%, rgba(0, 0, 0, .12) 65%, transparent 90%);
  transition: opacity .5s ease;
}
.xc__glare {
  position: absolute; inset: 0; z-index: 3; pointer-events: none;
  background: radial-gradient(farthest-corner circle at var(--gx, 50%) var(--gy, 50%), rgba(255, 255, 255, .42) 0%, rgba(255, 255, 255, .14) 22%, rgba(255, 255, 255, 0) 52%);
  mix-blend-mode: soft-light;
  opacity: calc(var(--glow, 0) * (.45 + var(--tip, 0) * .55));
  transition: opacity .5s ease;
}
/* the back: black, the full white wordmark in the middle, a faint sheen */
.xc__back { transform: rotateY(180deg) translateZ(2px); display: grid; place-items: center; background: #0b0a09; }
.xc .xc__back .xc__logo { position: static; inset: auto; width: 40%; height: auto; object-fit: contain; }
.xc__back::after {
  content: ""; position: absolute; inset: 0; pointer-events: none;
  background: linear-gradient(110deg, transparent 30%, rgba(255, 255, 255, .08) 45%, rgba(190, 215, 255, .1) 50%, transparent 65%) var(--fx, 50%) 0 / 250% 100% no-repeat;
}
/* card-stock thickness: thin layers between the faces */
.xc__edge, .xc__edge::before, .xc__edge::after { position: absolute; inset: 0; border-radius: 28px; background: #2a2622; content: ""; }
.xc__edge::before { transform: translateZ(1px); }
.xc__edge::after { transform: translateZ(-1px); }
@media (prefers-reduced-motion: reduce) { .xc__body { transition: none; } }
@media (max-width: 599px) {
  .xp__rail { grid-auto-columns: 78vw; }
  /* phones: the number and the step name read small against a card that
     fills most of the screen */
  .xp__no { --d: 64px; font-size: 27px; }
  .xp__over h3 { font-size: 36px; }
}

/* ── Case study (index #stories), tests/bartow5.html 22 ────────────────
   "Case study" takes the big centred title (.cine__title, as How we work);
   Bartow County drops to a one-line label; the story runs on between two
   prints — staggered, the right one smaller and lower — and fades out
   rather than stopping on an ellipsis. */
.case { display: grid; justify-items: center; text-align: center; }
.case__title { margin-bottom: var(--s-4); }
.case__place { display: flex; flex-wrap: wrap; justify-content: center; gap: 4px 10px; font-size: clamp(15px, 1.4vw, 18px); line-height: 1.4; }
/* the site ships one weight; a <b> here was being faux-bolded by the browser */
.case__place b { font-weight: 400; }
.case__place span { color: var(--fg-60); }
.case__place span::before { content: "·"; margin-right: 10px; }
.case__row {
  display: grid; grid-template-columns: 1fr auto 1fr; align-items: start; gap: clamp(var(--s-8), 5vw, 88px);
  width: 100%; margin-top: var(--gap-title-block);
}
.case__story {
  max-width: 26ch;
  font-size: clamp(22px, 2.7vw, 38px); line-height: 1.18; letter-spacing: -0.035em;
  color: var(--fg); text-wrap: pretty;
  max-height: 9.8em; overflow: hidden;
  /* fades harder toward the foot and is fully clear before the clipped last
     line, so no half-cut letters show */
  -webkit-mask-image: linear-gradient(to bottom, #000 38%, rgba(0, 0, 0, .35) 66%, transparent 86%);
  mask-image: linear-gradient(to bottom, #000 38%, rgba(0, 0, 0, .35) 66%, transparent 86%);
}
.case__print {
  width: clamp(90px, 14vw, 200px); aspect-ratio: 4 / 5; object-fit: cover;
  border-radius: 14px; border: 6px solid #fff;
  box-shadow: 0 14px 30px -14px rgba(19, 15, 12, .5);
}
.case__print--l { justify-self: end; margin-top: -6%; transform: rotate(-8deg); }
.case__print--r { justify-self: start; width: clamp(76px, 11.5vw, 164px); margin-top: 40%; transform: rotate(7deg); }
.case__ctas { margin-top: var(--s-6); }
.case__ctas .btn span { margin-left: var(--s-2); }
/* phone: no margins to hold the prints, so they sit side by side above the
   story, still staggered */
@media (max-width: 699px) {
  /* the overlapping pair sits as one group on the centre line: empty
     columns either side take the slack */
  .case__row { grid-template-columns: 1fr auto auto 1fr; grid-template-areas: ". l r ." "s s s s"; column-gap: 0; row-gap: var(--s-8); }
  .case__print--l { grid-area: l; width: 150px; margin-top: 0; translate: 16px 0; }
  /* the smaller print slides in over the first one's corner: a little pile
     rather than two photos side by side */
  /* translate, not margin: the overlap's negative margin and the tilt left the
     pair visibly 16px left of centre */
  .case__print--r { grid-area: r; width: 128px; margin-top: 28px; margin-left: -28px; position: relative; z-index: 1; translate: 16px 0; }
  .case__story { grid-area: s; justify-self: center; }
  .case__place { flex-direction: column; align-items: center; gap: 0; }
  .case__place span::before { content: none; }
}

html.is-ssheet { overflow: hidden; }
.ssheet { position: fixed; inset: 0; z-index: 200; --fg: #130F0C; --bg: #FFFFFF; --fg-60: rgba(19, 15, 12, .6); color: #130F0C; }
.ssheet__back { position: absolute; inset: 0; background: rgba(19, 15, 12, .5); opacity: 0; transition: opacity .45s var(--ease); }
.ssheet.is-in .ssheet__back { opacity: 1; }
.ssheet__panel {
  position: absolute; left: var(--s-3); right: var(--s-3); top: var(--s-4); bottom: 0;
  overflow-y: auto; overscroll-behavior: contain;
  border-radius: var(--r-md) var(--r-md) 0 0;
  background: #FFFFFF;
  transform: translateY(102%);
  transition: transform .55s cubic-bezier(.2, .9, .2, 1);
}
.ssheet.is-in .ssheet__panel { transform: none; }
/* desktop: a centred sheet a little wider than a phone, not edge to edge */
@media (min-width: 900px) {
  .ssheet__panel { left: 50%; right: auto; width: min(560px, calc(100vw - 2 * var(--s-12))); margin-left: calc(min(560px, calc(100vw - 2 * var(--s-12))) / -2); top: var(--s-8); }   /* a touch wider than a phone */
}
.ssheet__x { position: sticky; float: right; top: var(--s-4); margin: var(--s-4) var(--s-4) 0 0; z-index: 3; width: 44px; height: 44px; border: 0; border-radius: 50%; background: #F1EFEA; color: #130F0C; font-size: 14px; cursor: pointer; }
.ssheet__bar { position: sticky; top: 0; z-index: 2; height: 3px; }
/* iOS grabber: drag between full and half height, down to close (story-sheet.js) */
.ssheet__grab {
  position: sticky; top: 0; z-index: 4;
  display: grid; place-items: center;
  width: 140px; height: 26px; margin: 0 auto -26px;   /* narrow: clear of the ✕ */
  touch-action: none; cursor: grab;
}
.ssheet__grab:active { cursor: grabbing; }
.ssheet__grab i { width: 38px; height: 5px; border-radius: 3px; background: rgba(19, 15, 12, .22); }
.ssheet__bar i { display: block; height: 100%; background: #130F0C; transform-origin: 0 50%; transform: scaleX(0); }
/* --s-14 top, not --s-16: there is no 16 step, and one undefined token threw
   out the whole padding — the buttons sat flush on the sheet's bottom edge */
.ssheet__in { max-width: 680px; margin: 0 auto; padding: var(--s-14) var(--s-6) calc(var(--s-20) + env(safe-area-inset-bottom, 0px)); }
.ssheet__kicker { font-size: var(--t-caption); color: var(--fg-60); }
.ssheet__title { margin-top: var(--s-3); font-size: clamp(36px, 4.4vw, 60px); line-height: 1; letter-spacing: -0.05em; font-weight: 400; }
.ssheet__quote { margin: var(--s-8) 0 0; font-size: clamp(20px, 2vw, 24px); line-height: 1.35; letter-spacing: -0.02em; }
.ssheet__quote cite { display: block; margin-top: var(--s-3); font-style: normal; font-size: var(--t-caption); color: var(--fg-60); }
/* David's words larger and tighter, the opening mark hung into the margin,
   and his face beside his name */
.ssheet__quote { margin-top: var(--s-12); font-size: clamp(24px, 5.4vw, 30px); line-height: 1.2; letter-spacing: -0.035em; text-indent: -.38em; color: #130F0C; }
.ssheet__quote cite { display: flex; align-items: center; gap: 10px; margin-top: var(--s-5); text-indent: 0; font-family: var(--font); font-size: 15px; color: #130F0C; }
.ssheet__quote cite::before { content: ""; flex: none; width: 36px; height: 36px; border-radius: 50%; background: url("assets/collage/david-listen-1200.webp") 50% 14% / 220% auto no-repeat; }
.ssheet__in > p { margin-top: var(--s-6); font-size: 17px; line-height: 1.6; color: rgba(19, 15, 12, .8); }
.ssheet__in > p.ssheet__turn { font-size: clamp(24px, 2.6vw, 32px); line-height: 1.15; letter-spacing: -0.035em; color: #130F0C; }
.ssheet__in > img, .ssheet__in figure img { display: block; width: 100%; border-radius: var(--r-md); margin-top: var(--s-8); }
.ssheet__in figure { margin: 0; }
/* the story opens as a magazine cover: David full-bleed to the sheet's top
   and sides, the title in white on him over a fade dark enough to read */
.ssheet__cover {
  position: relative; aspect-ratio: 4 / 5;
  margin: calc(var(--s-14) * -1 - 3px) calc(var(--s-6) * -1) var(--s-4);
}
.ssheet__in > .ssheet__cover img { position: absolute; inset: 0; width: 100%; height: 100%; margin: 0; border-radius: 0; object-fit: cover; object-position: 50% 15%; }
.ssheet__cover::after {
  content: ""; position: absolute; inset: 0;
  background: linear-gradient(transparent 28%, rgba(0, 0, 0, .38) 52%, rgba(0, 0, 0, .86));
}
.ssheet__covertxt { position: absolute; left: var(--s-6); right: var(--s-6); bottom: var(--s-8); z-index: 1; color: #fff; }
.ssheet__covertxt .ssheet__kicker { color: rgba(255, 255, 255, .78); }
.ssheet__covertxt .ssheet__title { font-size: clamp(38px, 10.5vw, 56px); }
.ssheet__in figcaption { margin-top: var(--s-2); font-size: var(--t-caption); color: var(--fg-60); }
.ssheet__in > p.ssheet__rk { margin-top: var(--s-12); font-size: var(--t-caption); color: var(--fg-60); }
.ssheet__nums { display: flex; flex-wrap: wrap; gap: var(--s-8); margin-top: var(--s-3); }
.ssheet__nums div { display: grid; max-width: 240px; }
.ssheet__nums b { font-weight: 400; font-size: clamp(56px, 6vw, 80px); line-height: 1; letter-spacing: -0.05em; }
.ssheet__nums span { font-size: var(--t-caption); line-height: 1.35; margin-top: 6px; color: var(--fg-60); }
.ssheet__ctas { display: flex; flex-wrap: wrap; gap: var(--s-3); margin-top: var(--s-12); }
/* Back's arrow: drawn, not the ← glyph (which sat low and thin), cap-height
   tall and on the label's centre line */
.btn__arrow { width: .95em; height: .76em; margin-right: .45em; flex: none; fill: none; stroke: currentColor; stroke-width: 1.8; stroke-linecap: round; stroke-linejoin: round; translate: 0 -.04em; }
@media (prefers-reduced-motion: reduce) { .ssheet__panel, .ssheet__back { transition: none; } }

/* ── Get in touch on phones: a card, and the forms in a bottom sheet ───────
   Under 700px #contact shows .cpick (heading, one line, two buttons) and
   script.js moves .cform — the switch and both forms — into .fsheet, so the
   section stops taking a screen and a half of scroll. Above 700px .cform
   sits back in the section and .cpick is hidden. */
.cpick { display: none; }
.cpick__btns { display: flex; flex-wrap: wrap; gap: var(--s-3); margin-top: var(--s-8); }
@media (max-width: 699px) {
  .cpick { display: block; }
  /* Whisper.exe drops level with the short Volunteer button, clear of the long one */
  #contact .sprinkle--win95 { bottom: var(--s-8); }
  .contact__inner--tabs > .cform { display: none; }
}

html.is-fsheet { overflow: hidden; }
.fsheet {
  position: fixed; inset: 0; z-index: 200;
  color: #130F0C; --fg: #130F0C; --bg: #FFFFFF;
  --fg-60: color-mix(in srgb, #130F0C 62%, transparent);
}
.fsheet[hidden] { display: none; }
.fsheet__back { position: absolute; inset: 0; background: rgba(19, 15, 12, .5); opacity: 0; transition: opacity .45s var(--ease); }
.fsheet.is-in .fsheet__back { opacity: 1; }
.fsheet__panel {
  position: absolute; left: 0; right: 0; bottom: 0;
  max-height: calc(100dvh - var(--s-6));
  overflow-y: auto; overscroll-behavior: contain;
  border-radius: 28px 28px 0 0;
  background: #FFFFFF;
  transform: translateY(102%);
  transition: transform .55s cubic-bezier(.2, .9, .2, 1);
}
.fsheet.is-in .fsheet__panel { transform: none; }
.fsheet__panel:focus { outline: none; }
.fsheet__grab {
  position: sticky; top: 0; z-index: 2;
  display: grid; place-items: center;
  width: 140px; height: 26px; margin: 0 auto -26px;
  touch-action: none; cursor: grab;
}
.fsheet__grab i { width: 38px; height: 5px; border-radius: 3px; background: rgba(19, 15, 12, .22); }
.fsheet__close {
  position: sticky; float: right; top: var(--s-3); z-index: 3;
  margin: var(--s-3) var(--s-3) 0 0;
  display: grid; place-items: center;
  width: 44px; height: 44px; border: 0; border-radius: 50%;
  background: color-mix(in srgb, #130F0C 8%, transparent); color: #130F0C; cursor: pointer;
}
.fsheet__close svg { width: 18px; height: 18px; fill: none; stroke: currentColor; stroke-width: 1.8; stroke-linecap: round; }
.fsheet__body { padding: var(--s-8) var(--pad) calc(var(--s-8) + env(safe-area-inset-bottom, 0px)); }
/* no switch in the sheet: each button on the card opens its own form, so
   the sheet shows just that one; the heading starts level with the ✕ */
.fsheet .seg { display: none; }
.fsheet .pane .h1 { padding-right: 52px; }   /* only the heading sits beside the ✕ */
.fsheet .contact__lede { max-width: none; }
.fsheet .pane { display: grid; gap: var(--s-8); }
.fsheet .pane[hidden] { display: none; }
.fsheet .h1 { font-size: 32px; line-height: 1.05; }
.fsheet .contact__lede { margin-top: var(--s-3); font-size: 15px; }
.fsheet .form { gap: var(--s-4); }
.fsheet .field textarea { min-height: 96px; }
.fsheet .form__actions .btn { width: 100%; }
/* nothing inside waits for a scroll reveal */
.fsheet .reveal { opacity: 1; transform: none; transition: none; }
/* field chrome pinned to the sheet ink, as in .contact--sheet */
.fsheet .field label { color: color-mix(in srgb, #130F0C 62%, transparent); }
.fsheet .field input,
.fsheet .field textarea {
  color: #130F0C;
  border-color: color-mix(in srgb, #130F0C 28%, transparent);
  background: color-mix(in srgb, #130F0C 8%, transparent);
}
.fsheet .field input:focus,
.fsheet .field textarea:focus { border-color: #130F0C; }
@media (prefers-reduced-motion: reduce) { .fsheet__panel, .fsheet__back { transition: none; } }

/* ── The parent's note on an index card (script.js, [data-icard]) ─────────
   A ruled 3x5 in blue ballpoint that turns in 3D. Everything inside is sized
   off the card's own width (cqi), so the writing sits on the rules at any
   size. Landscape 5:3 on wide screens, upright 3:5 on phones. */
.icard {
  --cw: min(500px, 78vw);   /* smaller again (612 / 81vw before) */
  position: relative; width: var(--cw); aspect-ratio: 5 / 3; margin-inline: auto;
  rotate: -3deg;            /* rests leaning left; the 3D turn happens inside */
  perspective: 1500px; cursor: grab; touch-action: pan-y; container-type: inline-size;
  -webkit-user-select: none; user-select: none; outline: none;
  --fs: 4.5cqi; --L: 5.6cqi; --band: 11cqi; --pad: 6.5cqi;
}
.icard.is-drag { cursor: grabbing; }
.icard:focus-visible .icard__face { box-shadow: 0 0 0 3px var(--fg), 0 24px 40px -18px rgba(19, 15, 12, .45); }
@media (max-width: 699px) {
  .icard { --cw: min(306px, 77vw); aspect-ratio: 100 / 157.1;   /* 3:5 less one ruled row (--L) at the foot */ --fs: 8cqi; --L: 9.6cqi; --band: 16cqi; --pad: 8cqi; }
}
.icard__body {
  position: absolute; inset: 0; transform-style: preserve-3d;
  transform: rotateX(var(--rx, 0deg)) rotateY(var(--ry, 0deg));
}
.icard__face {
  position: absolute; inset: 0; border-radius: 1cqi; overflow: hidden;
  backface-visibility: hidden; -webkit-backface-visibility: hidden;
  background:
    radial-gradient(120% 90% at 20% 0%, rgba(255, 255, 255, .55), transparent 60%),
    radial-gradient(80% 60% at 100% 100%, rgba(140, 110, 60, .08), transparent 70%),
    #FBF7EC;
  box-shadow: 0 24px 40px -18px rgba(19, 15, 12, .45), 0 2px 4px rgba(19, 15, 12, .12), inset 0 0 0 1px rgba(19, 15, 12, .05);
}
/* the rules: red margin line under the header band, pale blue lines below */
.icard__front {
  padding: calc(var(--band) + var(--L) * .25) var(--pad) var(--pad);
  background:
    linear-gradient(rgba(214, 72, 72, .7), rgba(214, 72, 72, .7)) 0 var(--band) / 100% 2px no-repeat,
    repeating-linear-gradient(to bottom, transparent 0 calc(var(--L) - 1px), rgba(98, 150, 214, .42) calc(var(--L) - 1px) var(--L))
      0 calc(var(--band) + var(--L) * .25) / 100% 100% no-repeat,   /* each rule under its own line of writing */
    radial-gradient(120% 90% at 20% 0%, rgba(255, 255, 255, .55), transparent 60%),
    #FBF7EC;
}
.icard__front blockquote,
.icard__front figcaption {
  font-family: "Reenie Beanie", "Bradley Hand", cursive;
  font-size: var(--fs); line-height: var(--L); letter-spacing: .01em;
  color: #1F3E9C; text-wrap: pretty;
}
/* the card is far down and fixed in shape: its handwriting face (28KB)
   waits for the first paint (html.is-cold, cleared by script.js) instead
   of downloading ahead of the headline */
html.is-cold .icard__front blockquote,
html.is-cold .icard__front figcaption { font-family: "Bradley Hand", cursive; }
.icard__front blockquote { rotate: -.6deg; transform-origin: 0 0; }
.icard__front figcaption { margin-top: calc(var(--L) * .2); text-align: right; color: #2A4AAE; rotate: -1.2deg; }
.icard__back { rotate: y 180deg; display: grid; place-items: center; }
.icard__stamp { width: 20%; aspect-ratio: 351.4 / 92.3; fill: #130F0C; rotate: -4deg; }
/* the back: the logo sketched in pencil — two wobbly passes of the mark,
   the second a touch off and lighter, like a hand going over it again */
.icard__stamp--sketch { width: 30%; fill: #2b2a28; overflow: visible; }
.icard__stamp--sketch .s2 { opacity: .6; transform: translate(4px, -3px) rotate(1.4deg); transform-origin: center; }
.icard__stamp--sketch .s3 { opacity: .35; transform: translate(-3px, 2.5px) rotate(-1deg); transform-origin: center; }
.icard__stamp--sketch { fill: #3a3835; }
@media (prefers-reduced-motion: reduce) { .icard { cursor: pointer; } }

/* ── Overheard: students in two parallax columns (script.js, [data-ohcols]) ──
   The live page's black plaques, each with the student's round headshot
   above their words. The right column starts lower and drifts up faster as
   the page scrolls. Two-up on phones too, with smaller type. */
.ohcols { display: grid; grid-template-columns: 1fr 1fr; gap: var(--s-6); max-width: 960px; margin: var(--s-8) auto 0; align-items: start; padding-bottom: 0; }
.ohcol { display: grid; gap: var(--s-6); will-change: transform; }
.ohcol:nth-child(2) { margin-top: 180px; }
.ohcard {
  margin: 0; padding: var(--s-6); border-radius: var(--r-md);
  background: #130F0C; color: #FFFFFF; --fg: #FFFFFF;
}
.ohcard__face { display: block; width: 56px; height: 56px; border-radius: 50%; object-fit: cover; margin-bottom: var(--s-5); background: #2a2622; }
.ohcard blockquote { font-family: var(--font); font-size: 1.125rem; line-height: 1.4; letter-spacing: -0.02em; }
.ohcard figcaption { margin-top: var(--s-5); font-size: 15px; color: rgba(255, 255, 255, .72); }
.ohcard--ask { display: grid; gap: var(--s-6); background: #FFFFFF; color: #130F0C; box-shadow: inset 0 0 0 1px rgba(19, 15, 12, .14); text-decoration: none; }
.ohcard--ask p { font-size: 1.375rem; line-height: 1.25; letter-spacing: -0.02em; }
.ohcard--ask span { display: inline-flex; align-items: center; gap: 10px; font-size: 15px; }
.ohcard--ask svg { width: 20px; height: 20px; fill: none; stroke: currentColor; stroke-width: 3; }
@media (max-width: 699px) {
  .ohcols { gap: var(--s-3); padding-bottom: 16px; }
  .ohcol { gap: var(--s-3); }
  .ohcol:nth-child(2) { margin-top: 96px; }
  .ohcard { padding: var(--s-5) var(--s-4); }
  .ohcard__face { width: 44px; height: 44px; margin-bottom: var(--s-3); }
  .ohcard blockquote { font-size: 15px; line-height: 1.38; }
  .ohcard figcaption { margin-top: var(--s-3); font-size: 13px; }
  .ohcard--ask p { font-size: 17px; }
}
@media (prefers-reduced-motion: reduce) { .ohcol { will-change: auto; } }

/* the dive line's "Whisper": the wordmark in 3D chrome (chrome-logo.js).
   The canvas box is inset around the mark so the turned logo has room. */
.b3-logo { position: relative; display: inline-block; width: 3.9em; aspect-ratio: 351.4 / 92.3; vertical-align: -0.12em; margin-inline: .08em; }
.b3-logo__flat { display: block; width: 100%; height: 100%; fill: currentColor; rotate: -6deg; }
.b3-logo.is-chrome .b3-logo__flat { visibility: hidden; }
.b3-logo__gl { position: absolute; inset: -70% -16%; pointer-events: none; }
.b3-logo__gl .berg__chrome { display: block; width: 100%; height: 100%; }

/* no boxes: each student is just their face, their words and their name on
   the page ground; ink follows the ground as it changes */
.ohcard { --fg: inherit; padding: 0; border-radius: 0; background: none; color: var(--fg); }
.ohcard figcaption { color: var(--fg-60); }
.ohcols { row-gap: 0; }
.ohcol { gap: var(--s-14); }
.ohcard blockquote { font-size: clamp(1.125rem, 1.6vw, 1.375rem); line-height: 1.38; }
.ohcard--ask { background: none; box-shadow: none; color: var(--fg); padding-top: var(--s-6); border-top: 1px solid var(--rule); border-radius: 0; }
.ohcard--ask span { text-decoration: underline; text-underline-offset: 4px; }
@media (max-width: 699px) {
  .ohcard { padding: 0; }
  .ohcol { gap: var(--s-10, 40px); }
  .ohcard blockquote { font-size: 15px; }
}

/* centred: face, words and name on one axis, the words held to a narrower
   measure inside each column */
.ohcard { display: flex; flex-direction: column; align-items: center; text-align: center; }
.ohcard__face { margin-inline: auto; }
.ohcard blockquote { max-width: 30ch; text-wrap: balance; }
/* the line that lands, in the real bold */
.ohcard blockquote strong { font-family: "TeX Gyre Heros Bold", var(--font); font-weight: 400; letter-spacing: -0.03em; }
.ohcard--ask { align-items: center; text-align: center; }
.ohcard--ask p { max-width: 20ch; }
@media (max-width: 699px) { .ohcard blockquote { max-width: 18ch; } }

/* Overheard, scattered: each student at their own place across a wide field
   (--x), overlapping the one before a little, drifting at their own speed
   (script.js, [data-ohfield]) — not two lock-step columns */
.ohfield { position: relative; max-width: 1240px; margin: 100px auto 0; }   /* room under the title so the first face never rises into it */
.ohfield .ohcard { width: 36%; margin-left: var(--x, 0); will-change: transform; }
.ohfield .ohcard + .ohcard { margin-top: -30px; }   /* a little tuck, never a collision */
.ohfield .ohcard blockquote { max-width: 34ch; }
.ohfield .ohcard--ask { margin-top: var(--s-12) !important; }
@media (max-width: 699px) {
  .ohfield { margin-top: 56px; }
  .ohfield .ohcard { width: 80%; }
  .ohfield .ohcard:nth-child(odd) { margin-left: 0; }
  .ohfield .ohcard:nth-child(even) { margin-left: 20%; }
  .ohfield .ohcard + .ohcard { margin-top: 56px; }
  .ohfield .ohcard blockquote { max-width: 26ch; }
}
@media (prefers-reduced-motion: reduce) { .ohfield .ohcard { will-change: auto; } }


/* How we work: rows on wide screens, the What you can expect 3D cards on phones */
.how__xc { display: none; }
@media (max-width: 767px) {
  .how .bands { display: none; }
  .how__xc { display: block; margin-top: var(--gap-title-block, var(--s-8)); }
}

/* the chrome hand wears the REACH OUT pill (.wall__cursor) in place of the cursor */
[data-chrome-hand].is-reach, [data-chrome-hand].is-reach * { cursor: none; }

