/* ═══════════════════════════════════════════════════════════════════════════
   velisa.app — site.css   (v4, "The Calibrated Darkroom")

   A measuring instrument, not a brochure. Warm olive-black darkroom; real
   screenshots glow in hairline masks; every motion critically damped.

   LAWS THIS FILE OBEYS:
   · No content is hidden at rest. The only hiding rule is scoped to
     html[data-js="on"][data-anim="on"], and BOTH attributes are set by
     site.js — the same file that removes them (watchdog, 2500ms).
   · Section 3 (#band) ships its FINAL composed state as the CSS default:
     every progress var rests at 1. JS only rewinds and scrubs.
   · Ember appears in exactly six places: primary CTA buttons, the S3 band +
     its mono caption, the eyebrow live-dot + status chip, the footer's amber
     curve, form focus rings, and the scroll-progress hairline. Nowhere else.
   ═══════════════════════════════════════════════════════════════════════════ */


/* ─── 0 · FONTS — self-hosted latin subsets, strict-CSP safe ─────────────── */

@font-face {
  font-family: 'Newsreader';
  font-style: normal;
  font-weight: 400 500;
  font-display: swap;
  src: url('/assets/fonts/newsreader-var.woff2') format('woff2');
  unicode-range: U+0000-00FF, U+0131, U+0152-0153, U+02BB-02BC, U+02C6, U+02DA, U+02DC, U+0304, U+0308, U+0329, U+2000-206F, U+20AC, U+2122, U+2191, U+2193, U+2212, U+2215, U+FEFF, U+FFFD;
}

@font-face {
  font-family: 'Newsreader';
  font-style: italic;
  font-weight: 400 500;
  font-display: swap;
  src: url('/assets/fonts/newsreader-var-italic.woff2') format('woff2');
  unicode-range: U+0000-00FF, U+0131, U+0152-0153, U+02BB-02BC, U+02C6, U+02DA, U+02DC, U+0304, U+0308, U+0329, U+2000-206F, U+20AC, U+2122, U+2191, U+2193, U+2212, U+2215, U+FEFF, U+FFFD;
}

@font-face {
  font-family: 'IBM Plex Mono';
  font-style: normal;
  font-weight: 400;
  font-display: swap;
  src: url('/assets/fonts/plexmono-400.woff2') format('woff2');
  unicode-range: U+0000-00FF, U+0131, U+0152-0153, U+02BB-02BC, U+02C6, U+02DA, U+02DC, U+0304, U+0308, U+0329, U+2000-206F, U+20AC, U+2122, U+2191, U+2193, U+2212, U+2215, U+FEFF, U+FFFD;
}

@font-face {
  font-family: 'IBM Plex Mono';
  font-style: normal;
  font-weight: 500;
  font-display: swap;
  src: url('/assets/fonts/plexmono-500.woff2') format('woff2');
  unicode-range: U+0000-00FF, U+0131, U+0152-0153, U+02BB-02BC, U+02C6, U+02DA, U+02DC, U+0304, U+0308, U+0329, U+2000-206F, U+20AC, U+2122, U+2191, U+2193, U+2212, U+2215, U+FEFF, U+FFFD;
}

@font-face {
  font-family: 'Geist';
  font-style: normal;
  font-weight: 400 600;
  font-display: swap;
  src: url('/assets/fonts/geist-var.woff2') format('woff2');
  unicode-range: U+0000-00FF, U+0131, U+0152-0153, U+02BB-02BC, U+02C6, U+02DA, U+02DC, U+0304, U+0308, U+0329, U+2000-206F, U+20AC, U+2122, U+2191, U+2193, U+2212, U+2215, U+FEFF, U+FFFD;
}


/* ─── 1 · TOKENS ─────────────────────────────────────────────────────────── */

:root {
  --bg-0: #0B0A08;
  --bg-1: #151310;
  --bg-2: #1D1913;

  --hairline: rgba(237, 230, 220, .08);
  --hairline-strong: rgba(237, 230, 220, .14);

  --ink: #EDE6DC;
  --ink-2: #9A8F84;
  --ink-3: #5E564B;          /* decorative only: ledger dash, grey footer curve */
  --ink-3-text: #918779;     /* quietest TEXT tier — ≥4.5:1 on bg-0/1/2 (AA) */

  --ember: #F0A32E;
  --ember-deep: #B87A17;
  --band-fill: linear-gradient(180deg, rgba(240, 163, 46, .16), rgba(240, 163, 46, .04));

  --serif: 'Newsreader', ui-serif, Georgia, 'Times New Roman', serif;
  --mono: 'IBM Plex Mono', ui-monospace, 'SF Mono', Menlo, monospace;
  --sans: 'Geist', system-ui, -apple-system, 'Segoe UI', sans-serif;

  --r-outer: 28px;
  --r-inner: 20px;

  --wrap: 1120px;
  --sec-pad: clamp(80px, 13vw, 176px);

  --ease-out: cubic-bezier(0.23, 1, 0.32, 1);

  --nav-h: 60px;
}


/* ─── 2 · RESET + BASE ───────────────────────────────────────────────────── */

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

html {
  scroll-behavior: smooth;
  -webkit-text-size-adjust: 100%;
}

body {
  margin: 0;
  background: var(--bg-0);
  color: var(--ink);
  font-family: var(--sans);
  font-size: 16.5px;
  line-height: 1.55;
  font-weight: 400;
  -webkit-font-smoothing: antialiased;
  overflow-x: clip;
}

h1, h2, h3, p, ul, figure { margin: 0; }
ul { padding: 0; list-style: none; }
img { max-width: 100%; height: auto; display: block; }
a { color: inherit; }

[hidden] { display: none !important; }

::selection { background: rgba(237, 230, 220, .18); color: var(--ink); }

/* Form focus rings — ember (allowed place 5 of 6). */
:focus-visible {
  outline: 2px solid var(--ember);
  outline-offset: 2px;
}

.v-skip {
  position: absolute;
  left: 16px; top: 8px;
  z-index: 300;
  padding: 8px 14px;
  background: var(--bg-2);
  border: 1px solid var(--hairline-strong);
  border-radius: 8px;
  font-family: var(--mono);
  font-size: 12px;
  text-decoration: none;
  transform: translateY(-300%);
}
.v-skip:focus-visible { transform: none; }

