/* fuime — design system.
   Structure, type scale and band rhythm are Mercury's. One knob turned on
   purpose: the accent is #C2401F, not an indigo. Everything below is a token
   or a component built from tokens. No raw hex past :root. */

@import url('https://api.fontshare.com/v2/css?f%5B%5D=general-sans@1,2&display=swap');

:root {
  /* ink */
  --ink: #272735;
  --ink-muted: #535461;
  --ink-faint: #c3c3cc;
  /* --ink-faint on white is 1.75:1. That is fine for the hairline ticks it
     draws on the split bar and nowhere near fine for a placeholder, which is
     the only thing telling you what the field wants. 4.55:1 clears AA.
     Deliberately not --ink-muted (7.49:1) — a placeholder that dark reads as
     a value already typed in, and people stop before they type. */
  --ink-placeholder: #757580;
  --on-dark: #ededf3;
  --on-dark-mu: rgba(237, 237, 243, 0.62);
  --on-dark-faint: rgba(237, 237, 243, 0.14);

  /* ground */
  --cream: #f4f1ec;
  --paper: #ffffff;
  --night: #141420;
  --night-2: #1d1d2b;
  --ink-soft: #3a3a4c;
  --hair: rgba(39, 39, 53, 0.12);

  /* accent — one hue, three jobs: on light, on dark, and pressed */
  --accent: #c2401f;
  --accent-lit: #e2724f;
  --accent-deep: #a83518;
  --accent-tint: rgba(194, 64, 31, 0.1);
  --accent-shadow: rgba(194, 64, 31, 0.55);
  --accent-glow: rgba(226, 114, 79, 0.17);

  /* Paid. The one green on the site, and it only ever means money that has
     already landed — never "success", never a decorative tick. */
  --good: #6fbf95;

  /* elevation + masking */
  --shadow-1: rgba(20, 20, 32, 0.05);
  --shadow-2: rgba(20, 20, 32, 0.28);
  --mask-on: rgba(0, 0, 0, 1);

  /* type */
  --font:
    'General Sans', -apple-system, BlinkMacSystemFont, 'Segoe UI', sans-serif;
  --w-display: 480;
  --w-ui: 500;
  --w-body: 400;

  --t-hero: clamp(2.5rem, 1.35rem + 4.6vw, 4.0625rem); /* → 65px */
  /* Was 42px at the top end. 42 is Mercury's h2, but Mercury spends the other
     half of the row on dense product copy — ours was a lone 42px line on a
     1100px measure with nothing beside it, which reads small rather than
     restrained. 52 with the band head now filling both tracks. */
  --t-h2: clamp(2.125rem, 1.3rem + 3.3vw, 3.25rem); /* → 52px */
  --t-h3: clamp(1.5rem, 1.2rem + 1.2vw, 2rem); /* → 32px */
  --t-h4: 1.5rem; /* 24 */
  --t-lead: clamp(1.125rem, 1.02rem + 0.42vw, 1.3125rem); /* → 21 */
  --t-body: 1rem; /* 16 */
  --t-small: 0.875rem; /* 14 */

  /* space */
  --s1: 4px;
  --s2: 8px;
  --s3: 12px;
  --s4: 16px;
  --s5: 24px;
  --s6: 32px;
  --s7: 48px;
  --s8: 64px;
  --s9: 96px;
  --s10: 144px;

  /* form */
  --r: 4px;
  --r-card: 12px;
  --r-pill: 999px;
  --r-media: 40px;
  --r-arch: 120px;
  --ease: cubic-bezier(0.2, 0, 0, 1);
  --dur: 180ms;

  --wrap: 1180px;
  --gut: clamp(20px, 5vw, 40px);

  /* the split — one rule cut into three proportional segments. Three scales,
     because it appears at three sizes and must be the same object each time. */
  --split-h: 10px;
  --split-h-dock: 2px;
  --track-hud: 0.04em;

  /* One z band for every layer any fx module is allowed to occupy, so a module
     author never has to guess and never has to read another module's CSS. */
  --cwe-z-bg: 0;
  --cwe-z-content: 10;
  --cwe-z-fx: 20;
  --cwe-z-transition: 100;
}

/* ── reset ─────────────────────────────────────────────────────────────── */

*,
*::before,
*::after {
  box-sizing: border-box;
}
* {
  margin: 0;
}
html {
  -webkit-text-size-adjust: 100%;
  scroll-behavior: smooth;
}
body {
  font-family: var(--font);
  font-weight: var(--w-body);
  font-size: var(--t-body);
  line-height: 1.55;
  color: var(--ink);
  background: var(--cream);
  -webkit-font-smoothing: antialiased;
  text-rendering: optimizeLegibility;
  overflow-x: hidden;
}
img,
svg {
  display: block;
  max-width: 100%;
}
a {
  color: inherit;
  text-decoration: none;
}
button,
input {
  font: inherit;
  color: inherit;
}
h1,
h2,
h3,
h4 {
  font-weight: var(--w-display);
  line-height: 1.08;
  letter-spacing: -0.018em;
  text-wrap: balance;
}

/* ── layout ────────────────────────────────────────────────────────────── */

/* The only utilities on the site, and they exist for one reason: the same four
   values were repeated as inline `style="margin-top: var(--sN)"` nineteen times
   across three pages, which is a spacing scale that cannot be changed in one
   place. Measures (`max-width: 52ch`) stay inline on purpose — a line length is
   a judgment about one specific paragraph, not a reusable value. */
.mt-5 {
  margin-top: var(--s5);
}
.mt-6 {
  margin-top: var(--s6);
}
.mt-7 {
  margin-top: var(--s7);
}
.mt-8 {
  margin-top: var(--s8);
}

.wrap {
  width: 100%;
  max-width: var(--wrap);
  margin-inline: auto;
  padding-inline: var(--gut);
}
/* 9vw hit the 144px ceiling at 1440, so consecutive bands put 288px of empty
   ground between the last line of one and the first line of the next — plus the
   mt-8 the content inside each one already carries. 6.6vw tops out at 104 and
   holds the same proportion on the way down. */
.band {
  position: relative;
  overflow: hidden;
  padding-block: clamp(var(--s7), 6.6vw, 104px);
}

/* ── band head ─────────────────────────────────────────────────────────────
   Every band opened as a single left column: eyebrow, headline, then support
   copy stacked underneath at 46ch. On a 1440 viewport that left the right ~40%
   of all seven bands empty and spent vertical space saying what fits beside the
   headline. Two tracks now — statement left, support right, both pulled to the
   same baseline with one hairline under them. Children that are not part of the
   head (a table, a card grid, a form) span both tracks and behave as before. */
.bhead {
  display: grid;
  grid-template-columns: minmax(0, 1.12fr) minmax(0, 0.88fr);
  column-gap: var(--s8);
  align-items: end;
  padding-bottom: var(--s6);
  border-bottom: 1px solid var(--hair);
}
.band--night .bhead {
  border-bottom-color: var(--on-dark-faint);
}
.bhead > .eyebrow {
  grid-column: 1;
  grid-row: 1;
  margin-bottom: var(--s4);
}
.bhead > .h2 {
  grid-column: 1;
  grid-row: 2;
  max-width: 22ch;
}
/* The lead sits in the right track across both head rows and drops to the
   headline's last baseline. Its own margin-top and the inline max-width the
   markup carries would both fight that, so both are overridden here. */
.bhead > .lead {
  grid-column: 2;
  grid-row: 1 / 3;
  align-self: end;
  margin-top: 0;
  max-width: 38ch !important;
}
/* A head with no support copy is a one-column head — the headline gets the
   room the lead would have used instead of leaving a 400px hole beside it. */
.bhead:not(:has(> .lead)) {
  grid-template-columns: minmax(0, 1fr);
}
.bhead:not(:has(> .lead)) > .h2 {
  max-width: 30ch;
}
@media (max-width: 860px) {
  .bhead {
    grid-template-columns: minmax(0, 1fr);
    row-gap: var(--s5);
    padding-bottom: var(--s5);
  }
  .bhead > .h2,
  .bhead > .lead {
    grid-column: 1;
    grid-row: auto;
    max-width: none !important;
  }
}
/* Everything inside a band sits above the arch watermark drawn behind it. */
.band > .wrap {
  position: relative;
  z-index: 1;
}
/* The nav is 72px and fixed, so an in-page jump would otherwise land the
   section heading underneath it. 88 clears the nav plus a little air. */
section[id] {
  scroll-margin-top: 88px;
}
/* --band-bg names the colour a band is actually painted, so anything that has
   to sit opaquely on top of it — a pinned table column, a scroll shadow that
   fades into the page — can read it instead of being written out three times
   with a band selector in front. */
.band--night {
  --band-bg: var(--night);
  background: var(--night);
  color: var(--on-dark);
}
.band--cream {
  --band-bg: var(--cream);
  background: var(--cream);
}
.band--paper {
  --band-bg: var(--paper);
  background: var(--paper);
}

.eyebrow {
  font-size: var(--t-small);
  font-weight: var(--w-ui);
  letter-spacing: 0.46px;
  text-transform: uppercase;
  color: var(--ink-muted);
  margin-bottom: var(--s5);
}
.band--night .eyebrow {
  color: var(--on-dark-mu);
}

/* Optical tracking: at 52px the default spacing reads loose, and the display
   weight is 480 so there is no extra stroke weight to carry it. */
.h2 {
  font-size: var(--t-h2);
  letter-spacing: -0.019em;
  line-height: 1.08;
}
.h3 {
  font-size: var(--t-h3);
}
.h4 {
  font-size: var(--t-h4);
  letter-spacing: -0.006em;
}
.lead {
  font-size: var(--t-lead);
  line-height: 1.5;
  color: var(--ink-muted);
  max-width: 46ch;
}
.band--night .lead {
  color: var(--on-dark-mu);
}
.muted {
  color: var(--ink-muted);
}
.band--night .muted {
  color: var(--on-dark-mu);
}
.small {
  font-size: var(--t-small);
  letter-spacing: 0.42px;
}

/* ── loader ────────────────────────────────────────────────────────────────
   #boot's position/background live inline in each page's <head> so the night
   colour is painted before this stylesheet has been fetched. Everything below
   is the part that is allowed to arrive a frame late. */

.boot__arch {
  width: 62px;
  height: auto;
  overflow: visible;
}
/* The empty arch, and the same arch lit — the second one revealed by a rect
   that rises as the manifest completes. */
.boot__ghost {
  fill: var(--on-dark-faint);
}
.boot__lit {
  fill: var(--on-dark);
}
#boot-rect {
  /* Starts pushed fully below the glyph, so nothing is lit at 0%. */
  transform: translateY(582px);
  transition: transform 420ms cubic-bezier(0.33, 0, 0.15, 1);
}
.boot__word {
  font-size: 1.25rem;
  font-weight: var(--w-display);
  letter-spacing: -0.03em;
  color: var(--on-dark);
  opacity: 0;
  transform: translateY(6px);
  transition:
    opacity 520ms var(--ease) 180ms,
    transform 520ms var(--ease) 180ms;
}
#boot[data-lit='true'] .boot__word {
  opacity: 1;
  transform: none;
}

/* The exit: the arch takes one confident step toward the viewer as the night
   lifts off the page underneath it. */
#boot[data-done='true'] {
  opacity: 0;
  pointer-events: none;
  transition:
    opacity 620ms var(--ease),
    visibility 0s linear 620ms;
  visibility: hidden;
}
#boot[data-done='true'] .boot__arch,
#boot[data-done='true'] .boot__word {
  transform: scale(1.12);
  transition: transform 620ms cubic-bezier(0.4, 0, 0.1, 1);
}
/* Everything the loader was covering settles the last 1% into place. Opacity
   only — a transform here would make the fixed nav's containing block the
   animated element and drop it out of the viewport for the duration. */
.boot-out body > *:not(#boot) {
  animation: boot-settle 760ms var(--ease) both;
}
@keyframes boot-settle {
  from {
    opacity: 0;
  }
  to {
    opacity: 1;
  }
}

/* Returning within the session: straight to the page, no loader. */
.booted #boot {
  display: none;
}

@media (prefers-reduced-motion: reduce) {
  #boot-rect,
  .boot__word,
  #boot[data-done='true'] .boot__arch,
  #boot[data-done='true'] .boot__word {
    transition-duration: 1ms;
    transform: none;
  }
  .boot-out body > *:not(#boot) {
    animation-duration: 1ms;
  }
}

/* ── nav ───────────────────────────────────────────────────────────────── */

.nav {
  position: fixed;
  inset: 0 0 auto;
  z-index: 60;
  transition:
    background var(--dur) var(--ease),
    border-color var(--dur) var(--ease);
  border-bottom: 1px solid transparent;
}
.nav__in {
  display: flex;
  align-items: center;
  gap: var(--s7);
  height: 72px;
}
.nav__links {
  display: flex;
  gap: var(--s6);
  margin-left: auto;
  align-items: center;
}
.nav a.nav__link {
  font-size: var(--t-small);
  font-weight: var(--w-ui);
  letter-spacing: 0.42px;
  opacity: 0.78;
  transition: opacity var(--dur) var(--ease);
  /* Measured at 90x22, which is under the 24px WCAG 2.5.8 floor. The inline
     exception does not apply — these are standalone nav items, not links inside
     a sentence. Free to fix here because .nav__in is a fixed 72px row with
     align-items:center, so a taller link box re-centres and the text does not
     move. */
  display: inline-flex;
  align-items: center;
  min-height: 30px;
}
.nav a.nav__link:hover {
  opacity: 1;
}

/* over the hero */
.nav--over {
  color: var(--on-dark);
}
/* Scrolled past it. Opaque, and coloured by whichever band it is currently
   over.

   The obvious build — one translucent cream bar with a blur, the way every
   light-page product site does it — was what shipped, and it is wrong here for
   a structural reason: this page alternates dark and cream bands, so for half
   its length the 12% of backdrop showing through was a dark band or a bright
   photograph, smeared by the blur into a grey-brown that matched neither side
   of it. A translucent nav only works when everything under it is the same
   colour. Ours never is, so the bar is opaque and changes colour instead, and
   the transition below is what keeps that from being a snap. */
.nav--solid {
  background: var(--paper);
  border-bottom-color: var(--hair);
  color: var(--ink);
  transition:
    background-color var(--dur) var(--ease),
    border-color var(--dur) var(--ease),
    color var(--dur) var(--ease);
}
.nav--solid.nav--dark {
  background: var(--night);
  border-bottom-color: var(--on-dark-faint);
  color: var(--on-dark);
}

/* Logo lockup: the arch mark + the wordmark, both in currentColor so the
   nav can flip from over-the-hero white to scrolled-past ink for free. */
.mark {
  display: inline-flex;
  align-items: center;
  gap: 10px;
  font-size: 1.375rem;
  font-weight: var(--w-display);
  letter-spacing: -0.03em;
}
.mark svg {
  width: 26px;
  height: auto;
  flex: none;
  overflow: visible;
}
.mark__glyph {
  fill: currentColor;
  transition: fill var(--dur) var(--ease);
}
.nav--over .mark__glyph,
.foot .mark__glyph {
  fill: currentColor;
}
/* The only accent on the site besides a primary button. */
.mark i {
  font-style: normal;
  color: var(--accent);
}

.nav__burger {
  display: none;
  margin-left: auto;
  background: none;
  border: 0;
  padding: var(--s3);
  cursor: pointer;
}
.nav__burger span {
  display: block;
  width: 20px;
  height: 1.5px;
  background: currentColor;
  transition:
    transform var(--dur) var(--ease),
    opacity var(--dur) var(--ease);
}
.nav__burger span + span {
  margin-top: 5px;
}
.nav[data-open='true'] .nav__burger span:nth-child(1) {
  transform: translateY(6.5px) rotate(45deg);
}
.nav[data-open='true'] .nav__burger span:nth-child(2) {
  opacity: 0;
}
.nav[data-open='true'] .nav__burger span:nth-child(3) {
  transform: translateY(-6.5px) rotate(-45deg);
}

/* ── buttons ───────────────────────────────────────────────────────────── */

.btn {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  gap: var(--s2);
  height: 46px;
  padding-inline: var(--s5);
  border: 0;
  border-radius: var(--r-pill);
  font-size: var(--t-small);
  font-weight: var(--w-ui);
  letter-spacing: 0.42px;
  cursor: pointer;
  white-space: nowrap;
  transition:
    background var(--dur) var(--ease),
    color var(--dur) var(--ease),
    opacity var(--dur) var(--ease);
}
.btn--accent {
  background: var(--accent);
  color: var(--paper);
}
.btn--accent:hover {
  background: var(--accent-deep);
}
.btn--ink {
  background: var(--ink);
  color: var(--on-dark);
}
.btn--ink:hover {
  background: var(--ink-soft);
}
.btn--light {
  background: var(--on-dark);
  color: var(--ink);
}
.btn--light:hover {
  background: var(--paper);
}
.btn--ghost {
  background: transparent;
  color: inherit;
  box-shadow: inset 0 0 0 1px currentColor;
  opacity: 0.85;
}
.btn--ghost:hover {
  opacity: 1;
}
.btn[disabled] {
  opacity: 0.55;
  cursor: default;
}

.arrow {
  transition: transform var(--dur) var(--ease);
}
.btn:hover .arrow,
.tlink:hover .arrow {
  transform: translateX(3px);
}
.tlink {
  display: inline-flex;
  align-items: center;
  gap: var(--s2);
  font-size: var(--t-small);
  font-weight: var(--w-ui);
  letter-spacing: 0.42px;
  color: var(--ink);
  /* Also 22px tall, also standalone — every .tlink is the entire contents of its
     own <p>, so it is a block-level target in practice and the sentence
     exception does not cover it either. Padding buys the hit area; the matching
     negative margin gives it straight back to the line box, because an
     inline-flex is an atomic inline and the line box measures its MARGIN box.
     Net effect on layout is zero and the target clears the floor by 6px. */
  padding-block: 4px;
  margin-block: -4px;
}
.band--night .tlink {
  color: var(--on-dark);
}

/* ── hero ──────────────────────────────────────────────────────────────── */

.hero {
  position: relative;
  min-height: 760px;
  height: 100svh;
  max-height: 940px;
  display: flex;
  flex-direction: column;
  /* Anchored below the fixed nav rather than centred, so the headline never
     collides with it on a short viewport. The photograph takes the rest. */
  justify-content: flex-start;
  padding-top: calc(72px + clamp(var(--s7), 7vh, var(--s9)));
  color: var(--on-dark);
  background: var(--night);
  overflow: hidden;
}
.hero--short {
  min-height: 0;
  height: auto;
  max-height: none;
  padding-block: calc(72px + var(--s9)) var(--s9);
}
.hero__media {
  position: absolute;
  inset: 0;
  z-index: 0;
}
.hero__media img {
  width: 100%;
  height: 100%;
  object-fit: cover;
  object-position: center 62%;
}
/* The two secondary heroes are short, so a 16:9 frame gets cropped hard and
   the subject rides up into the headline. Favouring the top of the frame
   drops it back below the type. */
.hero--short .hero__media img {
  object-position: center 34%;
}
/* The photograph is the LCP element and stays that way. The clip is layered
   over it and only fades in once it can actually play, so a slow connection
   gets the still and nothing worse. */
.hero__live {
  position: absolute;
  inset: 0;
  width: 100%;
  height: 100%;
  object-fit: cover;
  object-position: center 62%;
  opacity: 0;
  transition: opacity 900ms ease;
  pointer-events: none;
}
.hero__live[data-playing='true'] {
  opacity: 1;
}
.hero--short .hero__live {
  object-position: center 34%;
}

/* Three layers, because no one of them can do all three jobs. The vertical
   pass keeps the nav and the headline off the town-lights band; the horizontal
   pass shades only the type column, so the right half of the photograph keeps
   its full brightness instead of the whole frame going flat.

   The third is an ellipse over the bottom of the type column, and it exists
   because the first two are aimed at the headline and both have faded out by
   the time the page reaches the line under the email field. Measured, that
   line landed on the brightest part of the town at 3.36:1 — small text, so it
   needs 4.5. Brightening the type would have fixed the number by flattening it
   into the lead above it; darkening the ground it sits on fixes the number and
   leaves the hierarchy, and the photograph's right side, alone. */
.hero__media::after {
  content: '';
  position: absolute;
  inset: 0;
  background:
    radial-gradient(
      120% 62% at 6% 88%,
      rgba(10, 12, 24, 0.78) 0%,
      rgba(10, 12, 24, 0.5) 38%,
      rgba(10, 12, 24, 0) 72%
    ),
    linear-gradient(
      100deg,
      rgba(10, 12, 24, 0.66) 0%,
      rgba(10, 12, 24, 0.42) 34%,
      rgba(10, 12, 24, 0) 66%
    ),
    linear-gradient(
      180deg,
      rgba(10, 12, 24, 0.42) 0%,
      rgba(10, 12, 24, 0.18) 42%,
      rgba(10, 12, 24, 0) 68%
    );
}
/* The two short heroes crop a 16:9 frame hard, which lands the headline
   directly on the out-of-focus town lights — measured at 2.2:1 against a
   4.5:1 requirement, and worse on the clip's bright frames than on the still.
   The tall hero on / has enough dark air under its type to pass at 15:1 and is
   left alone. This is a plate under the type column, not a wash: heavy through
   46%, then off a cliff, so the right third of the photograph keeps its light.
   Measured 6.9:1 on /parents and 5.3:1 on /pricing at the worst video frame. */