/* Grain — fixed, ≤0.025 opacity, no pointer capture. The colon in the data:
   URI's xmlns is percent-encoded (%3A): still a valid same-document data URI,
   never a network fetch. */
.v-grain {
  position: fixed;
  inset: 0;
  z-index: 240;
  pointer-events: none;
  opacity: calc(0.022 * (1 - var(--veil, 0) * 0.75));
  background-image: url("data:image/svg+xml,%3Csvg xmlns='http%3A//www.w3.org/2000/svg' width='160' height='160'%3E%3Cfilter id='n'%3E%3CfeTurbulence type='fractalNoise' baseFrequency='0.9' numOctaves='2' stitchTiles='stitch'/%3E%3C/filter%3E%3Crect width='160' height='160' filter='url(%23n)'/%3E%3C/svg%3E");
  background-size: 160px 160px;
}


/* ─── 3 · TYPE ROLES ─────────────────────────────────────────────────────── */

.v-h1 {
  font-family: var(--serif);
  font-weight: 500;
  font-size: clamp(40px, 4.4vw, 63px);
  line-height: 1.02;
  letter-spacing: -0.015em;
}

.v-h2 {
  font-family: var(--serif);
  font-weight: 500;
  font-size: clamp(28px, 3.4vw, 40px);
  line-height: 1.12;
  letter-spacing: -0.01em;
}

.v-body {
  color: var(--ink-2);
  max-width: 34ch;   /* hard rule — every body block */
}
.v-body a { color: var(--ink); text-underline-offset: 3px; }

/* Mono label: ALL-CAPS, 11px, 0.18em tracking. */
.v-eyebrow, .v-caption, .v-chip, .v-bandsec__pm,
.v-ledger__line, .v-recital__line, .v-collect__flex, .v-themes__caption {
  font-family: var(--mono);
  font-weight: 500;
  font-size: 11px;
  letter-spacing: 0.18em;
  text-transform: uppercase;
}

.v-eyebrow { color: var(--ink-2); margin-bottom: 20px; }

/* Live-dot — ember (allowed place 3 of 6). */
.v-eyebrow__dot {
  display: inline-block;
  width: 6px; height: 6px;
  margin-right: 10px;
  border-radius: 50%;
  background: var(--ember);
  vertical-align: 1px;
}

/* Provenance plate — 10px mono, quiet but AA-legible. */
.v-plate {
  display: block;
  font-family: var(--mono);
  font-weight: 400;
  font-size: 10px;
  letter-spacing: 0.16em;
  text-transform: uppercase;
  color: var(--ink-3-text);
  margin-top: 10px;
}

.v-caption { display: block; color: var(--ink-2); margin-top: 14px; }
.v-caption + .v-plate { margin-top: 6px; }

.v-aphorism {
  font-family: var(--serif);
  font-style: italic;
  font-weight: 400;
  font-size: 19px;
  color: var(--ink-2);
  max-width: 38ch;
}


/* ─── 4 · LAYOUT PRIMITIVES ──────────────────────────────────────────────── */

.v-wrap {
  max-width: var(--wrap);
  margin-inline: auto;
  padding-inline: 24px;
}

.v-section { padding-block: var(--sec-pad); }

.v-sechead { margin-bottom: clamp(40px, 6vw, 72px); }
.v-sechead .v-h2 { margin-bottom: 16px; }


/* ─── 5 · THE DARKROOM SYSTEM — masks, bezels, cards ─────────────────────── */

/* The screenshot IS the device: hairline mask, no CSS bezel. */
.v-mask {
  border-radius: var(--r-outer);
  overflow: hidden;
  border: 1px solid var(--hairline-strong);
  background: var(--bg-1);
  /* depth by inset top-light, never drop shadow */
  box-shadow: inset 0 1px 0 rgba(255, 255, 255, .06);
}
.v-mask img { width: 100%; }

/* Half-height crop (S6 themes). */
.v-mask--half { aspect-ratio: 540 / 560; }
.v-mask--half img { height: 100%; object-fit: cover; object-position: top; }

/* Double bezel: outer wash + hairline + 8px pad, inner plate + top-light. */
.v-bezel {
  background: rgba(255, 255, 255, .03);
  border: 1px solid var(--hairline);
  border-radius: var(--r-outer);
  padding: 8px;
}
.v-bezel > * {
  background: var(--bg-2);
  border-radius: var(--r-inner);
  box-shadow: inset 0 1px 0 rgba(255, 255, 255, .06);
}


/* ─── 6 · BUTTONS + FORMS ────────────────────────────────────────────────── */

/* Primary CTA — ember (allowed place 1 of 6). The ::before layer is the
   directional fill: it sweeps ember-deep from the cursor's entry side
   (--fill-o set by site.js), z-index -1 so the label always paints above. */
.v-btn {
  position: relative;
  isolation: isolate;
  display: inline-block;
  padding: 13px 22px;
  border: 0;
  border-radius: 12px;
  background: var(--ember);
  color: #14100A;
  font-family: var(--sans);
  font-size: 15px;
  font-weight: 600;
  line-height: 1.2;
  text-decoration: none;
  cursor: pointer;
  overflow: hidden;
}
.v-btn::before {
  content: "";
  position: absolute;
  inset: 0;
  z-index: -1;
  background: var(--ember-deep);
  transform: scaleX(0);
  transform-origin: var(--fill-o, left center);
}
.v-btn:disabled { opacity: .6; cursor: default; }

.v-btn--ghost {
  background: transparent;
  border: 1px solid var(--hairline-strong);
  color: var(--ink);
}
.v-btn--ghost::before { content: none; }

@media (hover: hover) and (pointer: fine) {
  .v-btn::before { transition: transform .3s var(--ease-out); }
  .v-btn:hover::before { transform: scaleX(1); }
  .v-btn--ghost:hover { border-color: rgba(237, 230, 220, .3); }
  .v-btn:active, .v-ghostlink:active, .v-dock__dismiss:active {
    transform: scale(0.97);
    transition: transform .14s var(--ease-out);
  }
  /* Plate stamp (micro 2) — an embossing die: press in fast, spring out. */
  .v-plate { transition: transform .28s var(--ease-out); }
  .v-plate:hover { transform: translateY(1px); transition-duration: .12s; }
}