.hero--short .hero__media::after {
  background:
    linear-gradient(
      100deg,
      rgba(10, 12, 24, 0.92) 0%,
      rgba(10, 12, 24, 0.86) 46%,
      rgba(10, 12, 24, 0.1) 74%,
      rgba(10, 12, 24, 0) 92%
    ),
    linear-gradient(
      180deg,
      rgba(10, 12, 24, 0.42) 0%,
      rgba(10, 12, 24, 0.18) 42%,
      rgba(10, 12, 24, 0) 68%
    );
}
.hero__in {
  position: relative;
  z-index: 1;
  width: 100%;
}
.hero__body {
  max-width: 720px;
}
.hero--short .hero__body {
  max-width: 780px;
}
.hero h1 {
  font-size: var(--t-hero);
  letter-spacing: -0.026em;
}
.hero .lead {
  color: var(--on-dark-mu);
  margin-top: var(--s5);
  max-width: 50ch;
}
.hero__cta {
  margin-top: var(--s7);
}
.hero__note {
  margin-top: var(--s4);
  font-size: var(--t-small);
  letter-spacing: 0.42px;
  color: var(--on-dark-mu);
}

/* It is a .wrap, so it already has the site's max-width, centring and gutter.
   Setting the gutter a second time here — width: calc(100% - gut*2) on top of
   .wrap's own padding-inline — indented the fine print one full gutter past
   the headline it sits under. On a phone that is 40px against everything
   else's 20, which is exactly the kind of misalignment that reads as sloppy
   without the reader being able to say why. */
.hero__disclaimer {
  position: absolute;
  z-index: 1;
  left: 50%;
  bottom: var(--s6);
  transform: translateX(-50%);
  width: 100%;
}
/* Legal fine print, so it should read as fine print. The pill treatment it
   used to carry made it float like a cookie toast — a thing you dismiss —
   which is the opposite of what a disclaimer is for. A hairline rule ties it
   to the headline above it instead. */
.disclaimer {
  display: block;
  max-width: 62ch;
  padding-top: var(--s3);
  border-top: 1px solid var(--on-dark-faint);
  font-size: var(--t-small);
  line-height: 1.55;
  letter-spacing: 0.42px;
  color: var(--on-dark-mu);
  text-wrap: pretty;
}

/* The state-of-the-product chip, above the headline on every hero. It is not
   fine print and must not read as fine print: nothing under it — the price
   list, the three steps, the parents page — is true of a live payment yet, so
   the reader has to have this before they have any of that. Full --on-dark on
   an opaque --night-2 ground rather than the muted disclaimer grey, and an
   accent dot, because the one thing a status chip has to survive is being sat
   on top of a photograph. The dot cannot shrink or the flex row collapses it
   to nothing when the label wraps on a phone. */
.beta {
  display: inline-flex;
  align-items: center;
  gap: var(--s2);
  margin-bottom: var(--s5);
  padding: var(--s2) var(--s4);
  border-radius: var(--r-pill);
  background: var(--night-2);
  box-shadow: inset 0 0 0 1px var(--on-dark-faint);
  font-size: var(--t-small);
  font-weight: var(--w-ui);
  letter-spacing: var(--track-hud);
  text-transform: uppercase;
  color: var(--on-dark);
}
.beta::before {
  content: '';
  flex: 0 0 6px;
  height: 6px;
  border-radius: var(--r-pill);
  background: var(--accent-lit);
}

/* ── email capture ─────────────────────────────────────────────────────── */

.capture {
  max-width: 470px;
}
.capture__row {
  display: flex;
  align-items: center;
  gap: var(--s2);
  padding: var(--s1);
  border-radius: var(--r-pill);
  background: var(--paper);
  box-shadow: inset 0 0 0 1px var(--hair);
  transition: box-shadow var(--dur) var(--ease);
}
.capture__row:focus-within {
  box-shadow: inset 0 0 0 1.5px var(--accent);
}
.capture input {
  flex: 1;
  min-width: 0;
  height: 46px;
  padding-inline: var(--s5);
  border: 0;
  border-radius: var(--r-pill);
  background: transparent;
  outline: none;
  color: var(--ink);
}
.capture input::placeholder {
  color: var(--ink-placeholder);
}
.capture__msg {
  min-height: 20px;
  margin-top: var(--s3);
  font-size: var(--t-small);
  letter-spacing: 0.42px;
}
.capture__msg[data-state='error'] {
  color: var(--accent-lit);
}
.capture__msg[data-state='ok'] {
  color: inherit;
}
.capture[data-done='true'] .capture__row {
  display: none;
}
.capture__done {
  display: none;
  align-items: center;
  gap: var(--s3);
  font-size: var(--t-lead);
}
.capture[data-done='true'] .capture__done {
  display: flex;
}

/* The address takes off as a paper plane: it climbs in from where the field
   was, banks, and settles beside the confirmation — which then follows it in.
   It lands rather than exits, because this icon is the receipt. A plane that
   flies off screen leaves the sentence sitting next to an empty gap. */
.capture__plane {
  flex: none;
  width: 22px;
  height: 22px;
  color: var(--accent);
}
.capture[data-done='true'] .capture__plane {
  animation: plane-in 0.75s cubic-bezier(0.22, 1, 0.36, 1) both;
}
.capture[data-done='true'] .capture__done span {
  animation: done-in 0.45s ease-out 0.3s both;
}

@keyframes plane-in {
  0% {
    transform: translate(-14px, 16px) rotate(-18deg) scale(0.7);
    opacity: 0;
  }
  40% {
    opacity: 1;
  }
  /* Overshoots the climb, then eases back level — the bank that sells it. */
  65% {
    transform: translate(3px, -5px) rotate(9deg) scale(1.06);
    opacity: 1;
  }
  100% {
    transform: none;
    opacity: 1;
  }
}
@keyframes done-in {
  from {
    opacity: 0;
    transform: translateY(4px);
  }
  to {
    opacity: 1;
    transform: none;
  }
}

/* A plane that flies is decoration; the confirmation is the message. Motion
   off means the icon simply sits there and the text is legible immediately. */
@media (prefers-reduced-motion: reduce) {
  .capture[data-done='true'] .capture__plane,
  .capture[data-done='true'] .capture__done span {
    animation: none;
  }
}

/* ── grid + cards ──────────────────────────────────────────────────────── */

.grid {
  display: grid;
  gap: var(--s5);
}
.grid--2 {
  grid-template-columns: repeat(2, 1fr);
}
.grid--3 {
  grid-template-columns: repeat(3, 1fr);
}
.grid--4 {
  grid-template-columns: repeat(4, 1fr);
}
.split {
  display: grid;
  grid-template-columns: minmax(0, 1fr) minmax(0, 1fr);
  gap: clamp(var(--s7), 6vw, var(--s9));
  align-items: start;
}
.stack-lg > * + * {
  margin-top: var(--s7);
}

.card {
  padding: var(--s6);
  border-radius: var(--r-card);
  background: var(--paper);
  box-shadow: inset 0 0 0 1px var(--hair);
}
.band--night .card {
  background: var(--night-2);
  box-shadow: inset 0 0 0 1px var(--on-dark-faint);
}
.card h3,
.card h4 {
  margin-bottom: var(--s3);
}
.card p {
  color: var(--ink-muted);
}
.band--night .card p {
  color: var(--on-dark-mu);
}

.icon {
  width: 22px;
  height: 22px;
  margin-bottom: var(--s5);
  stroke: var(--ink);
  stroke-width: 1.4;
  fill: none;
  stroke-linecap: round;
  stroke-linejoin: round;
}
.band--night .icon {
  stroke: var(--on-dark);
}

/* numbered steps */
/* Three steps side by side carry three different lengths of copy, and left
   alone that pushed the three images to three different heights — tops ragged
   by 76px, bottoms by 78px. Three arches starting and stopping at three
   heights reads as a grid that missed, and it is the first thing the eye finds
   in the section.

   The step is a column and the picture is pushed to the bottom of it. Grid
   items already stretch to the tallest in the row, so one rule bottom-aligns
   all three; the images are the same aspect, so aligning the bottoms aligns
   the tops too. The copy stays where it belongs, under its own heading. */
.step {
  padding-top: var(--s5);
  border-top: 1px solid var(--hair);
  display: flex;
  flex-direction: column;
}
.step > picture {
  margin-top: auto;
}
.band--night .step {
  border-top-color: var(--on-dark-faint);
}
.step__n {
  font-size: var(--t-small);
  font-weight: var(--w-ui);
  letter-spacing: 0.46px;
  color: var(--ink-muted);
  margin-bottom: var(--s5);
}
.band--night .step__n {
  color: var(--on-dark-mu);
}
/* The step number and its qualifier sit on one line. A guardian requirement
   announced here, on the step that needs it, is a fact; the same sentence
   discovered three screens later is a catch. */
.step__meta {
  display: flex;
  align-items: baseline;
  flex-wrap: wrap;
  gap: var(--s3);
  margin-bottom: var(--s5);
}
.step__meta .step__n {
  margin-bottom: 0;
}
.step h3 {
  font-size: var(--t-h4);
  margin-bottom: var(--s3);
}
.step p {
  color: var(--ink-muted);
}
.band--night .step p {
  color: var(--on-dark-mu);
}

/* price cards */
.price {
  display: flex;
  flex-direction: column;
  gap: var(--s3);
}
.price__fig {
  font-size: var(--t-h2);
  font-weight: var(--w-display);
  letter-spacing: -0.022em;
  line-height: 1;
}
.price__fig sub {
  font-size: var(--t-body);
  font-weight: var(--w-body);
  letter-spacing: 0;
  vertical-align: baseline;
  color: var(--ink-muted);
}
.band--night .price__fig sub {
  color: var(--on-dark-mu);
}

/* ── invoice artifact ──────────────────────────────────────────────────── */

.invoice {
  border-radius: var(--r-card);
  background: var(--paper);
  box-shadow:
    0 1px 2px rgba(20, 20, 32, 0.05),
    0 18px 44px -18px rgba(20, 20, 32, 0.28),
    inset 0 0 0 1px var(--hair);
  overflow: hidden;
  font-size: var(--t-small);
  letter-spacing: 0.42px;
}
.invoice__head {
  display: flex;
  align-items: baseline;
  justify-content: space-between;
  gap: var(--s4);
  padding: var(--s5) var(--s6);
  border-bottom: 1px solid var(--hair);
}
.invoice__body {
  padding: var(--s5) var(--s6);
}
.invoice__row {
  display: flex;
  align-items: baseline;
  justify-content: space-between;
  gap: var(--s4);
  padding-block: var(--s3);
}
.invoice__row + .invoice__row {
  border-top: 1px solid var(--hair);
}
.invoice__row--total {
  border-top: 1px solid var(--ink);
  font-size: var(--t-body);
  font-weight: var(--w-ui);
  letter-spacing: 0;
}
.invoice__foot {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: var(--s4);
  padding: var(--s4) var(--s6);
  border-top: 1px solid var(--hair);
  background: var(--cream);
  color: var(--ink-muted);
}
.tag {
  display: inline-block;
  padding: var(--s1) var(--s3);
  border-radius: var(--r);
  box-shadow: inset 0 0 0 1px var(--hair);
  color: var(--ink-muted);
  font-weight: var(--w-ui);
}

/* ── ruled rows ────────────────────────────────────────────────────────────
   The site argues that fuime is a ledger, so its comparisons are set as one.
   Four floating boxes are four separate things a reader has to assemble; four
   ruled rows are a list with a line under each entry, which is a shape they
   already know how to scan — and a shape rows.js can strike a line through.

   `.card` stays on the element so the night-band and typography rules that
   already exist keep applying. The variant only takes the box away. */
.rows {
  border-top: 1px solid var(--hair);
}
.band--night .rows {
  border-top-color: var(--on-dark-faint);
}
.card.card--row {
  display: grid;
  grid-template-columns: 22px minmax(9rem, 15rem) minmax(0, 1fr);
  align-items: start;
  column-gap: var(--s5);
  padding: var(--s5) 0;
  border-radius: 0;
  background: none;
  box-shadow: none;
  border-bottom: 1px solid var(--hair);
}
.band--night .card.card--row {
  background: none;
  box-shadow: none;
  border-bottom-color: var(--on-dark-faint);
}
/* Optically centre the glyph on the label's x-height rather than its box. */
.card--row .icon {
  margin-top: 0.22em;
}
.card--row h3,
.card--row h4 {
  margin-bottom: 0;
  font-size: var(--t-body);
  font-weight: var(--w-ui);
  letter-spacing: 0;
  line-height: 1.55;
}

/* The money rows: what it is on the left, what it costs hard right, and the
   qualifier underneath the label where a qualifier belongs. A price table that
   performs is a price table that is selling you something. */
.card.card--fig {
  grid-template-columns: minmax(0, 1fr) auto;
  align-items: baseline;
  column-gap: var(--s6);
}
.card--fig .price__fig {
  font-size: var(--t-h3);
  text-align: right;
}

/* ── leader row ────────────────────────────────────────────────────────────
   Label, a run of dots, the value. Lifted from a printed statement, which is
   the point: a dotted leader is how paper says "these two belong together"
   without drawing a box around them. */
.lrow {
  display: flex;
  align-items: baseline;
  gap: var(--s3);
  font-size: var(--t-small);
  letter-spacing: 0.42px;
  color: var(--ink-muted);
}
/* .foot is painted night but carries no band class, so every dark-surface rule
   below has to name it too. Leaving it out is what put the footer's status row
   in --ink-muted on a near-black ground — technically styled, actually unreadable. */
.band--night .lrow,
.foot .lrow {
  color: var(--on-dark-mu);
}
.lrow__lead {
  flex: 1 1 auto;
  min-width: var(--s6);
  align-self: center;
  height: 2px;
  background-image: radial-gradient(
    circle,
    currentColor 0.8px,
    transparent 1.1px
  );
  background-size: 6px 2px;
  background-repeat: repeat-x;
  opacity: 0.4;
}
.lrow__val {
  font-weight: var(--w-ui);
  color: var(--ink);
}
.band--night .lrow__val,
.foot .lrow__val {
  color: var(--on-dark);
}
/* The status line sits under the footer's rule, so it needs its own air on
   both sides or it reads as a stray caption glued to the divider. */
.foot__status {
  padding-top: var(--s6);
  margin-bottom: var(--s5);
}
.lrow--boxed {
  padding: var(--s4) var(--s5);
  border-radius: var(--r);
  box-shadow: inset 0 0 0 1px var(--hair);
}
.band--night .lrow--boxed {
  box-shadow: inset 0 0 0 1px var(--on-dark-faint);
}

/* ── the split, before the split ───────────────────────────────────────────
   split.js owns this element. Until it mounts — and forever, if scripting is
   off — the bar still has to be right, because the bar is the claim. :empty
   paints the default 90/7/3 from gradient stops and stops matching the instant
   the module puts anything inside, so there is never two of them. */
.split-mount:empty {
  display: block;
  height: var(--split-h);
  border-radius: 2px;
  box-shadow: inset 0 0 0 1px var(--hair);
  background: linear-gradient(
    90deg,
    var(--ink-soft) 0 90%,
    transparent 90% 90.5%,
    var(--ink-faint) 90.5% 97%,
    transparent 97% 97.5%,
    var(--ink-faint) 97.5% 100%
  );
}
.split-mount--dock:empty {
  height: var(--split-h-dock);
  border-radius: 0;
  box-shadow: none;
}

/* ── artifact label ────────────────────────────────────────────────────────
   The invoice is arithmetic, not a customer. Saying so out loud is cheaper
   than any amount of fine print underneath it. */
.artifact-label {
  display: block;
  font-size: var(--t-small);
  font-weight: var(--w-ui);
  letter-spacing: var(--track-hud);
  text-transform: uppercase;
  color: var(--ink-muted);
  margin-bottom: var(--s3);
}
.band--night .artifact-label {
  color: var(--on-dark-mu);
}

/* Off-screen but announced. Used where a table needs a column header that
   would be noise on screen — the method column's heading is the table's whole
   subject and printing it would only make the header row lie about itself. */
.visually-hidden {
  position: absolute;
  width: 1px;
  height: 1px;
  margin: -1px;
  padding: 0;
  overflow: hidden;
  clip-path: inset(50%);
  white-space: nowrap;
  border: 0;
}

/* The reasoning behind a comparison, one click away and never in front of it.
   Deliberately quieter than an FAQ summary: this is a footnote you can open,
   not a question the page is inviting. */
.reasoning summary {
  font-size: var(--t-small);
  font-weight: var(--w-ui);
  letter-spacing: 0.42px;
  color: var(--ink-muted);
  cursor: pointer;
  padding-block: var(--s2);
}
.band--night .reasoning summary {
  color: var(--on-dark-mu);
}
.reasoning summary:hover {
  color: var(--ink);
}
.band--night .reasoning summary:hover {
  color: var(--on-dark);
}
.reasoning .faq__a {
  max-width: 62ch;
  padding-top: var(--s3);
}

/* ── matrix ────────────────────────────────────────────────────────────────
   Five ways of getting paid against the four things a parent is actually
   checking. Hairlines only: a table with fills and stripes reads as a pricing
   page trying to win, and this one only has to be legible.

   The scroller is real — at 390px four columns of answers do not fit and the
   honest fix is to let them scroll with the method name pinned, not to hide
   two columns behind a breakpoint and call the comparison complete. */
.matrix-scroll {
  overflow-x: auto;
  -webkit-overflow-scrolling: touch;

  /* A cut-off column with no cue reads as a broken table rather than a
     scrollable one. These are the two classic paired layers: the `local`
     gradient scrolls with the content and parks over the `scroll` shadow when
     that edge is reached, so the shadow is visible exactly when there is more
     to see and never when there isn't. No JS, and it cannot lie. */
  background:
    linear-gradient(90deg, var(--band-bg, var(--paper)) 60%, transparent) left /
      2.5rem 100% no-repeat local,
    linear-gradient(270deg, var(--band-bg, var(--paper)) 60%, transparent)
      right / 2.5rem 100% no-repeat local,
    radial-gradient(farthest-side at 0 50%, rgb(20 20 32 / 9%), transparent)
      left / 0.9rem 100% no-repeat scroll,
    radial-gradient(farthest-side at 100% 50%, rgb(20 20 32 / 9%), transparent)
      right / 0.9rem 100% no-repeat scroll;
}
.band--night .matrix-scroll {
  background-image:
    linear-gradient(90deg, var(--band-bg) 60%, transparent),
    linear-gradient(270deg, var(--band-bg) 60%, transparent),
    radial-gradient(farthest-side at 0 50%, rgb(0 0 0 / 45%), transparent),
    radial-gradient(farthest-side at 100% 50%, rgb(0 0 0 / 45%), transparent);
}
.matrix {
  width: 100%;
  min-width: 34rem;
  border-collapse: collapse;
  font-size: var(--t-small);
  text-align: left;
}
.matrix th,
.matrix td {
  padding: var(--s4) var(--s5) var(--s4) 0;
  border-bottom: 1px solid var(--hair);
  vertical-align: baseline;
  white-space: nowrap;
}
.band--night .matrix th,
.band--night .matrix td {
  border-bottom-color: var(--on-dark-faint);
}
.matrix thead th {
  font-weight: var(--w-ui);
  letter-spacing: var(--track-hud);
  text-transform: uppercase;
  color: var(--ink-muted);
}
.band--night .matrix thead th {
  color: var(--on-dark-mu);
}
/* The method name is pinned so a scrolled answer never loses its question.
   It has to be opaque or the columns slide under it and turn to mud, and it
   carries a hairline on its right so the pin reads as deliberate. */
.matrix tbody th {
  font-weight: var(--w-ui);
  position: sticky;
  left: 0;
  background: var(--band-bg, var(--paper));
  border-right: 1px solid var(--hair);
  padding-right: var(--s6);
}
.band--night .matrix tbody th {
  border-right-color: var(--on-dark-faint);
}
.matrix td {
  color: var(--ink-muted);
}
.band--night .matrix td {
  color: var(--on-dark-mu);
}
/* The one row that is the answer. No fill, no accent — just ink at full
   strength, which is what "this is the one" looks like in a ledger. */
.matrix tr[data-row='fuime'] th,
.matrix tr[data-row='fuime'] td {
  color: var(--ink);
  font-weight: var(--w-ui);
  border-bottom: none;
}
.band--night .matrix tr[data-row='fuime'] th,
.band--night .matrix tr[data-row='fuime'] td {
  color: var(--on-dark);
}

/* ── faq ───────────────────────────────────────────────────────────────── */