/* Spent ink (micro 3) — hooked off the waitlist module's own states only:
   the fill holds at 100% while pending; on a terminal outcome it drains
   left→right as the status line settles in. No JS beyond what exists. */
.v-btn[aria-busy="true"]::before { transform: scaleX(1); }
form[data-outcome] .v-btn::before {
  transform: scaleX(0);
  transform-origin: right center;
  transition: transform .24s var(--ease-out);
}

.v-ghostlink {
  display: inline-block;
  font-family: var(--mono);
  font-size: 12px;
  letter-spacing: 0.14em;
  text-transform: uppercase;
  color: var(--ink-2);
  text-decoration: none;
  border-bottom: 1px solid var(--hairline-strong);
  padding-bottom: 4px;
}
.v-ghostlink:hover { color: var(--ink); }

/* Waitlist form */
.v-waitlist { max-width: 560px; }

.v-waitlist__row {
  display: flex;
  flex-wrap: wrap;
  gap: 12px;
  align-items: flex-end;
}

.v-field { flex: 1 1 160px; min-width: 150px; position: relative; }

/* Detent focus (micro 1) — a 1px amber tick seats into the field's left edge
   while it holds focus. Part of the focus-ring treatment (ember slot 5). */
.v-field::after {
  content: "";
  position: absolute;
  left: -8px; bottom: 4px;
  width: 1px; height: 38px;
  background: var(--ember);
  transform: scaleY(0);
  transform-origin: bottom;
  transition: transform .16s var(--ease-out);
}
.v-field:focus-within::after { transform: scaleY(1); }

.v-field__label {
  display: block;
  font-family: var(--mono);
  font-size: 11px;
  letter-spacing: 0.18em;
  text-transform: uppercase;
  color: var(--ink-2);
  margin-bottom: 8px;
}

.v-field__input {
  width: 100%;
  padding: 12px 14px;
  background: var(--bg-1);
  border: 1px solid var(--hairline-strong);
  border-radius: 12px;
  color: var(--ink);
  font-family: var(--sans);
  font-size: 16px;
  line-height: 1.2;
}
.v-field__input:focus-visible {
  outline: none;
  border-color: var(--ember);
  box-shadow: 0 0 0 1px var(--ember);
}
.v-field__input[aria-invalid="true"] { border-color: rgba(240, 100, 60, .6); }

.v-field__err {
  font-size: 13px;
  color: #E08A6A;
  margin-top: 6px;
}

.v-waitlist__status {
  font-size: 15px;
  color: var(--ink);
  border: 1px solid var(--hairline-strong);
  border-radius: 12px;
  background: var(--bg-2);
  padding: 12px 14px;
  margin-bottom: 14px;
}
.v-waitlist__status[data-tone="bad"] { color: #E08A6A; }

/* Honeypot — visually removed, never display:none (bots check that). */
.v-field--trap {
  position: absolute;
  left: -9999px;
  width: 1px; height: 1px;
  overflow: hidden;
}

.v-cta-note {
  font-size: 13px;
  color: var(--ink-3-text);
  max-width: 44ch;
  margin-top: 16px;
}
.v-cta-note a { color: var(--ink-2); }

.v-waitlist__hint {
  font-size: 13px;
  color: var(--ink-3-text);
  margin-top: 6px;
}
.v-waitlist__hint a { color: var(--ink-2); }

.v-cta { display: flex; flex-wrap: wrap; gap: 12px; align-items: center; }


/* ─── 7 · NAV + PROGRESS ─────────────────────────────────────────────────── */

.v-nav {
  position: fixed;
  top: 0; left: 0; right: 0;
  z-index: 200;
  background: rgba(11, 10, 8, .78);
  -webkit-backdrop-filter: blur(14px);
  backdrop-filter: blur(14px);
  border-bottom: 1px solid var(--hairline);
}

.v-nav__in {
  max-width: var(--wrap);
  margin-inline: auto;
  padding-inline: 24px;
  height: var(--nav-h);
  display: flex;
  align-items: center;
  gap: 20px;
}

.v-nav__wordmark {
  font-family: var(--serif);
  font-weight: 500;
  font-size: 21px;
  letter-spacing: -0.01em;
  text-decoration: none;
}

.v-chip {
  margin-inline: auto;
  color: var(--ink-2);
  border: 1px solid var(--hairline);
  border-radius: 999px;
  padding: 6px 14px;
  white-space: nowrap;
}
.v-chip__live { display: none; }
body[data-release="live"] .v-chip__pre { display: none; }
body[data-release="live"] .v-chip__live { display: inline; color: var(--ember); }

.v-nav__links { display: flex; gap: 22px; }
.v-nav__links a {
  font-size: 14px;
  color: var(--ink-2);
  text-decoration: none;
}
.v-nav__links a:hover { color: var(--ink); }

/* Scroll-progress hairline — ember (allowed place 6 of 6). Chrome only:
   width 0 without JS is a missing ornament, not missing content. The bar
   scales; the seismograph tick (part of this same slot — it IS the hairline's
   head) rides the same var and leans with live scroll velocity. Both dim with
   the Act 6 veil. */
.v-progress {
  position: relative;
  height: 1px;
  opacity: calc(1 - var(--veil, 0) * 0.75);
}
.v-progress__bar {
  position: absolute;
  inset: 0;
  background: var(--ember);
  transform: scaleX(var(--scroll-p, 0));
  transform-origin: left center;
}
.v-progress__tick {
  display: none;
  position: absolute;
  top: -4px; left: -2px;
  width: 2px; height: 10px;
  background: var(--ember);
  transform: translateX(calc(var(--scroll-p, 0) * 100vw + var(--lean, 0px)))
             skewX(var(--skew, 0deg));
}
html[data-js="on"]:not([data-motion="reduced"]) .v-progress__tick { display: block; }


/* ─── 8 · S1 · HERO ──────────────────────────────────────────────────────── */

.v-hero {
  min-height: 100vh;
  display: flex;
  align-items: center;
  padding-block: calc(var(--nav-h) + 48px) 64px;
}

/* ACT 1b · the cover. Desktop, motion-full only: the hero holds its ground
   (sticky) while the rest of the page slides over it — receding and dimming
   as it is covered (site.js writes --hx 0→1 over the hero's exit). CSS rest
   (--hx absent) = full presence; JS-off / reduced-motion = static flow. */
@media (min-width: 1025px) {
  html[data-motion="full"] .v-hero {
    position: sticky;
    top: 0;
    z-index: 1;
    transform-origin: 50% 30%;
    transform: scale(calc(1 - var(--hx, 0) * 0.05));
    opacity: calc(1 - var(--hx, 0) * 0.6);
  }
  /* #collect is excluded from both cover rules: giving it position+z-index
     would make it a stacking context, trapping its content's z-151 below the
     fixed veil (z-150) — the whole section painted black. It sits far below
     the hero and never participates in the cover anyway. */
  html[data-motion="full"] main :where(.v-section:not(#collect), #what) { background: var(--bg-0); }
  html[data-motion="full"] main > :not(.v-hero, #collect) { position: relative; z-index: 2; }
  html[data-motion="full"] #what { border-top: 1px solid var(--hairline); }
}

.v-hero__grid {
  display: grid;
  grid-template-columns: minmax(0, 1.12fr) minmax(0, .88fr);
  gap: clamp(32px, 5vw, 72px);
  align-items: center;
  width: 100%;
}

.v-hero__sub { margin: 24px 0 32px; }

.v-line { display: block; overflow: hidden; }
.v-line__in { display: block; white-space: nowrap; }

.v-hero__ghost { margin-top: 28px; }

/* Ghost arrow drop — a released needle, never looping (micro 4). */
.v-ghostlink__arr { display: inline-block; transition: transform .3s var(--ease-out); }
.v-ghostlink:hover .v-ghostlink__arr { transform: translateY(6px); }

.v-hero__stage { display: flex; justify-content: center; }

.v-persp { perspective: 1200px; }

/* Act 1 — pointer depth. --px/--py rest at 0 (JS damped follower writes
   them); every consumer is a calc term inside an existing transform, so
   JS-off and reduced simply see the rest pose. */
#hero .v-h1 {
  transform: translate(calc(var(--px, 0) * -4px), calc(var(--py, 0) * -3px));
}
#hero .v-eyebrow,
.v-hero__device .v-plate {
  transform: translate(calc(var(--px, 0) * -2px), calc(var(--py, 0) * -1.5px));
}

.v-hero__device { width: min(400px, 100%); }
.v-hero__device .v-mask {
  transform: rotateX(calc(var(--hero-rx, 8deg) + var(--py, 0) * 1.2deg))
             rotateY(calc(var(--px, 0) * 1.8deg))
             translateY(var(--hero-ty, 0px));
  transform-origin: center bottom;
}
.v-hero__device .v-plate { text-align: center; }


/* ─── 9 · S2 · WHAT — the triptych ───────────────────────────────────────── */

.v-triptych {
  display: grid;
  grid-template-columns: repeat(3, 1fr);
  gap: clamp(16px, 2.5vw, 32px);
  align-items: start;
}


/* ─── 9b · ACT 3 · THE CARRIAGE (#what.is-rack — JS-applied, desktop only) ──
   One var --gal-p; the track and rail cursor consume it as transforms, the
   per-figure focus (caption opacity + column scale) is a pure calc
   derivation with per-column center constants --ci. */
#what.is-rack {
  padding-block: 0;
  height: 180vh;
}
#what.is-rack > .v-wrap {
  position: sticky;
  top: 0;
  height: 100vh;
  max-width: none;
  padding: 0;
  overflow: hidden;
}
#what.is-rack .v-sechead {
  position: absolute;
  top: calc(var(--nav-h) + 28px);
  left: max(24px, calc((100vw - var(--wrap)) / 2 + 24px));
  z-index: 2;
  margin: 0;
}
#what.is-rack .v-triptych {
  --colw: min(280px, calc((100vh - 480px) * 0.492));
  display: flex;
  align-items: center;
  gap: 40px;
  height: 100%;
  padding-inline: calc(50vw - var(--colw) / 2);
  padding-top: 150px;      /* clears the pinned header */
  padding-bottom: 70px;    /* clears the rail */
  transform: translateX(calc(var(--gal-p, 0) * var(--gal-max, 0px)));
}
#what.is-rack .v-triptych__col {
  --focus: min(1, max(calc((var(--gal-p, 0) - var(--ci, 0)) * 3),
                      calc((var(--ci, 0) - var(--gal-p, 0)) * 3)));
  flex: 0 0 var(--colw);
  transform: scale(calc(1 - var(--focus) * 0.03));
}
#what.is-rack .v-triptych__col--a { --ci: 0; }
#what.is-rack .v-triptych__col--b { --ci: 0.5; }
#what.is-rack .v-triptych__col--c { --ci: 1; }
#what.is-rack .v-triptych__col .v-caption { opacity: calc(1 - 0.65 * var(--focus)); }

/* The rail: 1px hairline, three etched ticks, a 2×12 ink cursor mirroring
   the same var. JS-injected chrome. */
.v-rail {
  position: absolute;
  bottom: 88px;   /* rides clear of the CTA dock */
  left: 50%;
  transform: translateX(-50%);
  width: 240px;
  height: 1px;
  background: var(--hairline-strong);
}
.v-rail__tick {
  position: absolute;
  top: -3px;
  width: 1px; height: 7px;
  background: var(--ink-3);
}
.v-rail__tick:nth-child(1) { left: 0; }
.v-rail__tick:nth-child(2) { left: 50%; }
.v-rail__tick:nth-child(3) { right: 0; }
.v-rail__cursor {
  position: absolute;
  top: -5px; left: 0;
  width: 2px; height: 12px;
  background: var(--ink);
  transform: translateX(calc(var(--gal-p, 0) * 238px));
}


/* ─── 10 · S3 · THE BAND — the signature ─────────────────────────────────── */