.faq {
  border-top: 1px solid var(--hair);
}
.band--night .faq {
  border-top-color: var(--on-dark-faint);
}
.faq details {
  border-bottom: 1px solid var(--hair);
}
.band--night .faq details {
  border-bottom-color: var(--on-dark-faint);
}
.faq summary {
  display: flex;
  align-items: flex-start;
  justify-content: space-between;
  gap: var(--s5);
  padding-block: var(--s5);
  font-size: var(--t-h4);
  font-weight: var(--w-display);
  letter-spacing: -0.006em;
  line-height: 1.25;
  cursor: pointer;
  list-style: none;
}
.faq summary::-webkit-details-marker {
  display: none;
}
.faq summary::after {
  content: '';
  flex: none;
  width: 11px;
  height: 11px;
  margin-top: 8px;
  border-right: 1.5px solid currentColor;
  border-bottom: 1.5px solid currentColor;
  transform: rotate(45deg);
  opacity: 0.45;
  transition: transform var(--dur) var(--ease);
}
.faq details[open] summary::after {
  transform: rotate(225deg);
}
.faq__a {
  padding-bottom: var(--s5);
  color: var(--ink-muted);
  max-width: 62ch;
}
.band--night .faq__a {
  color: var(--on-dark-mu);
}

/* ── footer ────────────────────────────────────────────────────────────── */

.foot {
  background: var(--night);
  color: var(--on-dark);
  padding-block: var(--s9) var(--s7);
}
/* Measured before touching this: at 1440 the wordmark sat at x=170 and the two
   link columns were jammed together at x=1032 and x=1172, hard against the right
   edge, with ~740px of empty night between. That is what space-between does when
   one side is a single small mark and the other is a nested flex row — it pushes
   the whole row to the far edge, and the row's own gap keeps its columns tight.

   A grid fixes it because the columns are placed on tracks instead of packed.
   1.6fr for the brand cell gives the mark room to sit under the eye without
   owning half the footer, and the two equal tracks after it land the columns at
   roughly the thirds. display:contents on .foot__nav promotes the two .foot__col
   into this grid without adding a wrapper to the markup; it is safe here because
   the div carries no role, and the accessibility-tree bug with display:contents
   only bites elements that do. */
.foot__top {
  display: grid;
  grid-template-columns: minmax(0, 1.6fr) repeat(2, minmax(0, 1fr));
  gap: var(--s7);
  align-items: start;
  padding-bottom: var(--s8);
  border-bottom: 1px solid var(--on-dark-faint);
}
.foot__nav {
  display: contents;
}
.foot__col h4 {
  font-size: var(--t-small);
  font-weight: var(--w-ui);
  letter-spacing: 0.46px;
  text-transform: uppercase;
  color: var(--on-dark-mu);
  margin-bottom: var(--s4);
}
.foot__col a {
  display: block;
  /* The regrid above made each column a 279px track, and a display:block link
     fills it — which meant clicking 200px of empty night to the right of
     "Parents" navigated. max-content pulls the box back to the text while
     keeping each link on its own line. */
  width: max-content;
  max-width: 100%;
  font-size: var(--t-small);
  letter-spacing: 0.42px;
  color: var(--on-dark-mu);
  padding-block: var(--s1);
  transition: color var(--dur) var(--ease);
}
.foot__col a:hover {
  color: var(--on-dark);
}
/* The standing disclosure. It is four sentences, which is three too many for
   the space-between row below — as a second span it would have wrapped into
   the wordmark's column and read as an accident. Its own block, above the row,
   measure-capped so the lines break where a paragraph's lines should. */
.foot__legal {
  max-width: 92ch;
  padding-top: var(--s5);
  font-size: var(--t-small);
  line-height: 1.6;
  letter-spacing: 0.42px;
  color: var(--on-dark-mu);
  text-wrap: pretty;
}
.foot__bot {
  display: flex;
  flex-wrap: wrap;
  gap: var(--s4) var(--s6);
  justify-content: space-between;
  padding-top: var(--s5);
  font-size: var(--t-small);
  letter-spacing: 0.42px;
  color: var(--on-dark-mu);
}

/* ── scroll reveal ─────────────────────────────────────────────────────── */

.rise {
  opacity: 0;
  transform: translateY(14px);
  transition:
    opacity 420ms var(--ease),
    transform 420ms var(--ease);
}
.rise.is-in {
  opacity: 1;
  transform: none;
}

/* ── the arch ──────────────────────────────────────────────────────────── */

/* The logo is an archway, so the whole site is built out of archways. A
   border-radius larger than the box makes the browser clamp both radii to
   half the width — a true semicircle on straight legs, at any size. */

.arch {
  position: relative;
  overflow: hidden;
  border-radius: 9999px 9999px var(--r) var(--r);
  background: var(--night-2);
}
/* Descendant, not child: every image on this site ships inside a <picture>. */
.arch img {
  display: block;
  width: 100%;
  height: 100%;
  object-fit: cover;
}
.arch--portrait {
  aspect-ratio: 4 / 5;
}

/* Landscape media gets the same gesture at a size that survives the crop. */
.tile {
  width: 100%;
  /* The height="600" HTML attribute wins over aspect-ratio unless height is
     explicitly released, which was rendering an 800×605 source as a 351×600
     portrait crop. */
  height: auto;
  aspect-ratio: 4 / 3;
  object-fit: cover;
  margin-top: var(--s5);
  border-radius: var(--r-arch) var(--r-arch) var(--r) var(--r);
  background: var(--night-2);
}

/* ── portal: the closing archway ───────────────────────────────────────── */

.portal {
  position: relative;
  display: grid;
  place-items: center;
  isolation: isolate;
}
.portal__media {
  position: relative;
  width: min(620px, 100%);
  aspect-ratio: 4 / 5;
  border-radius: 9999px 9999px var(--r) var(--r);
  overflow: hidden;
  background: var(--night-2);
}
.portal__media img {
  width: 100%;
  height: 100%;
  object-fit: cover;
  transform: scale(1.06) translate3d(0, var(--py, 0px), 0);
  transition: transform 900ms var(--ease);
}
/* The copy sits over the bottom of the archway, where the photograph is
   already grass and shadow. The scrim guarantees it rather than hoping. */
.portal__media::after {
  content: '';
  position: absolute;
  inset: 0;
  background: linear-gradient(
    180deg,
    rgba(10, 12, 24, 0) 40%,
    rgba(10, 12, 24, 0.58) 72%,
    rgba(10, 12, 24, 0.93) 100%
  );
}
.portal__in {
  position: absolute;
  z-index: 1;
  inset: auto 0 var(--s8);
  display: grid;
  justify-items: center;
  gap: var(--s5);
  padding-inline: var(--s5);
  text-align: center;
  color: var(--on-dark);
}
/* Wider than the archway so the headline can breathe, narrow enough that it
   still reads as one centred column. */
.portal__in > * {
  width: 100%;
  max-width: 620px;
}

/* ── live fee calculator ───────────────────────────────────────────────── */

.calc {
  display: grid;
  grid-template-columns: minmax(0, 0.85fr) minmax(0, 1fr);
  gap: clamp(var(--s7), 6vw, var(--s9));
  align-items: center;
}
.calc__figure {
  font-size: var(--t-hero);
  font-weight: var(--w-display);
  letter-spacing: -0.026em;
  line-height: 1;
  font-variant-numeric: tabular-nums;
}
.calc__label {
  margin-bottom: var(--s3);
  font-size: var(--t-small);
  font-weight: var(--w-ui);
  letter-spacing: 0.46px;
  text-transform: uppercase;
  color: var(--ink-muted);
}
.band--night .calc__label {
  color: var(--on-dark-mu);
}
.calc__slider {
  margin-top: var(--s7);
}
.calc__range {
  -webkit-appearance: none;
  appearance: none;
  display: block;
  width: 100%;
  /* ASCENT specifies a 44px track with a 28px thumb, and 44x44 minimum hit
     areas on mobile. This box was 28px, so on a phone the only interactive
     control on the pricing page was 16px under the floor.

     The 44px is hit area, not paint: the runnable track keeps its 3px and
     Chrome centres it in the content box, so the thumb's -11.5px stays
     correct and nothing moves visually. The negative block margin gives the
     16px back to the layout, which keeps the slider's centre and the ticks
     below it exactly where they were. The 8px it bleeds into its neighbours
     lands on the figure above and the tick labels below — neither is
     clickable, so nothing gets stolen. */
  height: 44px;
  margin-block: -8px;
  background: transparent;
  cursor: grab;
}
.calc__range:active {
  cursor: grabbing;
}
.calc__range::-webkit-slider-runnable-track {
  height: 3px;
  border-radius: var(--r-pill);
  background: linear-gradient(
    90deg,
    var(--ink) 0 var(--fill, 30%),
    var(--hair) var(--fill, 30%) 100%
  );
}
.calc__range::-moz-range-track {
  height: 3px;
  border-radius: var(--r-pill);
  background: var(--hair);
}
.calc__range::-moz-range-progress {
  height: 3px;
  border-radius: var(--r-pill);
  background: var(--ink);
}
.calc__range::-webkit-slider-thumb {
  -webkit-appearance: none;
  appearance: none;
  width: 26px;
  height: 26px;
  margin-top: -11.5px;
  border: 0;
  border-radius: var(--r-pill);
  background: var(--ink);
  box-shadow: 0 2px 10px -2px var(--shadow-2);
  transition: transform var(--dur) var(--ease);
}
.calc__range::-moz-range-thumb {
  width: 26px;
  height: 26px;
  border: 0;
  border-radius: var(--r-pill);
  background: var(--ink);
  box-shadow: 0 2px 10px -2px var(--shadow-2);
}
.calc__range:hover::-webkit-slider-thumb,
.calc__range:focus-visible::-webkit-slider-thumb {
  transform: scale(1.12);
}
.calc__range:focus-visible {
  outline: none;
}
.calc__range:focus-visible::-webkit-slider-thumb {
  box-shadow: 0 0 0 4px var(--accent-tint);
}
.calc__ticks {
  display: flex;
  justify-content: space-between;
  margin-top: var(--s2);
  font-size: var(--t-small);
  letter-spacing: 0.42px;
  color: var(--ink-muted);
}
.band--night .calc__ticks {
  color: var(--on-dark-mu);
}
.calc__hint {
  margin-top: var(--s5);
  font-size: var(--t-small);
  letter-spacing: 0.42px;
  color: var(--ink-muted);
}

/* Every figure on the site, everywhere one appears.

   Measured, not assumed: General Sans already ships tabular figures, so
   font-variant-numeric changes nothing here and is kept only to state the
   requirement if the face is ever swapped. The thing that actually moved the
   numbers was kerning — "$1,111.11" sets 7px narrower than "$8,888.88" purely
   from the pairs around a 1, which is enough to make a column visibly breathe
   while the slider is dragged. font-kerning:none is the fix. */
.num,
.price__fig,
.invoice__row,
.fx-split__fig {
  font-variant-numeric: tabular-nums;
  font-kerning: none;
}
.num {
  transition: color var(--dur) var(--ease);
}
.num[data-live='true'] {
  color: inherit;
}
.invoice[data-live='true'] {
  box-shadow:
    0 1px 2px var(--shadow-1),
    0 22px 54px -20px var(--shadow-2),
    inset 0 0 0 1px var(--hair);
}

/* ── hero atmosphere ───────────────────────────────────────────────────── */

/* The photograph is lit by one laptop; the pointer adds a second light that
   only ever brightens, so the headline contrast measured at build time is a
   floor rather than an average. */
.hero__glow {
  position: absolute;
  inset: 0;
  z-index: 0;
  pointer-events: none;
  opacity: 0;
  background: radial-gradient(
    460px circle at var(--mx, 50%) var(--my, 65%),
    var(--accent-glow),
    transparent 72%
  );
  transition: opacity 700ms var(--ease);
}
.hero[data-pointer='true'] .hero__glow {
  opacity: 1;
}
.hero__media img {
  transform: scale(1.06) translate3d(var(--px, 0px), var(--py, 0px), 0);
  transition: transform 1200ms var(--ease);
}

/* 1px of noise stops the dusk gradient banding on cheap panels and reads as
   film grain rather than compression. */
.grain {
  position: absolute;
  inset: 0;
  z-index: 1;
  pointer-events: none;
  opacity: 0.05;
  mix-blend-mode: overlay;
  background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='140' height='140'%3E%3Cfilter id='n'%3E%3CfeTurbulence type='fractalNoise' baseFrequency='.82' numOctaves='3'/%3E%3C/filter%3E%3Crect width='140' height='140' filter='url(%23n)'/%3E%3C/svg%3E");
}

/* A full-bleed sliver of photograph between two bands. Carries the night
   colour across the seam so the cream band arrives as a cut, not a jolt. */
.seam {
  display: block;
  width: 100%;
  height: clamp(120px, 16vw, 210px);
  object-fit: cover;
  background: var(--night);
}

/* ── running strip of the work people actually do ──────────────────────── */

.strip {
  overflow: hidden;
  padding-block: var(--s5);
  border-block: 1px solid var(--on-dark-faint);
  -webkit-mask-image: linear-gradient(
    90deg,
    transparent,
    var(--mask-on) 12%,
    var(--mask-on) 88%,
    transparent
  );
  mask-image: linear-gradient(
    90deg,
    transparent,
    var(--mask-on) 12%,
    var(--mask-on) 88%,
    transparent
  );
}
.strip__row {
  display: flex;
  width: max-content;
  gap: var(--s7);
  animation: strip 42s linear infinite;
}
.strip:hover .strip__row {
  animation-play-state: paused;
}
.strip__item {
  display: flex;
  align-items: center;
  gap: var(--s5);
  font-size: var(--t-lead);
  color: var(--on-dark-mu);
  white-space: nowrap;
}
.strip__item::after {
  content: '';
  width: 5px;
  height: 5px;
  border-radius: var(--r-pill);
  background: var(--on-dark-mu);
  opacity: 0.7;
}
@keyframes strip {
  to {
    transform: translateX(-50%);
  }
}

/* ── reveal ────────────────────────────────────────────────────────────── */

.rise > .rise-i {
  opacity: 0;
  transform: translateY(16px);
  transition:
    opacity 620ms var(--ease),
    transform 620ms var(--ease);
  transition-delay: calc(var(--i, 0) * 70ms);
}
.rise.is-in > .rise-i {
  opacity: 1;
  transform: none;
}

/* ── the dive ──────────────────────────────────────────────────────────────
   Scroll flies into the laptop from the hero and lands inside the app.

   Everything here is the RESTING state: a still of the app open on the screen,
   in normal document flow. There is no sticky, no travel and no canvas until
   fx/dive.js has the frames and adds [data-fx-dive-on], and its sheet owns
   every declaration that differs. Delete the script and this section is a
   photograph, which is exactly what it should degrade to.

   Below .dive__app is a DOM copy of the UI baked into those frames. It is the
   same design at two sizes — a 1600px console, and a 420px phone app under
   phoneUpTo in dive.js — because the panel is scaled to fit the viewport, and a
   console scaled to a phone is a picture of an app rather than an app. */

.dive {
  position: relative;
  background: var(--night);
  color: var(--on-dark);
  /* The bloom composite.py lifts the baked plate with, so the DOM panel can
     carry the same exposure through the cross-fade. Same colour, one place. */
  --dive-bloom: #96d6e0;
}
.dive__track,
.dive__stage {
  position: relative;
}
.dive__still {
  display: block;
  width: 100%;
  height: auto;
}
.dive__canvas,
.dive__app {
  display: none;
}

/* ── the panel ─────────────────────────────────────────────────────────── */

.dive__app {
  width: 1600px;
  height: 1000px;
  grid-template-columns: 228px 1fr;
  background: var(--night);
  font-feature-settings: 'tnum' 1;
  text-align: left;
}

.dv-rail {
  border-right: 1px solid var(--on-dark-faint);
  padding: 22px 16px;
  display: flex;
  flex-direction: column;
  gap: 26px;
  background: var(--night-2);
}
.dv-brand {
  display: flex;
  align-items: center;
  gap: 9px;
  padding: 4px 8px;
}
.dv-brand__mark {
  width: 22px;
  height: 22px;
  border-radius: 6px;
  background: linear-gradient(150deg, var(--accent-lit), var(--accent));
  box-shadow: 0 0 0 1px var(--accent-glow);
}
.dv-brand__name {
  font-size: 16px;
  font-weight: 600;
  letter-spacing: -0.01em;
}

.dv-nav {
  display: flex;
  flex-direction: column;
  gap: 2px;
}
.dv-nav__label {
  font-size: 11px;
  letter-spacing: 0.08em;
  text-transform: uppercase;
  color: var(--on-dark-mu);
  opacity: 0.62;
  padding: 0 8px 8px;
}
.dv-nav__item {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 10px;
  padding: 8px 9px;
  border-radius: 7px;
  color: var(--on-dark-mu);
  font-size: 14px;
  font-weight: var(--w-ui);
}
.dv-nav__item.is-on {
  background: var(--on-dark-faint);
  color: var(--on-dark);
}
.dv-nav__dot {
  width: 6px;
  height: 6px;
  border-radius: 50%;
  background: var(--accent-lit);
}
.dv-nav__count {
  font-size: 11px;
  color: var(--on-dark-mu);
  font-variant-numeric: tabular-nums;
}

.dv-rail__foot {
  margin-top: auto;
  display: flex;
  align-items: center;
  gap: 9px;
  padding: 8px;
}
.dv-avatar {
  width: 26px;
  height: 26px;
  border-radius: 50%;
  flex: none;
  background: linear-gradient(160deg, var(--ink-soft), var(--night-2));
  border: 1px solid var(--on-dark-faint);
}
.dv-who {
  font-size: 13px;
}
.dv-who span {
  display: block;
  font-size: 11px;
  color: var(--on-dark-mu);
}

.dv-main {
  padding: 26px 30px;
  display: flex;
  flex-direction: column;
  gap: 20px;
  overflow: hidden;
}
.dv-top {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 20px;
}
.dv-top__day {
  font-size: 24px;
  font-weight: var(--w-ui);
  letter-spacing: -0.02em;
}
.dv-top__day em {
  font-style: normal;
  color: var(--on-dark-mu);
}
.dv-top__acts {
  display: flex;
  gap: 9px;
  flex: none;
}
.dv-btn {
  font-size: 13px;
  font-weight: var(--w-ui);
  padding: 8px 14px;
  border-radius: 8px;
  border: 1px solid var(--on-dark-faint);
  color: var(--on-dark-mu);
  white-space: nowrap;
}
.dv-btn--accent {
  background: var(--accent);
  border-color: var(--accent);
  color: var(--paper);
}

.dv-grid {
  display: grid;
  grid-template-columns: 1.55fr 1fr;
  gap: 16px;
}
.dv-card {
  border: 1px solid var(--on-dark-faint);
  border-radius: 12px;
  background: var(--night-2);
  padding: 18px 20px;
}
.dv-card__hd {
  display: flex;
  align-items: baseline;
  justify-content: space-between;
  gap: 12px;
  font-size: 12px;
  letter-spacing: 0.06em;
  text-transform: uppercase;
  color: var(--on-dark-mu);
  margin-bottom: 12px;
}
.dv-figure {
  font-size: 44px;
  font-weight: var(--w-ui);
  letter-spacing: -0.03em;
  line-height: 1;
}
.dv-figure sup {
  font-size: 20px;
  top: -0.9em;
  position: relative;
  margin-left: 1px;
}
.dv-figure--sm {
  font-size: 34px;
}
.dv-figure--sm sup {
  font-size: 16px;
}
.dv-sub {
  margin-top: 8px;
  font-size: 13px;
  color: var(--on-dark-mu);
}
.dv-sub b {
  color: var(--good);
  font-weight: var(--w-ui);
}
/* Hand-plotted, so the shape is a decision rather than noise. */
.dv-spark {
  margin-top: 16px;
  width: 100%;
  height: 92px;
  display: block;
}
.dv-aside {
  display: flex;
  flex-direction: column;
  justify-content: space-between;
}
.dv-aside .dv-sub {
  margin-top: 18px;
}
.dv-bar {
  margin-top: 14px;
  height: 8px;
  border-radius: 99px;
  background: var(--on-dark-faint);
  overflow: hidden;
}
.dv-bar i {
  display: block;
  height: 100%;
  width: 62%;
  border-radius: 99px;
  background: linear-gradient(90deg, var(--accent), var(--accent-lit));
}
.dv-legend {
  margin-top: 10px;
  display: flex;
  justify-content: space-between;
  font-size: 12px;
  color: var(--on-dark-mu);
}