/* All progress vars rest at 1 = final composed state. JS scrubs --p 0→1;
   the derived vars split it into FIVE phases (Act 4 retime, 280vh):
     pre   (0→.10)    → approach: numeral firms, seed line reaches full
     open  (.10→.38)  → the split: fill scales, edges translate
     cardp (.38→.70)  → emission: the shot docks; fill defers over last 10%
     copyp (.70→.90)  → verdict: copy develops; aphorism last (.85→.90)
     fix   (.90→1)    → the fix: fill drains to structure, edges hold      */
.v-bandsec {
  --p: 1;
  --pre:   clamp(0, calc(var(--p) / 0.10), 1);
  --open:  clamp(0, calc((var(--p) - 0.10) / 0.28), 1);
  --cardp: clamp(0, calc((var(--p) - 0.38) / 0.32), 1);
  --copyp: clamp(0, calc((var(--p) - 0.70) / 0.20), 1);
  --fix:   clamp(0, calc((var(--p) - 0.90) / 0.10), 1);
  --defer: clamp(0, calc((var(--cardp) - 0.9) / 0.1), 1);
  --zone-h: min(55vh, calc(100vh - 330px));
  position: relative;
  height: 280vh;
  background: var(--bg-0);
}

.v-bandsec__pin {
  position: sticky;
  top: 0;
  height: 100vh;
  overflow: hidden;
}

.v-bandsec__stage {
  height: 100%;
  display: flex;
  flex-direction: column;
  align-items: center;
  justify-content: center;
  gap: clamp(16px, 3vh, 32px);
  padding-top: var(--nav-h);
}

.v-bandsec__head { text-align: center; }
.v-bandsec__head .v-eyebrow { margin-bottom: 8px; }

.v-bandsec__readout {
  display: flex;
  align-items: baseline;
  justify-content: center;
  gap: 20px;
}

.v-bandsec__numeral {
  display: inline-block;
  font-family: var(--serif);
  font-weight: 500;
  font-size: clamp(88px, 16vw, 230px);
  line-height: 0.9;
  letter-spacing: -0.02em;
  font-variant-numeric: lining-nums;
  /* P0 approach — firms from 0.96/0.6 to 1/1. Rest = 1. */
  transform: scale(calc(0.96 + var(--pre, 1) * 0.04));
  opacity: calc(0.6 + var(--pre, 1) * 0.4);
}

/* The band's mono caption — ember (allowed place 2 of 6, with the band). */
.v-bandsec__pm { color: var(--ember); }

.v-bandsec__zone {
  position: relative;
  width: min(var(--wrap), calc(100% - 48px));
  height: var(--zone-h);
}

/* The seed: the 2px amber line the band opens from. P0 grows it 0.3→1 from
   center; visible only while the band is closed; gone at rest (open = 1). */
.v-bandsec__seed {
  position: absolute;
  left: 0; right: 0; top: 50%;
  height: 2px;
  margin-top: -1px;
  background: var(--ember);
  transform: scaleX(calc(0.3 + var(--pre, 1) * 0.7));
  transform-origin: center;
  opacity: calc(1 - var(--open));
}

/* The fill scales; the edges TRANSLATE — hairlines never distort.
   Opacity: 1 → .85 (deference, last 10% of the dock) → .25 (the fix —
   what remains is structure, not glow; edges hold at full). */
.v-bandsec__fill {
  position: absolute;
  inset: 0;
  background: var(--band-fill);
  transform: scaleY(var(--open));
  transform-origin: center;
  opacity: calc(1 - 0.15 * var(--defer, 1) - 0.6 * var(--fix, 1));
}

.v-bandsec__edge {
  position: absolute;
  left: 0; right: 0;
  height: 1px;
  background: rgba(240, 163, 46, .5);
}
.v-bandsec__edge--top {
  top: 0;
  transform: translateY(calc((1 - var(--open)) * (var(--zone-h) / 2)));
}
.v-bandsec__edge--btm {
  bottom: 0;
  transform: translateY(calc((1 - var(--open)) * (var(--zone-h) / -2)));
}

.v-bandsec__copy {
  position: absolute;
  left: clamp(20px, 5vw, 72px);
  top: 50%;
  transform: translateY(calc(-50% + (1 - var(--copyp)) * 16px));
  opacity: var(--copyp);
  max-width: 40ch;
}
.v-bandsec__copy .v-h2 { margin-bottom: 14px; }
.v-bandsec__copy .v-body { margin-bottom: 18px; }
/* The aphorism arrives last within the verdict window (.85→.90 of --p). */
.v-bandsec__copy .v-aphorism {
  font-size: 17px;
  opacity: clamp(0, calc((var(--copyp, 1) - 0.75) / 0.25), 1);
}

.v-bandsec__card {
  position: absolute;
  right: clamp(16px, 4vw, 64px);
  top: 50%;
  width: min(300px, 26vw);   /* fits the composed end state inside 900px */
  transform: translateY(-50%) translateX(calc((1 - var(--cardp)) * 60vw));
  opacity: var(--cardp);
}
.v-bandsec__card .v-mask { border-radius: var(--r-inner); }


/* ─── 11 · S4 · HONEST ───────────────────────────────────────────────────── */

.v-honest {
  display: grid;
  grid-template-columns: minmax(0, 1fr) minmax(0, 1fr);
  gap: clamp(40px, 6vw, 96px);
  align-items: center;
}

.v-honest__copy .v-h2 { margin-bottom: 32px; }

.v-ledger { margin-bottom: 28px; }
.v-ledger__line {
  color: var(--ink);
  padding: 14px 0;
  border-bottom: 1px solid var(--hairline);
}
.v-ledger__line:first-child { border-top: 1px solid var(--hairline); }
.v-ledger__line::before { content: "–  "; color: var(--ink-3); }

.v-honest__stage {
  position: relative;
  width: min(380px, 100%);
  justify-self: center;
}

/* The explainer's copy runs to the shot's bottom edge; this scrim fades that
   region out so the coach card composes over quieted ground, never over
   live copy at full opacity. Page-bg tint, not amber — outside the lint. */
.v-honest__main .v-mask { position: relative; }
.v-honest__main .v-mask::after {
  content: "";
  position: absolute;
  left: 0; right: 0; bottom: 0;
  height: 34%;
  pointer-events: none;
  background: linear-gradient(180deg, rgba(11, 10, 8, 0), rgba(11, 10, 8, .96));
}

.v-honest__coach {
  position: absolute;
  left: -16%;
  bottom: -9%;
  width: 46%;
}
.v-honest__coach .v-mask {
  border-radius: var(--r-inner);
  background: var(--bg-2);
  /* knockout ring: a page-coloured gap that separates the two shots */
  box-shadow: 0 0 0 10px var(--bg-0), inset 0 1px 0 rgba(255, 255, 255, .06);
}

/* One provenance plate for the pairing — right-aligned, clear of the coach
   card that hangs over the stage's bottom-left corner. */
.v-honest__plate { margin-top: 16px; text-align: right; }


/* ─── 12 · S5 · COLLECT — deliberately empty ─────────────────────────────── */

.v-collect { text-align: center; }
.v-collect .v-wrap {
  max-width: 720px;
  /* Rides above the Act 6 veil (z 150) — the words stay lit while the
     darkroom's safelight goes off around them. */
  position: relative;
  z-index: 151;
}

/* ACT 6 · the veil — one fixed #000 layer, JS-injected, opacity-only scrub.
   The only place the site leaves olive-black: nothing is being exposed. */
.v-veil {
  position: fixed;
  inset: 0;
  z-index: 150;
  background: #000;
  opacity: var(--veil, 0);
  pointer-events: none;
}
/* Nav content dims with the veil (opacity only; the fixed bar itself keeps
   its stacking role). Grain dims via its own opacity calc below. */
.v-nav__in { opacity: calc(1 - var(--veil, 0) * 0.75); }

.v-collect__statement {
  font-size: clamp(32px, 4.6vw, 54px);
  margin-bottom: clamp(40px, 6vh, 64px);
}

.v-recital { margin-bottom: clamp(40px, 6vh, 64px); }
.v-recital__line {
  color: var(--ink);
  font-size: 13px;
  padding: 12px 0;
}

.v-collect__quote { margin-inline: auto; margin-bottom: 20px; }

.v-collect__flex { color: var(--ink-2); }

/* ACT 2b · the recitation, scrubbed. site.js writes --rp (0→1) over
   #collect's transit; rest (no JS / reduced motion / dead scrub) = 1 = the
   composed page. Each line opens over its own window --ra..--ra+0.16. */
.v-collect { --rp: 1; }
.v-recital__line, .v-collect__quote, .v-collect__flex {
  --rw: clamp(0, calc((var(--rp, 1) - var(--ra, 0)) / 0.16), 1);
  opacity: var(--rw);
  transform: translateY(calc((1 - var(--rw)) * 10px));
}
.v-recital__line:nth-child(1) { --ra: 0; }
.v-recital__line:nth-child(2) { --ra: 0.13; }
.v-recital__line:nth-child(3) { --ra: 0.26; }
.v-recital__line:nth-child(4) { --ra: 0.39; }
.v-recital__line:nth-child(5) { --ra: 0.52; }
.v-collect__quote { --ra: 0.68; }
.v-collect__flex  { --ra: 0.82; }
html[data-motion="reduced"] .v-recital__line,
html[data-motion="reduced"] .v-collect__quote,
html[data-motion="reduced"] .v-collect__flex { opacity: 1; transform: none; }


/* ─── 13 · S6 · TRIAL ────────────────────────────────────────────────────── */

.v-trial {
  display: grid;
  grid-template-columns: minmax(0, .95fr) minmax(0, 1.05fr);
  gap: clamp(40px, 6vw, 96px);
  align-items: center;
}

.v-trial__row {
  display: flex;
  gap: clamp(20px, 3vw, 40px);
  align-items: baseline;
}

/* ACT 7 · the vessel. Two stacked spans: the etched ghost (always visible)
   and the solid ink above it. CSS rest = filled; the latent clip is applied
   only by JS, then scrubbed bottom-up — ink filling a vessel, never a count. */
.v-trial__numeral {
  position: relative;
  display: inline-block;
  font-family: var(--serif);
  font-weight: 500;
  font-size: clamp(96px, 12vw, 176px);
  line-height: 0.85;
  letter-spacing: -0.02em;
}
.v-trial__num { display: block; }
.v-trial__num--ghost {
  color: transparent;
  -webkit-text-stroke: 1px var(--ink-3);
}
@supports not (-webkit-text-stroke: 1px black) {
  .v-trial__num--ghost { color: var(--ink-3); }
}
.v-trial__num--ink {
  position: absolute;
  inset: 0;
  color: var(--ink);
}
.v-trial__num--ink.is-latent {
  clip-path: inset(calc((1 - var(--fill, 0)) * 100%) 0 0 0);
}

.v-trial__row .v-h2 { margin-bottom: 12px; }

.v-themes__row {
  display: grid;
  grid-template-columns: repeat(3, 1fr);
  gap: 14px;
  align-items: start;
}
.v-themes__caption {
  display: block;
  color: var(--ink-2);
  margin-top: 20px;
  text-align: center;
}

/* The trial section runs shorter than its siblings — a full bottom pad left
   a dead void before #cta. Tightened to hold the page's rhythm. */
#trial { padding-block-end: clamp(56px, 8vw, 96px); }


/* ─── 14 · S7 · CTA ──────────────────────────────────────────────────────── */

/* The closing ask sits centered on its own screen — the page's full stop. */
.v-ctasec {
  min-height: 78vh;
  display: flex;
  align-items: center;
  text-align: center;
}
.v-ctasec .v-wrap { width: 100%; }
.v-ctasec .v-wrap > .v-body { margin-inline: auto; }
.v-ctasec .v-h2 { margin-bottom: 14px; }
.v-ctasec .v-wrap > .v-body { margin-bottom: 36px; }