.dv-ledger {
  flex: 1;
  min-height: 0;
  overflow: hidden;
}
.dv-rows {
  width: 100%;
  border-collapse: collapse;
}
.dv-rows th {
  text-align: left;
  font-size: 11px;
  font-weight: var(--w-ui);
  letter-spacing: 0.07em;
  text-transform: uppercase;
  color: var(--on-dark-mu);
  padding: 0 0 10px;
  border-bottom: 1px solid var(--on-dark-faint);
}
.dv-rows td {
  padding: 13px 0;
  border-bottom: 1px solid var(--on-dark-faint);
  font-size: 14px;
  color: var(--on-dark);
}
.dv-rows tr:last-child td {
  border-bottom: 0;
}
.dv-mu {
  color: var(--on-dark-mu);
}
.dv-num {
  text-align: right;
  font-variant-numeric: tabular-nums;
}
.dv-cli {
  display: flex;
  align-items: center;
  gap: 11px;
}
.dv-cli__ini {
  width: 28px;
  height: 28px;
  border-radius: 50%;
  flex: none;
  display: grid;
  place-items: center;
  font-size: 11px;
  font-weight: 600;
  background: var(--on-dark-faint);
  color: var(--on-dark-mu);
}
.dv-cli span span {
  display: block;
  font-size: 12px;
  color: var(--on-dark-mu);
}
.dv-chip {
  display: inline-block;
  font-size: 11px;
  font-weight: var(--w-ui);
  padding: 3px 9px;
  border-radius: 99px;
  border: 1px solid currentColor;
  white-space: nowrap;
}
.dv-chip--paid {
  color: var(--good);
}
.dv-chip--sent {
  color: var(--on-dark-mu);
}
.dv-chip--due {
  color: var(--accent-lit);
}

/* The phone app. Same content, laid out for a hand — this is what the dive
   lands on below 820px, because the console above would fit the viewport at
   roughly a quarter scale and nothing on it would be readable. The two columns
   that go are the ones a phone can infer: what the job was, and the number the
   client paid before the fee. What you keep stays. */
@media (max-width: 820px) {
  .dive__app {
    width: 420px;
    height: 880px;
    grid-template-columns: 1fr;
    border-radius: 20px;
    overflow: hidden;
  }
  .dv-rail {
    display: none;
  }
  .dv-main {
    padding: 20px 16px;
    gap: 14px;
  }
  .dv-top__day {
    font-size: 19px;
  }
  .dv-top__acts .dv-btn:not(.dv-btn--accent) {
    display: none;
  }
  .dv-grid {
    grid-template-columns: 1fr;
    gap: 12px;
  }
  .dv-card {
    padding: 15px 16px;
  }
  .dv-figure {
    font-size: 38px;
  }
  .dv-figure--sm {
    font-size: 26px;
  }
  .dv-spark {
    height: 62px;
    margin-top: 12px;
  }
  .dv-aside .dv-sub {
    margin-top: 12px;
  }
  .dv-rows th:nth-child(2),
  .dv-rows td:nth-child(2),
  .dv-rows th:nth-child(4),
  .dv-rows td:nth-child(4) {
    display: none;
  }
  .dv-rows td {
    padding: 10px 0;
    font-size: 13px;
  }
  /* The panel is a fixed box, not a scroller, so rows past what fits are not
     shortened — they are cut in half at the edge. Five is what fits. */
  .dv-rows tbody tr:nth-child(n + 6) {
    display: none;
  }
  .dv-cli__ini {
    width: 24px;
    height: 24px;
  }
}

/* ── /start: the sign-up on the screen ─────────────────────────────────────
   The other thing the dive can land on. Not a picture of an app — the sign-up
   itself, so the form inside this panel is real and it is the only exit on
   that page.

   Two rules make it different from the console above, and both matter:

   1. It is the RESTING state, not something the dive produces. Everything
      that can stop the dive from running — scripts off, reduced motion, a
      ladder that 404s — leaves the base rules in charge, and a reader in any
      of those states still has to be able to sign up. So the panel lays out
      in normal flow underneath the poster and is usable on its own; fx/dive.js
      is what lifts it out of the flow, and only once there are frames to hand
      off from.
   2. It is sized in fixed px like every .dv- class, not in the viewport-
      relative --t- tokens. The panel is a 1600px box scaled to fit, so a vw
      unit inside it answers to the window rather than to the panel and the
      layout would drift with the scale instead of holding.

   Authored against a resting scale of roughly 0.8, which is what a 1600×1000
   panel fits to on a laptop. */

.dive__app--join {
  display: grid;
  place-items: center;
  grid-template-columns: 1fr;
  width: auto;
  height: auto;
  padding: var(--s9) var(--s5);
}
[data-fx-dive-on] .dive__app--join {
  width: 1600px;
  height: 1000px;
  padding: 0;
}

.dv-join {
  display: grid;
  justify-items: center;
  width: min(100%, 860px);
  text-align: center;
}
.dv-join__mark {
  width: 48px;
  height: 39px;
  margin-bottom: 20px;
  /* Not inherited. The symbol carries no fill of its own, so without this the
     mark paints black — which on this background is a mark that is not there. */
  fill: currentColor;
  color: var(--on-dark);
  opacity: 0.72;
}
.dv-join__eyebrow {
  margin-bottom: 14px;
  font-size: 15px;
  font-weight: var(--w-ui);
  letter-spacing: 1.6px;
  text-transform: uppercase;
  color: var(--accent-lit);
}
.dv-join__h {
  margin-bottom: 22px;
  font-size: 68px;
  font-weight: var(--w-display);
  line-height: 1.06;
  letter-spacing: -0.022em;
  color: var(--on-dark);
  text-wrap: balance;
}
.dv-join__lead {
  max-width: 44ch;
  margin-bottom: 34px;
  font-size: 22px;
  line-height: 1.55;
  color: var(--on-dark-mu);
  text-wrap: pretty;
}
.dv-join__fine {
  max-width: 52ch;
  /* Small, because the row above it is the capture's status line: it reserves
     26px whether or not there is a message in it, and that reservation is the
     real gap between the button and this. */
  margin-top: 10px;
  font-size: 16px;
  line-height: 1.5;
  color: var(--on-dark-mu);
}

/* The site's own capture, one size up: the panel it sits in is drawn at 1600px
   and scaled down, so a 46px field would land at about 37px on the glass. */
.dv-join .capture {
  width: 100%;
  max-width: 620px;
}
.dv-join .capture input {
  height: 66px;
  font-size: 20px;
}
.dv-join .capture .btn {
  height: 66px;
  padding-inline: 34px;
  font-size: 17px;
}
.dv-join .capture__msg {
  min-height: 26px;
  font-size: 16px;
}
.dv-join .capture__done {
  justify-content: center;
  font-size: 22px;
}

/* The sign-up at phone size, in its own block rather than folded into the
   dashboard's — it has to come after the desktop rules above or the 1600×1000
   box wins on source order and the whole panel lands on a phone at about a
   fifth scale, with a 10px email field on it. */
@media (max-width: 820px) {
  [data-fx-dive-on] .dive__app--join {
    width: 420px;
    height: 880px;
    border-radius: 20px;
    overflow: hidden;
  }
  .dv-join {
    width: min(100%, 340px);
  }
  .dv-join__mark {
    width: 32px;
    height: 26px;
    margin-bottom: 14px;
  }
  .dv-join__eyebrow {
    margin-bottom: 10px;
    font-size: 12px;
    letter-spacing: 1.3px;
  }
  .dv-join__h {
    margin-bottom: 16px;
    font-size: 34px;
  }
  .dv-join__lead {
    max-width: 26ch;
    margin-bottom: 24px;
    font-size: 15px;
  }
  .dv-join__fine {
    margin-top: 18px;
    font-size: 12px;
  }
  /* The field and the button stop sharing a line: side by side inside 340px
     they are two things too small to hit rather than one of each. */
  .dv-join .capture__row {
    flex-direction: column;
    align-items: stretch;
    gap: 10px;
    border-radius: 22px;
  }
  .dv-join .capture input {
    height: 50px;
    padding-inline: var(--s4);
    font-size: 16px;
    text-align: center;
  }
  .dv-join .capture .btn {
    height: 50px;
    padding-inline: var(--s5);
    font-size: 15px;
  }
  .dv-join .capture__msg {
    min-height: 20px;
    font-size: 13px;
  }
  .dv-join .capture__done {
    font-size: 16px;
  }
}

/* ── the lede ──────────────────────────────────────────────────────────────
   What the product is, on the first screen, in words.

   / serves start.html, so this is the front door. A dark room and the word
   SCROLL is a good second impression and a bad first one: the reader has to
   decide whether to invest four screens of scroll before anything has told
   them what they would be scrolling toward.

   Same construction as the cue directly below — a child of the track, sitting
   in its first viewport, gated on the dive being armed. It scrolls away with
   the document, which is why nothing here animates: the page moving is the
   animation, and a fade tied to scroll position would be a second scroll
   listener for an effect the browser already performs.

   Sized in viewport units rather than the type tokens because it is over a
   photograph, not on a band, and it wants to hold roughly the same share of
   the frame at every width. */
.dive__lede {
  display: none;
}
[data-fx-dive-on] .dive__lede {
  position: absolute;
  left: 0;
  top: 0;
  z-index: 3;
  display: grid;
  justify-items: center;
  align-content: start;
  width: 100%;
  /* One viewport, matching the cue, so the two divide the same screen between
     them — this one from the top, the cue from the bottom — and neither has to
     know the other's height. */
  height: 100vh;
  height: 100svh;
  padding: clamp(28px, 7svh, 74px) var(--gut) 0;
  text-align: center;
  /* The room underneath still has to be reachable; only the link is a target. */
  pointer-events: none;
}
/* The plate is a night interior with a lit city in the middle of it, and the
   middle is where a phone's crop lands. Type over that is legible in the
   render and not on a screen in daylight, so the text carries its own ground:
   a short wash from the top edge that is opaque where the words are and gone
   before the window is. Behind the text and above the canvas. */
[data-fx-dive-on] .dive__lede::before {
  content: '';
  position: absolute;
  inset: 0 0 auto;
  z-index: -1;
  height: 82%;
  background: linear-gradient(
    to bottom,
    rgba(20, 20, 32, 0.94) 0%,
    rgba(20, 20, 32, 0.9) 46%,
    rgba(20, 20, 32, 0.55) 74%,
    rgba(20, 20, 32, 0) 100%
  );
}
.lede__kicker {
  margin-bottom: clamp(10px, 1.6svh, 18px);
  font-size: var(--t-small);
  font-weight: var(--w-ui);
  letter-spacing: 2.4px;
  text-transform: lowercase;
  color: var(--accent-lit);
}
.lede__h {
  max-width: 17ch;
  font-size: clamp(1.75rem, 1.1rem + 2.5vw, 3.125rem);
  font-weight: var(--w-display);
  line-height: 1.08;
  letter-spacing: -0.022em;
  color: var(--on-dark);
  text-wrap: balance;
}
.lede__sub {
  max-width: 46ch;
  margin-top: clamp(12px, 2svh, 22px);
  font-size: clamp(0.9375rem, 0.86rem + 0.35vw, 1.125rem);
  line-height: 1.55;
  /* Brighter than --on-dark-mu, which is calibrated for support copy on a flat
     band. This sits on a photograph of a lit city, and the wash above only
     takes the ground so far. */
  color: rgba(237, 237, 243, 0.8);
  text-wrap: pretty;
}
/* Phone only. A wheel crosses the dive in two flicks; a thumb does not, and
   the reader who has read the four lines above and simply wants the form had
   no way to get to it. On a laptop the same link would be an apology for an
   animation that is not costing anyone anything. */