.v-ctasec__form { max-width: 640px; margin-inline: auto; text-align: left; }
.v-ctasec__form > [data-cta-slot] {
  background: rgba(255, 255, 255, .03);
  border: 1px solid var(--hairline);
  border-radius: var(--r-outer);
  padding: 8px;
}
.v-ctasec__form .v-waitlist {
  max-width: none;
  background: var(--bg-2);
  border-radius: var(--r-inner);
  box-shadow: inset 0 1px 0 rgba(255, 255, 255, .06);
  padding: clamp(20px, 3vw, 32px);
}
/* After the release switch the slot holds .v-cta instead of the form. */
.v-ctasec__form .v-cta {
  background: var(--bg-2);
  border-radius: var(--r-inner);
  box-shadow: inset 0 1px 0 rgba(255, 255, 255, .06);
  padding: clamp(20px, 3vw, 32px);
}


/* ─── 15 · FOOTER ────────────────────────────────────────────────────────── */

.v-footer {
  padding-block: 0 48px;
  background: var(--bg-1);
}

/* ACT 8 · the pluck — the footer's top hairline as a live path. Ink-coloured,
   NOT amber; 1px via non-scaling-stroke. Rest = the straight line. */
.v-pluck {
  display: block;
  width: 100%;
  height: 20px;
  margin-top: -10px;
  margin-bottom: 52px;
}
.v-pluck__line { stroke: var(--hairline-strong); }

.v-footer__curves {
  width: 132px;
  height: auto;
  margin-bottom: 40px;
}
.v-footer__curve--grey { stroke: var(--ink-3); }
/* The amber curve — ember (allowed place 4 of 6). */
.v-footer__curve--amber { stroke: var(--ember); }

/* The in-view draw. Resting state (no JS, blocked JS, post-watchdog):
   fully drawn — no dash exists unless site.js arms `.is-armed`, and the
   same file's watchdog un-arms it. `.is-drawn` transitions the offset to 0,
   so even a dropped transition still lands fully drawn. */
html[data-js="on"] .v-footer__curves.is-armed .v-footer__curve {
  stroke-dasharray: 1;
  stroke-dashoffset: 1;
}
html[data-js="on"] .v-footer__curves.is-armed.is-drawn .v-footer__curve {
  stroke-dashoffset: 0;
  transition: stroke-dashoffset .9s var(--ease-out);
}
html[data-js="on"] .v-footer__curves.is-armed.is-drawn .v-footer__curve--amber {
  transition-delay: .18s;
}

.v-footer__top {
  display: flex;
  justify-content: space-between;
  align-items: baseline;
  gap: 24px;
  flex-wrap: wrap;
  margin-bottom: 28px;
}

.v-footer__wordmark {
  font-family: var(--serif);
  font-weight: 500;
  font-size: 24px;
}

.v-footer__links { display: flex; gap: 24px; flex-wrap: wrap; }
.v-footer__links a {
  font-size: 14px;
  color: var(--ink-2);
  text-decoration: none;
}
.v-footer__links a:hover { color: var(--ink); }

.v-footer__fine {
  font-size: 13px;
  line-height: 1.6;
  color: var(--ink-3-text);
  max-width: 62ch;
  margin-bottom: 24px;
}

.v-footer__sig {
  font-family: var(--mono);
  font-size: 11px;
  letter-spacing: 0.18em;
  text-transform: uppercase;
  color: var(--ink-3-text);
}


/* ─── 16 · THE DOCK ──────────────────────────────────────────────────────── */

/* JS-dependent chrome: without a controller it is inert furniture, so it is
   gated on data-js. This is the one allowed hide — it duplicates the in-flow
   CTA, it is not content. */
html:not([data-js="on"]) .v-dock { display: none; }

.v-dock {
  position: fixed;
  left: 0; right: 0; bottom: 0;
  z-index: 210;
  background: rgba(21, 19, 16, .92);
  -webkit-backdrop-filter: blur(14px);
  backdrop-filter: blur(14px);
  border-top: 1px solid var(--hairline-strong);
  transform: translateY(110%);
  /* visibility pairs with the transform so the off-screen dock's three
     interactive children leave the tab order; the 0s delay flips it only
     after the slide-out finishes. */
  visibility: hidden;
  transition: transform .4s var(--ease-out), visibility 0s .4s;
}
.v-dock.is-up {
  transform: none;
  visibility: visible;
  transition: transform .4s var(--ease-out), visibility 0s 0s;
}
.v-dock.is-dismissed { display: none; }

.v-dock__rail {
  max-width: var(--wrap);
  margin-inline: auto;
  padding: 12px 24px;
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 16px;
}

.v-dock__dismiss {
  flex: none;
  width: 44px; height: 44px;   /* mobile touch-target floor */
  display: grid;
  place-items: center;
  background: transparent;
  border: 1px solid var(--hairline);
  border-radius: 50%;
  color: var(--ink-2);
  cursor: pointer;
}
.v-dock__dismiss svg { width: 16px; height: 16px; }
.v-dock__dismiss:hover { color: var(--ink); border-color: var(--hairline-strong); }


/* ─── 17 · ANIMATION ARMING ──────────────────────────────────────────────── */

/* The ONLY hiding rule for content, and it needs BOTH attributes set by
   site.js itself. The 2500ms watchdog removes data-anim and stamps .is-in,
   so nothing armed here can outlive a wedged observer. */
html[data-js="on"][data-anim="on"] .v-anim:not(.is-in) { opacity: 0; }


/* ─── 18 · BREAKPOINTS ───────────────────────────────────────────────────── */

@media (max-width: 1024px) {
  .v-hero__grid { grid-template-columns: 1fr; }
  .v-hero { min-height: 0; }
  .v-hero__stage { order: 2; margin-top: 48px; }
  .v-hero__device { width: min(340px, 76vw); }
  .v-bandsec__card { width: min(300px, 34vw); }
}

@media (max-width: 768px) {
  .v-nav__links { display: none; }  /* privacy/support remain in the footer */

  /* Act 2 mobile cut: no wrapper, no clone — the hero shot's own exit scrub
     gains a gentle push-in over its last half-viewport (--hexit, JS-written
     from the same hero scrub; rests at 0). Pointer terms absent on touch. */
  .v-hero__device .v-mask {
    transform: rotateX(var(--hero-rx, 8deg))
               translateY(calc(var(--hero-ty, 0px) - var(--hexit, 0) * 24px))
               scale(calc(1 + var(--hexit, 0) * 0.06));
  }

  /* S2 → pure-CSS scroll-snap strip. Parallax vars neutralised. */
  .v-triptych {
    display: flex;
    overflow-x: auto;
    scroll-snap-type: x mandatory;
    gap: 16px;
    margin-inline: -24px;
    padding-inline: 24px;
    scrollbar-width: none;
  }
  .v-triptych::-webkit-scrollbar { display: none; }
  .v-triptych__col {
    flex: 0 0 72vw;
    scroll-snap-align: center;
    transform: none;
  }

  /* S3 compact: 200vh wrapper (100vh scrub); card leaves the overlay and
     docks BELOW the band — P2 becomes a rise + fade. P4 identical. */
  .v-bandsec { --zone-h: 32vh; height: 200vh; }
  .v-bandsec__numeral { font-size: clamp(72px, 22vw, 120px); }
  .v-bandsec__copy {
    position: static;
    transform: none;
    padding: 20px;
    max-width: none;
  }
  .v-bandsec__copy .v-h2 { font-size: 24px; }
  .v-bandsec__copy .v-body { font-size: 15px; }
  .v-bandsec__zone {
    height: auto;
    min-height: var(--zone-h);
    display: flex;
    flex-direction: column;
    justify-content: center;
    align-items: stretch;
  }
  .v-bandsec__edge--top { transform: translateY(calc((1 - var(--open)) * 16vh)); }
  .v-bandsec__edge--btm { transform: translateY(calc((1 - var(--open)) * -16vh)); }
  .v-bandsec__copy .v-aphorism { font-size: 15px; }
  .v-bandsec__card {
    position: static;
    width: min(190px, 48vw);
    align-self: flex-end;
    margin: 4px 20px 16px 0;
    transform: translateY(calc((1 - var(--cardp)) * 48px));
  }

  .v-honest { grid-template-columns: 1fr; }
  .v-honest__stage { width: min(320px, 82vw); }
  .v-honest__coach { left: auto; right: -6%; }

  .v-trial { grid-template-columns: 1fr; }

  .v-dock .v-btn--ghost { display: none; }
}

@media (max-width: 480px) {
  :root { --sec-pad: clamp(80px, 22vw, 96px); }
  .v-h1 { font-size: clamp(32px, 10.2vw, 44px); }
  .v-chip { display: none; }
  .v-waitlist__row .v-btn { flex: 1 1 100%; }
  .v-trial__numeral { font-size: 96px; }
  .v-themes__row { gap: 10px; }
  .v-bandsec__card { width: 64vw; }
}


/* ─── 19 · REDUCED MOTION ────────────────────────────────────────────────── */

/* Two spellings of the same law: the boot.js attribute (covers JS-on), and
   the media query (covers JS-off + OS preference). Transforms, pinning,
   parallax, tilt and scrub all off; the band renders composed. */

html[data-motion="reduced"] * {
  transition-duration: .01ms !important;
  animation-duration: .01ms !important;
}
html[data-motion="reduced"] .v-hero__device .v-mask { transform: none; }
html[data-motion="reduced"] #hero .v-h1,
html[data-motion="reduced"] #hero .v-eyebrow,
html[data-motion="reduced"] .v-hero__device .v-plate { transform: none; }
html[data-motion="reduced"] .v-triptych__col { transform: none; }
html[data-motion="reduced"] .v-bandsec { --p: 1; height: auto; }
html[data-motion="reduced"] .v-bandsec__pin { position: static; height: auto; padding-block: var(--sec-pad); }
html[data-motion="reduced"] .v-bandsec__stage { height: auto; }
html[data-motion="reduced"] .v-bandsec__numeral { transform: none; }
html[data-motion="reduced"] .v-veil { display: none !important; }
html[data-motion="reduced"] .v-trial__num--ink { clip-path: none !important; }
html[data-motion="reduced"] .v-dock { transition: none; }
html[data-motion="reduced"] .v-anim { opacity: 1 !important; transition: opacity .2s var(--ease-out); }

@media (prefers-reduced-motion: reduce) {
  html { scroll-behavior: auto; }
  * {
    transition-duration: .01ms !important;
    animation-duration: .01ms !important;
  }
  .v-hero__device .v-mask { transform: none; }
  #hero .v-h1, #hero .v-eyebrow, .v-hero__device .v-plate { transform: none; }
  .v-triptych__col { transform: none; }
  .v-bandsec { --p: 1; height: auto; }
  .v-bandsec__pin { position: static; height: auto; padding-block: var(--sec-pad); }
  .v-bandsec__stage { height: auto; }
  .v-bandsec__numeral { transform: none; }
  .v-veil { display: none !important; }
  .v-trial__num--ink { clip-path: none !important; }
  .v-dock { transition: none; }
}


/* ─── 20 · PRINT ─────────────────────────────────────────────────────────── */

@media print {
  body { background: #fff; color: #111; }
  .v-nav, .v-dock, .v-grain, .v-progress, .v-veil { display: none !important; }
  .v-trial__num--ink { clip-path: none !important; }
  .v-body, .v-cta-note, .v-footer__fine { color: #333; }
  .v-anim { opacity: 1 !important; }
  .v-bandsec { height: auto; }
  .v-bandsec__pin { position: static; height: auto; }
  .v-mask { border-color: #ccc; box-shadow: none; }
  a { color: #111; }
}


/* ─── 21 · 404 ───────────────────────────────────────────────────────────── */

.v-404 {
  min-height: 100vh;
  display: grid;
  place-items: center;
  text-align: center;
  padding: 24px;
}
.v-404__in .v-eyebrow { margin-bottom: 24px; }
.v-404__line {
  font-family: var(--serif);
  font-weight: 500;
  font-size: clamp(28px, 5vw, 44px);
  line-height: 1.15;
  letter-spacing: -0.01em;
  max-width: 18ch;
  margin: 0 auto 36px;
}
.v-404__home {
  font-family: var(--mono);
  font-size: 12px;
  letter-spacing: 0.14em;
  text-transform: uppercase;
  color: var(--ink-2);
  text-decoration: none;
  border-bottom: 1px solid var(--hairline-strong);
  padding-bottom: 4px;
}
.v-404__home:hover { color: var(--ink); }