.lede__skip {
  display: none;
}
@media (max-width: 820px) {
  .lede__skip {
    display: inline-block;
    margin-top: clamp(16px, 2.6svh, 26px);
    padding: 11px 20px;
    border-radius: var(--r-pill);
    box-shadow: inset 0 0 0 1px var(--on-dark-faint);
    background: rgba(237, 237, 243, 0.06);
    font-size: var(--t-small);
    font-weight: var(--w-ui);
    letter-spacing: 0.3px;
    color: var(--on-dark);
    text-decoration: none;
    pointer-events: auto;
  }
}

/* The end of the flight, as a coordinate — see the note in start.html. Zero
   sized and pushed one viewport up from the foot of the track, which is the
   scroll offset at which the stage stops being pinned and the panel is at
   rest. Only meaningful while the dive is armed; when it is not, the track is
   auto-height, the panel is in normal flow, and the anchor is an empty span
   sitting harmlessly above it. */
.dive__anchor {
  position: absolute;
  left: 0;
  bottom: 100vh;
  bottom: 100svh;
  width: 1px;
  height: 1px;
}

/* ── the scroll cue ────────────────────────────────────────────────────────
   A child of the track rather than the pinned stage, so it leaves on the first
   screen of scroll instead of riding the stage all the way down. It answers
   one question — is there anything below this? — and once that is answered it
   should not still be on screen. Hence the height: it is a full first viewport
   with the cue sitting at the foot of it, not a thing stuck to the bottom of a
   track that is fifteen screens tall.

   Hidden unless the dive is armed. With no JS or under reduced motion nothing
   scrubs, and an invitation to scroll through an animation that will not play
   is worse than no invitation. */
.dive__cue {
  display: none;
}
[data-fx-dive-on] .dive__cue {
  position: absolute;
  left: 50%;
  top: 0;
  z-index: 3;
  display: grid;
  align-content: end;
  justify-items: center;
  gap: 10px;
  height: 100vh;
  height: 100svh;
  padding-bottom: 28px;
  transform: translateX(-50%);
  font-size: var(--t-small);
  letter-spacing: 1.4px;
  text-transform: uppercase;
  color: var(--on-dark-mu);
  pointer-events: none;
}
.dive__cue span {
  width: 1px;
  height: 34px;
  background: linear-gradient(var(--on-dark-faint), var(--on-dark-mu));
  animation: cue 2.1s var(--ease) infinite;
  transform-origin: 50% 0;
}
@keyframes cue {
  0%,
  100% {
    transform: scaleY(0.35);
    opacity: 0.35;
  }
  45% {
    transform: scaleY(1);
    opacity: 1;
  }
}
@media (prefers-reduced-motion: reduce) {
  .dive__cue span {
    animation: none;
  }
}

/* ── /start: the second exit ───────────────────────────────────────────────
   The sign-up on the glass is four screens behind anyone who has read to the
   bottom of this page, and a reader at the end of a page will leave rather
   than scroll back up to a form. Centred rather than left-set like the home
   page's portal, because it is the last thing on the page and has no image
   beside it to balance against. */
.st-join {
  display: grid;
  justify-items: center;
  text-align: center;
}
.st-join .h2 {
  max-width: 20ch;
}
.st-join .lead {
  max-width: 44ch;
}
.st-join .capture {
  width: 100%;
  max-width: 540px;
}
/* The confirmation is a flex row inside a centred grid cell, and a flex row
   ignores the text-align above it. */
.st-join .capture__done {
  justify-content: center;
}

/* ── responsive ────────────────────────────────────────────────────────── */

@media (max-width: 900px) {
  .grid--3,
  .grid--4 {
    grid-template-columns: repeat(2, 1fr);
  }
  /* The ruled rows are a list, not a grid, at every width. */
  .rows.grid--3,
  .rows.grid--4 {
    grid-template-columns: 1fr;
  }
  /* Two columns of prose in 9rem is not a column, it is a stack of hyphens.
     The label carries the icon on its own line and the reasoning drops under
     it, which is how a phone reads a ledger anyway. */
  .card.card--row {
    grid-template-columns: 22px minmax(0, 1fr);
    row-gap: var(--s2);
  }
  .card--row h3,
  .card--row h4 {
    grid-column: 2;
  }
  .card--row > p {
    grid-column: 2;
  }
  .card.card--fig {
    grid-template-columns: minmax(0, 1fr) auto;
  }
  .split {
    grid-template-columns: 1fr;
  }
  .hero__body {
    max-width: 620px;
  }
  /* Below 900 the headline spans the whole column, so the desktop plate's
     falloff lands underneath it and the type sits back on the bokeh — measured
     2.5:1 at 390 and 2.8:1 at 768. There is no right third to protect at this
     width, so the plate goes vertical. Lightest gradient that still clears the
     bar with room for the clip's bright frames: 6.7–7.9:1 across both pages. */
  .hero--short .hero__media::after {
    background: linear-gradient(
      180deg,
      rgba(10, 12, 24, 0.76) 0%,
      rgba(10, 12, 24, 0.66) 55%,
      rgba(10, 12, 24, 0.26) 100%
    );
  }
  /* Two columns computed to 138.75px / 163.25px at 390 and clipped the invoice
     mid-number — the best object on the site, unreadable on the device the
     primary reader is actually holding. */
  .calc {
    grid-template-columns: 1fr;
  }
}

@media (max-width: 720px) {
  /* A phone hero is short, so the copy lands on the lit table rather than on
     empty sky. The scrim has to reach further down than it does on desktop.

     Two layers. The first is the desktop idea, adjusted: heavy at the top for
     the headline, gone by the bottom so the lit table survives. The second is
     a band that peaks at 72% and eases off again before the frame ends —
     because the line under the email field sits exactly there, on the widest
     part of the town, and measured 2.32:1 with only the first layer under it.
     The desktop fix is a bottom-LEFT ellipse, which is the wrong shape here:
     at 390 that line spans nearly the full width and its right half falls off
     the ellipse entirely. A band is the shape a narrow column needs. */
  .hero__media::after {
    background:
      linear-gradient(
        180deg,
        rgba(10, 12, 24, 0) 44%,
        rgba(10, 12, 24, 0.58) 72%,
        rgba(10, 12, 24, 0.3) 100%
      ),
      linear-gradient(
        180deg,
        rgba(10, 12, 24, 0.62) 0%,
        rgba(10, 12, 24, 0.44) 46%,
        rgba(10, 12, 24, 0) 88%
      );
  }
  .nav__burger {
    display: block;
  }
  .nav__links {
    position: fixed;
    inset: 72px 0 auto;
    flex-direction: column;
    align-items: stretch;
    gap: 0;
    margin: 0;
    padding: var(--s5) var(--gut) var(--s7);
    background: var(--cream);
    color: var(--ink);
    border-bottom: 1px solid var(--hair);
    transform: translateY(-8px);
    opacity: 0;
    pointer-events: none;
    transition:
      opacity var(--dur) var(--ease),
      transform var(--dur) var(--ease);
  }
  .nav[data-open='true'] .nav__links {
    opacity: 1;
    transform: none;
    pointer-events: auto;
  }
  .nav__links .nav__link {
    padding-block: var(--s4);
    border-bottom: 1px solid var(--hair);
    opacity: 1;
    font-size: var(--t-body);
  }
  .nav__links .btn {
    margin-top: var(--s5);
    width: 100%;
  }

  .grid--2,
  .grid--3,
  .grid--4 {
    grid-template-columns: 1fr;
  }
  /* height: 100svh is a desktop idea — it makes the photograph fill the screen
     on a machine where the copy is three short lines. On a phone the same copy
     is nine wrapped lines and the hero has to be as tall as its contents, or
     overflow: hidden eats the bottom of them. 660 stays as the floor so a short
     hero still reads as a full frame. */
  .hero {
    min-height: 660px;
    height: auto;
    max-height: none;
  }
  .hero__body {
    max-width: none;
  }
  /* The disclaimer is pinned to the bottom of the photograph on desktop, where
     there is a clear band of night under the note to pin it to. On a phone the
     capture row stacks and the lead wraps to four lines, so the flow column
     grows past the pin and the note lands on top of the fine print. Measured at
     390 the two overlap by 19px. Dropping it back into flow lets the column push
     it down instead; margin-top: auto keeps it on the floor of the hero while
     the content is short enough for that to mean anything. */
  .hero__disclaimer {
    position: static;
    transform: none;
    margin-top: auto;
    padding-top: var(--s6);
  }
  .capture__row {
    flex-direction: column;
    align-items: stretch;
    border-radius: var(--r-card);
    padding: var(--s3);
  }
  .capture input {
    padding-inline: var(--s3);
  }
  .capture .btn {
    width: 100%;
  }
  /* .foot__nav is display:contents now, so it has no gap of its own to shrink —
     the rule that used to live here was doing nothing. Three tracks are far too
     narrow at 390 (they would be ~90px each), so drop to two and let the
     wordmark span them, which is the stacked shape the flex version fell into by
     wrapping. Two equal columns also stops COMPANY hugging PRODUCT with 200px of
     empty night to its right. */
  .foot__top {
    grid-template-columns: repeat(2, minmax(0, 1fr));
    gap: var(--s7) var(--s6);
  }
  .foot__top > .mark {
    grid-column: 1 / -1;
  }

  /* The closing copy is absolutely positioned over the bottom of the archway,
     and that works while the photograph is 775px tall and the copy is 326 — it
     lands on grass and shadow, which is what the scrim was built for.

     It stops working here, and the rule directly above is half the reason: the
     capture row stacks on a phone, which makes the form 154px instead of 86,
     while the photograph shrinks with the viewport. Measured at 390 the copy is
     395px against a 438px photograph and pokes 21px out of the top of it. The
     arch stops being a picture and becomes a texture behind a wall of text, the
     eyebrow lands on bare band above the frame where no scrim can reach it, and
     the headline sits on the bright part of the sky.

     So on a phone the two stop overlapping. The archway is a picture and the
     words go under it, which is the same composition read top to bottom instead
     of front to back. The scrim then only has to sink the photograph into the
     night band, not carry type, so it comes down to a fade. */
  .portal {
    gap: var(--s6);
    padding-bottom: var(--s8);
  }
  .portal__in {
    position: static;
    padding-inline: 0;
  }
  .portal__media::after {
    background: linear-gradient(
      180deg,
      rgba(10, 12, 24, 0) 58%,
      rgba(10, 12, 24, 0.34) 100%
    );
  }
}

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