/* ===========================================================================
   CASEFLOW NOTE — marketing site
   ---------------------------------------------------------------------------
   Aesthetic: "Daylight Eucalypt" — the app's own design system (Fraunces +
   DM Sans, cream + eucalyptus teal, from yarn-note/utils/theme.ts) in a bright,
   warm, airy register. The organising image is the light in the hero film:
   late-afternoon sun, a warm room, a phone in one hand.

   Light grounds carry the whole page. Exactly two dark moments — the practice
   doctrine quote and the footer — and both are deep EUCALYPTUS, not black, so
   the page never goes cold.

   Zero dependencies — no GSAP, no Lenis, no CDN JS. A content site with an
   LCP budget of 2.5s does not get to spend 90KB on scroll easing; the reveal
   work is ~2KB of IntersectionObserver in script.js.
   =========================================================================== */

/* ===========================================================================
   0 · FONTS — self-hosted (was Google Fonts: render-blocking cross-origin
   fetch + FOUT swap, the page's only remaining CLS residual).
   ---------------------------------------------------------------------------
   Weight/style ranges mirror actual usage in this file (Fraunces 300–600
   roman+italic; DM Sans 400–700 roman, 400 italic only). Latin subset only —
   Google's css2 also serves latin-ext/vietnamese ranges this site never uses.

   Fraunces MUST be requested from Google's css2 API with the SOFT and WONK
   axes explicit in the family tuple (…SOFT,WONK,wght@…) or Google silently
   drops them from the delivered variable font — confirmed against the file
   this site's OWN production <link> currently resolves to: it has only
   `opsz`/`wght` in fvar, no SOFT. Every `font-variation-settings: "SOFT" …`
   rule below has been a no-op in production. These files restore both axes
   (SOFT 0–100 default 0, WONK 0–1 default 1 — Fraunces' own upstream
   default). Verified via fontTools fvar dump, not by eye — on macOS the
   Iowan Old Style fallback reads close enough to Fraunces to pass a glance.
   =========================================================================== */
@font-face {
  font-family: 'DM Sans';
  font-style: normal;
  font-weight: 400 700;
  font-display: swap;
  src: url('assets/fonts/dm-sans-roman.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: 'DM Sans';
  font-style: italic;
  font-weight: 400;
  font-display: swap;
  src: url('assets/fonts/dm-sans-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: 'Fraunces';
  font-style: normal;
  font-weight: 300 600;
  font-display: swap;
  src: url('assets/fonts/fraunces-roman.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: 'Fraunces';
  font-style: italic;
  font-weight: 300 600;
  font-display: swap;
  src: url('assets/fonts/fraunces-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;
}

/* ===========================================================================
   1 · TOKENS
   =========================================================================== */
:root {
  /* — Paper (the page lives here) — */
  --paper:    #FBF9F5;
  --paper-2:  #F4F1EA;
  --paper-3:  #EDE8DE;
  --surface:  #FFFFFF;
  --line:     #E6E1D6;
  --line-2:   #D5CEC0;

  /* — Deep eucalyptus (the only dark grounds) — */
  --deep:     #103129;
  --deep-2:   #164036;

  /* — Eucalyptus (brand) — */
  --euc:       #217A6A;   /* AA on white; the workhorse accent */
  --euc-mid:   #2A8C7A;   /* app primary — decorative use only */
  --euc-light: #4FC0A6;   /* accents on the deep grounds */
  --euc-deep:  #12483E;
  --euc-wash:  #E6F1ED;
  --euc-wash2: #D5E9E2;

  /* — Warmth + signal — */
  --gold:      #B8862B;   /* decorative only — fails AA as text */
  --gold-ink:  #8A6520;   /* >=4.55:1 on every surface — use for any gold TEXT */
  --gold-wash: #FAF0DC;
  --clay:      #A05839;   /* >=4.57:1 on every surface */
  --clay-wash: #F9EBE4;

  /* — Ink — */
  --ink:    #16241F;
  --ink-2:  #4E605A;
  --ink-3:  #636E69;   /* >=4.55:1 on EVERY surface incl. the tinted washes.
                          Was #82908A (3.17:1). Tuned against --clay-wash, the
                          darkest ground it lands on — tuning against --paper
                          alone still failed on the wash panels. */
  --on-deep:   #EDF4F1;
  --on-deep-2: rgba(237,244,241,.76);
  --on-deep-3: rgba(237,244,241,.58);

  /* — Type — */
  --display: "Fraunces", "Iowan Old Style", Palatino, Georgia, serif;
  --body:    "DM Sans", -apple-system, BlinkMacSystemFont, "Segoe UI", sans-serif;
  --mono:    ui-monospace, "SF Mono", "JetBrains Mono", Menlo, monospace;

  /* — Rhythm — */
  --gut:    clamp(20px, 5vw, 40px);
  --band-y: clamp(72px, 10vw, 138px);
  --wrap:   1180px;

  /* — Radii — */
  --r-sm: 10px;
  --r-md: 16px;
  --r-lg: 24px;
  --r-xl: 34px;
  --r-pill: 999px;

  /* — Motion — */
  --ease:     cubic-bezier(.22,.61,.36,1);
  --ease-out: cubic-bezier(.16,1,.3,1);

  /* — Elevation, warm-tinted so cards sit on cream not grey — */
  --lift-1: 0 1px 2px rgba(22,36,31,.04), 0 6px 16px -8px rgba(22,36,31,.08);
  --lift-2: 0 2px 4px rgba(22,36,31,.04), 0 18px 40px -18px rgba(22,36,31,.16);
  --lift-3: 0 30px 70px -30px rgba(22,36,31,.24);
}

/* ===========================================================================
   2 · RESET & BASE
   =========================================================================== */
*, *::before, *::after { box-sizing: border-box; }

/* `hidden` must beat the component display rules below — the App Store CTA
   and the sound toggle both rely on it to stay off the page. */
[hidden] { display: none !important; }

html {
  -webkit-text-size-adjust: 100%;
  scroll-behavior: smooth;
  scroll-padding-top: 84px;
}

body {
  margin: 0;
  background: var(--paper);
  color: var(--ink);
  font-family: var(--body);
  font-size: 16.5px;
  line-height: 1.62;
  -webkit-font-smoothing: antialiased;
  -moz-osx-font-smoothing: grayscale;
  overflow-x: hidden;
}

img, svg, video { display: block; max-width: 100%; }
h1, h2, h3, p, figure, blockquote, ol, ul { margin: 0; }
ol, ul { padding: 0; list-style: none; }
a { color: inherit; text-decoration: none; }
button { font: inherit; color: inherit; background: none; border: 0; cursor: pointer; }
table { border-collapse: collapse; width: 100%; }
code { font-family: var(--mono); font-size: .88em; letter-spacing: -.01em; }

::selection { background: var(--euc-wash2); color: var(--euc-deep); }

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

.skip-link {
  position: fixed; top: 10px; left: 10px; z-index: 999;
  transform: translateY(-160%);
  background: var(--euc); color: #fff;
  padding: 11px 18px; border-radius: var(--r-pill);
  font-size: 14px; font-weight: 600;
  transition: transform .25s var(--ease);
}
.skip-link:focus { transform: none; }

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

.wrap {
  width: 100%; max-width: var(--wrap);
  margin-inline: auto; padding-inline: var(--gut);
}
.wrap-narrow { max-width: 760px; }

/* ===========================================================================
   3 · ATMOSPHERE
   Grain stays, but on a light ground it multiplies at low opacity — an
   overlay blend (which is what a dark page wants) turns cream milky.
   =========================================================================== */
.grain {
  position: fixed; inset: -50%;
  z-index: 3; pointer-events: none;
  opacity: .16; mix-blend-mode: multiply;
  background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='200' height='200'%3E%3Cfilter id='n'%3E%3CfeTurbulence type='fractalNoise' baseFrequency='.85' numOctaves='4' stitchTiles='stitch'/%3E%3CfeColorMatrix type='saturate' values='0'/%3E%3C/filter%3E%3Crect width='200' height='200' filter='url(%23n)' opacity='.5'/%3E%3C/svg%3E");
  animation: grain-drift 7s steps(6) infinite;
}
@keyframes grain-drift {
  0%,100% { transform: translate(0,0) }
  20% { transform: translate(-2%,1.5%) }
  40% { transform: translate(1.5%,-1%) }
  60% { transform: translate(-1%,-2%) }
  80% { transform: translate(2%,1%) }
}

/* ===========================================================================
   4 · TYPE
   =========================================================================== */
.display {
  font-family: var(--display);
  font-weight: 300;
  font-variation-settings: "SOFT" 34, "opsz" 144;
  font-size: clamp(2.35rem, 4.9vw, 4rem);
  line-height: 1.02;
  letter-spacing: -.028em;
  color: var(--ink);
  text-wrap: balance;
}
.display em {
  font-style: italic; font-weight: 300;
  font-variation-settings: "SOFT" 64, "opsz" 144;
  color: var(--euc);
}
.display-block em { display: block; }
.display-sm { font-size: clamp(2.1rem, 5.2vw, 3.5rem); }

.h2 {
  font-family: var(--display);
  font-weight: 400;
  font-variation-settings: "SOFT" 28, "opsz" 100;
  font-size: clamp(1.95rem, 4.2vw, 3.1rem);
  line-height: 1.05;
  letter-spacing: -.024em;
  color: var(--ink);
  text-wrap: balance;
}
.h2 em {
  font-style: italic;
  font-variation-settings: "SOFT" 62, "opsz" 100;
  color: var(--euc);
}
.h2-light { color: var(--on-deep); }
.h2-light em { color: var(--euc-light); }
.h2-sm { font-size: clamp(1.65rem, 3.2vw, 2.35rem); }

.eyebrow {
  display: inline-flex;
  /* flex-start, not center: on narrow screens the eyebrow wraps to two lines
     and a centred pip drifts down beside the gap between them. */
  align-items: flex-start;
  gap: 9px;
  font-size: 11.5px; font-weight: 600;
  letter-spacing: .15em; text-transform: uppercase;
  color: var(--euc);
  margin-bottom: 18px;
}
.eyebrow-light { color: var(--euc-light); }

.pip {
  width: 6px; height: 6px; border-radius: 50%;
  flex: none; margin-top: .42em;
  background: var(--gold);
  box-shadow: 0 0 0 0 rgba(184,134,43,.6);
  animation: pip 2.6s var(--ease) infinite;
}
@keyframes pip {
  0%   { box-shadow: 0 0 0 0 rgba(184,134,43,.55) }
  70%  { box-shadow: 0 0 0 9px rgba(184,134,43,0) }
  100% { box-shadow: 0 0 0 0 rgba(184,134,43,0) }
}

.lede {
  font-size: clamp(1rem, 1.4vw, 1.12rem);
  line-height: 1.62;
  color: var(--ink-2);
  max-width: 46ch;
  margin-top: 20px;
}

.prose { font-size: 1.02rem; line-height: 1.72; color: var(--ink-2); max-width: 62ch; }
.prose + .prose { margin-top: 1.1em; }
.prose-dim { color: var(--ink-3); font-size: .95rem; }

.sec-head { max-width: 760px; margin-bottom: clamp(40px, 5.5vw, 68px); }

/* #evidence inverts the usual order — its heading sits AFTER the stat cards
   it summarises (see the note on the evidence block). .sec-head only carries
   margin-bottom, which is right when a header leads a section and leaves this
   one butted straight up against the cards above it. Scoped to the adjacent
   sibling so every other section header is untouched. */
.stats + .sec-head { margin-top: clamp(46px, 5.5vw, 76px); }
.sec-lede { font-size: 1.04rem; line-height: 1.68; color: var(--ink-2); margin-top: 20px; max-width: 58ch; }
.sec-lede-light { color: var(--on-deep-2); }

/* ===========================================================================
   5 · BUTTONS
   =========================================================================== */
.btn {
  display: inline-flex; align-items: center; justify-content: center;
  gap: 9px;
  padding: 14px 24px;
  border-radius: var(--r-pill);
  font-size: 15px; font-weight: 600;
  letter-spacing: -.005em; white-space: nowrap;
  transition: transform .34s var(--ease-out), box-shadow .34s var(--ease-out),
              background-color .28s var(--ease), border-color .28s var(--ease),
              color .28s var(--ease);
}
.btn svg { flex: none; transition: transform .34s var(--ease-out); }
.btn:hover { transform: translateY(-2px); }
.btn:hover svg { transform: translateX(3px); }
.btn:active { transform: translateY(0); }

.btn-primary {
  background: var(--euc); color: #fff;
  box-shadow: 0 1px 0 rgba(255,255,255,.2) inset, 0 10px 24px -10px rgba(33,122,106,.6);
}
.btn-primary:hover {
  background: var(--euc-deep);
  box-shadow: 0 1px 0 rgba(255,255,255,.2) inset, 0 18px 34px -12px rgba(33,122,106,.55);
}

.btn-outline {
  color: var(--euc-deep);
  background: rgba(255,255,255,.72);
  border: 1px solid var(--line-2);
  backdrop-filter: blur(8px);
  -webkit-backdrop-filter: blur(8px);
}
.btn-outline:hover { background: #fff; border-color: var(--euc); }

.btn-onDeep {
  color: var(--on-deep);
  border: 1px solid rgba(237,244,241,.26);
}
.btn-onDeep:hover { border-color: rgba(237,244,241,.55); background: rgba(237,244,241,.06); }

.btn-lg { padding: 17px 32px; font-size: 16px; }

.cta-row { display: flex; flex-wrap: wrap; gap: 12px; margin-top: 26px; }

/* ===========================================================================
   6 · NAV
   =========================================================================== */
.nav {
  position: fixed; top: 0; left: 0; right: 0; z-index: 100;
  border-bottom: 1px solid transparent;
  transition: background-color .4s var(--ease), border-color .4s var(--ease);
}
.nav.is-stuck {
  background: rgba(251,249,245,.82);
  backdrop-filter: blur(18px) saturate(1.4);
  -webkit-backdrop-filter: blur(18px) saturate(1.4);
  border-bottom-color: var(--line);
}
/* gap trimmed 24px to 12px 19 Aug 2026, alongside the .nav-menu gap and
   link padding below. See the "breakpoint raised" comment further down
   this section for the full measurements: the three trims together
   bring the nav row's required width from 1335px (nine links, the new
   Training link, CTA, at the old spacing) down to 1243px, and the
   breakpoint move does the rest. This one gap is brand-to-menu breathing
   room, not the space between links, so losing 12px of it reads as
   tidier, not cramped. */
/* max-width is 1320px, NOT --wrap (1180px), and that is deliberate.
   Measured on the live site: .nav-menu alone needs 1079px and never shrinks
   (flex-shrink:0 below), the brand needs 170px, plus the 12px gap = 1203px
   required. At --wrap the row had 1100px of content width, so it overflowed
   by ~103px at EVERY viewport above the 1350px hamburger breakpoint — not
   just narrow ones, because the cap binds before the viewport does.

   Chrome hid the fault: min-width:auto stopped .brand shrinking, so the
   excess spilled past the container and body's overflow-x:hidden clipped it.
   Firefox resolved the brand's automatic minimum smaller, shrank it, and the
   wordmark visibly collided with "How it works".

   The nav is chrome rather than article content, so it may be wider than the
   reading column. 1320px yields 1240px of content — 37px of headroom over the
   measured requirement, from the 1351px breakpoint upward. */
.nav-inner {
  max-width: 1320px; margin-inline: auto;
  padding: 16px var(--gut);
  display: flex; align-items: center; justify-content: space-between; gap: 12px;
}

/* flex:none is the other half of the fix: it makes an over-long nav fail as a
   visible overflow in every engine, rather than as a silent squash in one. */
.brand { display: inline-flex; align-items: center; gap: 10px; color: var(--ink); flex: none; }
.brand-name { white-space: nowrap; }
.brand-glyph { color: var(--euc); flex: none; transition: transform .5s var(--ease-out); }
.brand:hover .brand-glyph { transform: rotate(-14deg); }
.brand-name {
  font-family: var(--display); font-weight: 500;
  font-variation-settings: "SOFT" 20, "opsz" 40;
  font-size: 19.5px; letter-spacing: -.018em;
}

/* flex-shrink:0 + nowrap are load-bearing, not belt-and-braces. .nav-menu is a
   flex item of .nav-inner, so it defaults to shrinking below its content width;
   the links then wrap mid-label ("How it / works") instead of overflowing, which
   is why this failed silently rather than showing a scrollbar. The pair of them
   means the row can only ever be one line — if labels are ever added back and no
   longer fit, it will visibly overflow instead of quietly wrapping, which is the
   failure we want because it gets noticed. */
/* gap 3px to 1px and the link padding below, 13px to 10px horizontal,
   trimmed 19 Aug 2026 to make room for the Training link. Font-size and
   the CTA's own padding are untouched: this is spacing only, chosen
   because it's the least visible lever (see the "breakpoint raised"
   comment lower in this section for the actual numbers). */
.nav-menu { display: flex; align-items: center; gap: 1px; flex-shrink: 0; }
.nav-menu > a {
  padding: 9px 10px; font-size: 14.5px; font-weight: 500;
  color: var(--ink-2); border-radius: var(--r-pill);
  white-space: nowrap;
  transition: color .25s var(--ease), background-color .25s var(--ease);
}
.nav-menu > a:hover { color: var(--ink); background: rgba(22,36,31,.05); }

/* Selector carries its own specificity instead of leaning on !important.
   `.nav-menu > a` (0,1,1) out-specifies a bare `.nav-cta` (0,1,0), so these
   declarations only ever won by force — and any property added here later
   without !important would have silently lost. Matching the descendant shape
   makes it win on merit. The mobile override below must keep the same shape. */
.nav-menu > a.nav-cta {
  margin-left: 8px;
  background: var(--euc); color: #fff;
  padding: 10px 20px;
  box-shadow: 0 8px 20px -10px rgba(33,122,106,.75);
}
.nav-menu > a.nav-cta:hover { background: var(--euc-deep); color: #fff; }

.nav-toggle {
  display: none; width: 42px; height: 42px;
  border-radius: var(--r-pill); border: 1px solid var(--line-2);
  background: rgba(255,255,255,.7);
  flex-direction: column; align-items: center; justify-content: center; gap: 5px;
}
.nav-toggle span {
  display: block; width: 17px; height: 1.6px;
  background: var(--ink); border-radius: 2px;
  transition: transform .35s var(--ease-out);
}
.nav-toggle[aria-expanded="true"] span:first-child { transform: translateY(3.3px) rotate(45deg); }
.nav-toggle[aria-expanded="true"] span:last-child  { transform: translateY(-3.3px) rotate(-45deg); }

/* 1100, not 960, historically. The desktop menu needs ~900px of intrinsic
   width beside the brand; at 960px it had ~880px and the links wrapped
   mid-label rather than overflowing. This also closed the band where every
   card grid below the fold had already gone single-column (880-900px)
   while the nav was still trying to render a full desktop menu.

   Breakpoint raised 1100 to 1350, 19 Aug 2026, adding the Training link.
   Measured live in a real browser, not estimated: .nav-menu's children
   never shrink (flex-shrink:0, see the comment above .nav-menu), so the
   row's required width is fixed regardless of viewport, and body's
   overflow-x:hidden clips whatever doesn't fit at the viewport edge. With
   the original nine links that required width (.nav-inner scrollWidth)
   was 1253px, which silently clipped "Request early access" at every
   viewport from the 1100px breakpoint up to ~1325px, confirmed by
   screenshot at 1280px, not merely computed. Adding a tenth link at the
   same spacing raised that to 1335px and pushed the clip zone out past
   1490px, catching common 1440px laptop widths too.
   Fix has two parts, both required:
     1. the gap/padding trims above (.nav-inner gap, .nav-menu gap, link
        padding) bring that required width back down to 1243px;
     2. this breakpoint moves from 1100 to 1350, so the entire range that
        used to render a silently-clipped desktop row now renders the
        mobile panel instead, which stacks vertically and can't overflow
        sideways. At the new smallest desktop width, 1351px, the row needs
        1328.5px, a 22.5px margin; at 1440px the margin is 67px.
   A pure breakpoint-only fix (no trim) would have needed roughly 1490px,
   pushing common 1440px-wide laptops into the hamburger menu for no
   reason. A pure spacing-only fix (no breakpoint move) would have needed
   cutting well over 200px from a 1335px row, which risks the mid-label
   wrap this rule already guards against. Splitting the fix across both
   keeps each change small.
   script.js's `matchMedia('(min-width: 1101px)')` moved to 1351px in
   lockstep, see its own comment; so did the hero's copy of this same
   media query further down this file (search "narrow: the whole stage
   stacks"), kept aligned on purpose so the top of the page changes shape
   once, not in two steps. */
@media (max-width: 1350px) {
  .nav-toggle { display: flex; }
  .nav-menu {
    position: fixed; inset: 72px 12px auto 12px;
    flex-direction: column; align-items: stretch; gap: 2px;
    padding: 14px; border-radius: var(--r-lg);
    background: rgba(251,249,245,.97);
    backdrop-filter: blur(22px); -webkit-backdrop-filter: blur(22px);
    border: 1px solid var(--line);
    box-shadow: var(--lift-3);
    opacity: 0; transform: translateY(-10px) scale(.985);
    pointer-events: none;
    transition: opacity .3s var(--ease), transform .35s var(--ease-out);
  }
  .nav-menu.is-open { opacity: 1; transform: none; pointer-events: auto; }
  .nav-menu > a { padding: 13px 16px; font-size: 16px; }
  /* Same selector shape as the base rule above, or this loses to it. */
  .nav-menu > a.nav-cta { margin: 6px 0 0; text-align: center; }

  /* The open dropdown is a LIGHT panel, so the over-the-hero inversion must
     not follow the links into it — see body.deep-hero .nav:not(.is-stuck)
     further down. Measured on the live site at scrollY 0: link colour
     rgb(237,244,241) on a panel of rgba(251,249,245,.97). Near-white on
     near-white — the menu was unreadable exactly where it is almost always
     opened, and only became legible after scrolling far enough to set
     .is-stuck.

     Scoped to .is-open so it applies only once the panel exists.
     Specificity (0,5,2) beats the inverting rule's (0,4,2), so no
     !important is needed. */
  body.deep-hero .nav:not(.is-stuck) .nav-menu.is-open > a { color: var(--ink-2); }
  body.deep-hero .nav:not(.is-stuck) .nav-menu.is-open > a:hover {
    color: var(--ink); background: rgba(22,36,31,.05);
  }
  body.deep-hero .nav:not(.is-stuck) .nav-menu.is-open > a.nav-cta {
    background: var(--euc); color: #fff;
  }
}

/* ===========================================================================
   7 · HERO — the five-beat story, rebuilt 17 Aug 2026
   ---------------------------------------------------------------------------
   REPLACES the park-bench photo/video hero (moved to a .glimpse section
   further down the page 17 Aug 2026, then cut outright 18 Aug 2026,
   reviewer direction; see the removal note near "16 · GLIMPSE" below).
   Two columns at rest: copy on the left, a narrated five-beat
   story on the right — a visit chosen, said out loud, shaped into a note,
   the safety question answered, and the note arriving on the desk. All CSS
   and inline SVG: no image or font download added, so the previous hero's
   716 ms Slow-4G paint should not regress.

   Ground: a warm eucalyptus wash (lightened --euc-wash, layered radial
   gradients for a soft mesh) rather than the flat --paper the rest of the
   page uses, and rather than a photo — this section is now a deliberately
   different, calmer register from the film that used to fill it.
   =========================================================================== */
.hero {
  position: relative;
  min-height: 100svh;
  display: flex;
  align-items: center;
  padding: clamp(100px, 15vh, 156px) 0 clamp(52px, 7vh, 84px);
  overflow: hidden;
  background:
    radial-gradient(56% 48% at 90% 4%, rgba(184,134,43,.10) 0%, transparent 62%),
    radial-gradient(66% 58% at 6% 100%, rgba(79,192,166,.30) 0%, transparent 60%),
    radial-gradient(100% 80% at 50% -12%, var(--euc-wash) 0%, transparent 68%),
    linear-gradient(178deg, #F4FAF7 0%, var(--euc-wash) 48%, #DCEEE6 100%);
}

/* ===========================================================================
   7a · THEN / NOW — the before-and-after practice pair (#time)
   ---------------------------------------------------------------------------
   Added 20 Aug 2026. Two photographs of the same worker, same child, same
   room, same visit — the only variable is where her attention is.

   IDENTICAL TREATMENT IS LOAD-BEARING, not tidiness. Both figures share one
   grid track, one aspect-ratio box, one radius and one filter (none). If a
   future change crops, tints or resizes one side, the comparison stops being
   like-for-like and the pair becomes an illustration of a claim rather than
   evidence for it. Style them together or not at all.

   aspect-ratio (not a fixed height) with object-fit: cover means the two
   images stay exactly the same shape at every width even though the sources
   differ by one pixel of height (786 vs 787) — and it reserves the box
   before the lazy image arrives, so this section contributes no CLS.
   =========================================================================== */
.then-now {
  display: grid;
  grid-template-columns: repeat(2, minmax(0, 1fr));
  gap: clamp(16px, 2.4vw, 30px);
  margin-top: clamp(34px, 4.6vw, 54px);
}

.tn-shot { margin: 0; }

.tn-shot img {
  display: block;
  width: 100%;
  /* height:auto is load-bearing, not boilerplate. The <img> carries
     width="1400" height="787" so the browser can reserve the box before the
     lazy image arrives (no CLS) — but those attributes map into the UA
     stylesheet as a used height, and an explicit height BEATS aspect-ratio.
     Without this line the box rendered 787px tall and object-fit:cover
     cropped the landscape photograph into a portrait slot, which threw away
     most of the room and half the reason the pair works. Measured, not
     theorised: 535x785 before, 535x301 after. */
  height: auto;
  aspect-ratio: 16 / 9;
  object-fit: cover;
  border-radius: var(--r-lg, 18px);
  background: var(--paper-3);
  box-shadow: 0 18px 44px -26px rgba(16,49,41,.42), 0 4px 12px -8px rgba(16,49,41,.18);
}

.tn-shot figcaption {
  display: flex;
  flex-direction: column;
  gap: 7px;
  margin-top: 15px;
}

/* The tag names the condition; the line says what it costs or buys. Kept as
   two elements rather than one sentence so the eye can compare the two tags
   first and then the two lines, which is the order the pair is read in. */
.tn-tag {
  font-size: 11.5px; font-weight: 600;
  letter-spacing: .14em; text-transform: uppercase;
}
.tn-tag-before { color: var(--ink-3); }
.tn-tag-after  { color: var(--euc); }

.tn-line {
  font-family: var(--display);
  font-style: italic; font-weight: 400;
  font-variation-settings: "SOFT" 40, "opsz" 40;
  font-size: clamp(1.02rem, 1.5vw, 1.22rem);
  line-height: 1.4;
  color: var(--ink);
  text-wrap: balance;
}

/* Stacking below 720px keeps each photo big enough to read a face in. Side
   by side on a phone would make both too small to carry the one detail the
   whole pair turns on — whether she is looking at the child or the page. */
@media (max-width: 720px) {
  .then-now { grid-template-columns: 1fr; gap: 26px; }
}

/* ===========================================================================
   7b · HERO WATER SURFACE — added 19 Aug 2026
   ---------------------------------------------------------------------------
   The ripple canvas from flowoutcomes.au, re-keyed light. See the long note
   on the <canvas> in index.html for why it rebuilds the gradient above in
   GLSL rather than refracting a photograph.

   LAYERING inside .hero, bottom to top:
     -   .hero's own background gradient (untouched — it is the fallback)
     0   .hero-water    the simulated surface
     1   .hero::after   the readability veil
     2   .hero-wrap     copy + stage

   .hero-wrap moved from z-index 1 to 2 to make room for the veil. That is the
   ONLY line of pre-existing CSS this feature changes.

   ROLLBACK: delete this section, revert .hero-wrap's z-index to 1, drop
   .hero-water from the print and reduced-motion lists below, and remove the
   <canvas> from index.html. Nothing in §7 was edited, so the hero returns to
   exactly what it was. `git checkout -- styles.css index.html script.js` does
   the same in one command — www-v2/ is a clean git repo, so that IS the
   rollback; ../rollback-hero-water-2026-08-19/ holds file copies as well.
   That directory lives OUTSIDE www-v2 on purpose: build-www.sh copies the
   whole source tree into the dist, so anything left in here ships.
   =========================================================================== */
.hero-water {
  position: absolute; inset: 0; z-index: 0;
  display: block; width: 100%; height: 100%;
  /* Events belong to .hero, which is what the module listens on — the canvas
     must not swallow them or the CTAs beneath the pointer stop hovering. */
  pointer-events: none;
  /* Held back until a frame has actually been drawn, so a device that fails
     halfway through WebGL setup never flashes a bare canvas over the wash. */
  opacity: 0;
  transition: opacity 1100ms ease;
  /* "Top background": the surface is strongest at the masthead and dissolves
     into the existing wash well before the fold, so the bottom edge of the
     hero — where it meets the page's paper — is unchanged. */
  -webkit-mask-image: linear-gradient(180deg, #000 0%, #000 44%, rgba(0,0,0,.52) 74%, transparent 100%);
          mask-image: linear-gradient(180deg, #000 0%, #000 44%, rgba(0,0,0,.52) 74%, transparent 100%);
}
.hero-water.is-live { opacity: 1; }

/* The readability veil. flowoutcomes.au darkens toward its headline; this page
   does the opposite and LIGHTENS, because the contrast at risk here is dark
   ink on a moving pale ground, plus the 1px strokes of the five-beat stage.
   Three layers: calm the copy column, settle the ground under the stage, and
   return to the wash at the fold.

   Gated on .has-water, which the module adds to .hero only once a frame has
   been drawn. The veil exists solely to protect contrast FROM the water, so on
   any device that never gets the water — no WebGL, shader failure,
   prefers-reduced-motion — it must not paint either, or the fallback would be
   a quietly lightened version of the hero rather than the hero. With this
   gate the fallback is the current design pixel for pixel. */
/* 2026-08-21: was `.hero.has-water::after`. The hero no longer runs the water,
   so that class is never added and the veil would have vanished with it. Scoped
   to the two heroes that actually want one rather than to bare `.hero` —
   training.html also has a .hero, and has never had a veil over it. */
.hero.is-deep::after,
.hero.has-water::after {
  content: ""; position: absolute; inset: 0; z-index: 1; pointer-events: none;
  background:
    linear-gradient(100deg, rgba(244,250,247,.80) 0%, rgba(244,250,247,.52) 36%, rgba(244,250,247,0) 64%),
    radial-gradient(56% 60% at 74% 50%, rgba(251,249,245,.40) 0%, rgba(251,249,245,0) 72%),
    linear-gradient(0deg, rgba(230,241,237,.82) 0%, rgba(230,241,237,0) 24%);
}

/* ===========================================================================
   7c · DEEP HERO — the app's own register, added 20 Aug 2026
   ---------------------------------------------------------------------------
   `class="hero is-deep"` swaps the pale eucalyptus wash for the ground the
   product's own home screen uses: brand.earth into brand.barkDark
   (utils/theme.ts:62-63), water on top, the BrandGlyph rings ghosted
   underneath, cream serif over it.

   WHY. The pale full-bleed version read as a stock WebGL hero — the effect was
   a BACKGROUND, and a background is where every site puts water. On the app's
   home card the water is an OBJECT: dark, bounded, carrying the primary action
   in cream Fraunces, with the mark submerged in it. Dark, contained and marked
   is what makes it ours; pale and ambient is what made it anyone's.

   NOT A NEW REGISTER. --on-deep / --on-deep-2 / --on-deep-3, .band-deep,
   .h2-light, .sec-lede-light and .eyebrow-light already exist in this
   stylesheet — dark grounds were designed for here from the start. This
   section reuses those tokens rather than inventing hero-only colours.

   THE FALLBACK IS DIFFERENT HERE, deliberately. In section 7b the no-WebGL
   fallback is .hero's own gradient. It cannot be that here: the copy is cream,
   so the ground must be dark whether or not the water ever runs. The static
   deep gradient below therefore applies unconditionally, and only the moving
   surface is conditional.

   ROLLBACK: remove `is-deep` from the <section class="hero"> in index.html and
   `deep-hero` from <body>. Everything in this section is scoped under one of
   those two classes, so the pale hero returns untouched.
   =========================================================================== */
/* 2026-08-21 — THE PLATE, AND NO WATER OVER IT.
   The ripple was tried behind this first and taken off: refraction over a
   treeline tears it into what read as scratches, and refraction over a face
   reads as a defect rather than a surface, because people are exquisitely
   tuned to facial geometry. The water still owns every other deep surface in
   the product; it just does not belong on top of a photograph of a person.

   `left center` is deliberate and counter-intuitive. The plate is wider than
   the hero, so cover crops the RIGHT — which pushes her toward the right edge,
   clear of the stage cards, and leaves the quiet dark bush under the copy.
   Centring her puts her face directly behind the case-history card.

   The grade is baked into the asset rather than done with a CSS filter: no
   filter cost, no repaint on scroll, and what ships is exactly what was
   measured. 28 KB at 1280, 39 KB at 1600. */
.hero.is-deep {
  background:
    radial-gradient(58% 50% at 88% 8%, rgba(79,192,166,.14) 0%, transparent 62%),
    radial-gradient(70% 62% at 12% 96%, rgba(5,38,32,.55) 0%, transparent 64%),
    image-set(url("assets/hero-bush-1600.webp") 1x) left center / cover no-repeat,
    linear-gradient(160deg, #1A6658 0%, #1E7466 52%, #17594D 100%);
}
@media (max-width: 900px) {
  /* Narrower viewport crops harder, so take the smaller plate and pull the
     framing back toward her — at phone widths the bush is mostly gone and she
     is the only thing left worth keeping. */
  .hero.is-deep {
    background:
      radial-gradient(70% 62% at 12% 96%, rgba(5,38,32,.55) 0%, transparent 64%),
      image-set(url("assets/hero-bush-1280.webp") 1x) 34% center / cover no-repeat,
      linear-gradient(160deg, #1A6658 0%, #1E7466 52%, #17594D 100%);
  }
}
/* Full bleed: no bottom fade. The pale version dissolved into the page because
   it was a wash; this one is an object, and an object wants an edge. */
.hero.is-deep .hero-water {
  -webkit-mask-image: none;
          mask-image: none;
}
/* Left-weighted depth so the cream type has something solid under it — the
   same move the app card makes, and flowoutcomes.au's scrim, inverted back to
   dark now that the ground is dark again. */
.hero.is-deep::after {
  background:
    /* 2026-08-21: was .80/.52/.08 to 66%. Strengthened when the hero plate went
       in behind the water. The plate is a graded photograph rather than a
       duotone, so it is lighter than the procedural ground it replaced and the
       copy column lost contrast: measured 2.94:1 against white, an AA fail.
       This lands it at 4.94:1, and it is the right lever because it is local to
       the type — the right-hand side, where she is, is untouched. */
    linear-gradient(102deg, rgba(6,40,34,.94) 0%, rgba(6,40,34,.70) 42%, rgba(6,40,34,.12) 72%),
    linear-gradient(0deg, rgba(6,40,34,.34) 0%, rgba(6,40,34,0) 24%),
    /* fixed dark pool under the stage caption — see .hv-caption's note */
    radial-gradient(32% 13% at 69% 79%, rgba(6,40,34,.45) 0%, rgba(6,40,34,0) 72%),
    /* the nav sits directly on this — hold the top band down so the links keep
       their contrast before .is-stuck paints its own backdrop */
    linear-gradient(180deg, rgba(6,40,34,.52) 0%, rgba(6,40,34,0) 14%);
}

/* — copy, inverted — */
/* NOT --euc-light: #4FC0A6 measures 3.70:1 against the brightest water
   this zone reaches — an AA fail that predates the deep hero's tuning
   pass (the original probe measured this zone with cream by mistake).
   #7FD3BD is the app's own dark-palette `earth` accent (utils/theme.ts
   line 22) — the token the product already uses for exactly this job,
   accent-on-deep-teal — and measures 5.19:1 on the same worst case. */
.hero.is-deep .eyebrow { color: #7FD3BD; }
.hero.is-deep .display { color: var(--on-deep); }
/* Cream for the emphasis too, not the accent teal: the app card sets
   <Em color={brand.cream}> and lets the italic carry the distinction. On this
   ground the accent would also be the weakest contrast on the page. */
.hero.is-deep .display em { color: var(--on-deep); }
/* Lifted above --on-deep-2's 76% — that left only 0.08 of AA headroom on
   the lede, which pinned the scrim at black-hole strength. The brighter
   text pays for the gentler scrim; both were remeasured together. */
.hero.is-deep .hero-lede { color: rgba(237,244,241,.88); }
.hero.is-deep .hero-facts { border-top-color: rgba(237,244,241,.20); }
.hero.is-deep .hero-facts li { color: rgba(237,244,241,.86); }
.hero.is-deep .hero-facts b { color: var(--on-deep); }
.hero.is-deep .hero-facts svg { color: var(--euc-light); }
/* The caption sits on OPEN water — bright caustic veins pass directly
   behind it, and the veil doesn't reach this far right. Full cream plus
   a soft dark halo; the veil below adds a fixed dark pool under the
   whole caption zone, which is what actually carries the AA number
   (cream alone measured 4.14:1 against the brightest caustic phase). */
.hero.is-deep .hv-caption {
  color: var(--on-deep);
  text-shadow: 0 1px 16px rgba(6,40,34,.85), 0 0 4px rgba(6,40,34,.6);
}
/* The stage surfaces carry shadows tuned for cream paper
   (rgba(16,49,41,.28-.36)) which vanish on teal — the white card read as
   pasted on rather than floating. On a mid-toned ground a darker,
   tighter shadow reads as depth again. */
.hero.is-deep .hv-frame {
  box-shadow:
    0 1px 0 rgba(255,255,255,.9) inset,
    0 34px 70px -22px rgba(3,26,21,.62),
    0 12px 28px -12px rgba(3,26,21,.42);
}
.hero.is-deep .hv-note { box-shadow: 0 14px 32px -14px rgba(3,26,21,.6); }
.hero.is-deep .hv-fold { box-shadow: 0 18px 36px -16px rgba(3,26,21,.62); }

/* — buttons on the deep ground —
   The filled button inverts to cream-on-teal, which is what the app does with
   its own primary surfaces; --euc-deep on --on-deep clears AA comfortably. */
.hero.is-deep .btn-primary {
  background: var(--on-deep); color: var(--euc-deep);
  box-shadow: 0 1px 0 rgba(255,255,255,.5) inset, 0 12px 32px rgba(26,102,88,.28);
}
.hero.is-deep .btn-primary:hover {
  background: #fff; color: var(--euc-deep);
  box-shadow: 0 1px 0 rgba(255,255,255,.6) inset, 0 18px 38px rgba(26,102,88,.34);
}
.hero.is-deep .btn-outline {
  color: var(--on-deep);
  background: rgba(237,244,241,.15);
  border-color: rgba(237,244,241,.46);
}
.hero.is-deep .btn-outline:hover {
  background: rgba(237,244,241,.24);
  border-color: rgba(237,244,241,.7);
}

/* — nav over the deep hero —
   .nav is fixed and transparent until it sticks, so at the top of this page it
   sits directly on the water with --ink links. Inverted only while unstuck;
   the moment .is-stuck paints its light backdrop the normal colours are right
   again. Scoped to body.deep-hero so no other page is touched. */
body.deep-hero .nav:not(.is-stuck) .brand,
body.deep-hero .nav:not(.is-stuck) .nav-menu > a { color: var(--on-deep); }
body.deep-hero .nav:not(.is-stuck) .nav-menu > a:hover {
  color: #fff; background: rgba(237,244,241,.12);
}
body.deep-hero .nav:not(.is-stuck) .nav-menu > a.nav-cta {
  background: var(--on-deep); color: var(--euc-deep);
}
body.deep-hero .nav:not(.is-stuck) .nav-menu > a.nav-cta:hover { background: #fff; }

.hero-wrap {
  position: relative; z-index: 2;
  display: grid;
  /* Not 1fr 1fr. At beat 5 the visual side has to hold a note card, a flight
     track and a desktop window side by side — 488px of hard minimum — and an
     even split starved the flight to ~20px, which is the one thing in this
     hero the founder asked to feel special ("like it jumps over"). The copy
     side loses ~40px it had spare. */
  grid-template-columns: minmax(0, .92fr) minmax(0, 1.08fr);
  align-items: center;
  gap: clamp(30px, 4.4vw, 60px);
}

.hero-copy { max-width: 560px; }

.hero-lede {
  margin-top: 18px;
  font-size: clamp(1.02rem, 1.3vw, 1.14rem);
  line-height: 1.6;
  color: var(--ink-2);
  max-width: 46ch;
}

.hero-facts {
  display: grid; gap: 10px;
  margin-top: 30px;
  padding-top: 20px;
  border-top: 1px solid rgba(16,49,41,.14);
}
.hero-facts li { display: flex; align-items: flex-start; gap: 9px; font-size: 13px; line-height: 1.45; color: var(--ink-2); }
.hero-facts svg { color: var(--euc); flex: none; margin-top: 2px; }
.hero-facts b { font-weight: 600; color: var(--ink); }

/* ── THE STAGE — five illustrative beats ──────────────────────────────
   REBUILT 17 Aug 2026 (second pass). What was here before: six faithful
   recreations of real app screens inside a 268px-wide phone. Accurate to
   the pixel and unreadable — 7.6px metadata, 8px tile sublabels, 9.2px
   options. A visitor spent fifteen seconds squinting at a device instead
   of one second understanding a product, which is fidelity to the
   screenshots winning over fidelity to the IDEA.

   Now: five beats, one oversized readable idea each, on one large surface,
   with a narrator line that changes underneath. The SMALLEST type inside
   the board is 12px at desktop. That constraint is the whole rebuild.

   Two structural rules make the rest safe:

   1. .hv-board has a hard px height per breakpoint and every beat is
      position:absolute inside it. Nothing a beat does — a note card
      growing, a case-history row expanding, a plane taking off — can
      change the page's layout. The ~16s sequence costs zero CLS by
      construction, not by luck.
   2. Every staggered entrance uses `backwards` fill and NO forwards fill,
      so its resting state is the element's own base style. That is what
      lets the reduced-motion block simply say `animation: none` without
      stranding anything at opacity 0 — the trap that bit this file twice.
   ──────────────────────────────────────────────────────────────────── */
.hv-stage { display: grid; }

.hv-board {
  position: relative;
  width: 100%;
  /* 424px, and every beat is measured against it below. See the size
     budget in each beat's own comment. */
  height: 424px;
  transition: opacity .7s var(--ease-out), transform .7s var(--ease-out);
}
/* Scene 0 is the ONLY state that hides the board, and it exists solely for
   the moment between script start and sequence start. The visible state is
   the BASE state, so a page whose JS never runs still renders the finished
   beat-5 tableau. Replay goes 5 → 1 and never passes 0, so this transition
   does not re-fire and the board never flashes out mid-replay. */
.hv-stage[data-scene="0"] .hv-board { opacity: 0; transform: translateY(18px) scale(.975); }

/* Beats 1–4 share ONE surface — a single element wrapping four beats rather
   than four separately-framed cards, because the surface itself is what
   recedes at beat 5. overflow:hidden is load-bearing: the beats push in and
   out horizontally and must be clipped by the device edge. */
.hv-frame {
  position: absolute; inset: 0;
  border-radius: 24px; overflow: hidden;
  background: var(--surface);
  border: 1px solid var(--line);
  box-shadow:
    0 1px 0 rgba(255,255,255,.9) inset,
    0 30px 60px -26px rgba(16,49,41,.28),
    0 10px 24px -14px rgba(16,49,41,.14);
  transition: opacity .42s var(--ease), transform .55s var(--ease-out);
}
.hv-stage[data-scene="5"] .hv-frame { opacity: 0; transform: translateX(-7%) scale(.86); }

/* Beats push rather than cross-fade — the way the app itself navigates.
   Default is "waiting off to the right"; `.is-past` (set by script.js) is
   "already been, gone left". CSS can express "is the active beat" but not
   "is behind us" without enumerating every scene/beat pair, so direction is
   carried by one class. */
.hv-beat {
  position: absolute; inset: 0;
  display: flex; flex-direction: column;
  padding: 16px 20px 20px;
  opacity: 0; transform: translateX(15%);
  /* visibility rides along, delayed past the exit so the outgoing beat stays
     paintable while it slides — then goes genuinely invisible. Opacity alone
     was not enough: axe computes contrast on opacity-0 text mid-fade, and
     Lighthouse caught beat 1's "Back" label blended to near-white against the
     panel (1.02:1) while the beat was hidden. visibility:hidden removes the
     text from that computation entirely. */
  visibility: hidden;
  transition: opacity .34s var(--ease), transform .52s var(--ease-out),
              visibility 0s .52s;
}
.hv-beat.is-past { transform: translateX(-13%); }
.hv-stage[data-scene="1"] .hv-beat[data-b="1"],
.hv-stage[data-scene="2"] .hv-beat[data-b="2"],
.hv-stage[data-scene="3"] .hv-beat[data-b="3"],
.hv-stage[data-scene="4"] .hv-beat[data-b="4"] {
  opacity: 1; transform: none;
  visibility: visible; transition-delay: 0s;
}

/* — the app's own chrome, per beat —
   space-between, and deliberately no auto margins: an auto margin consumes
   free space BEFORE justify-content does, so `margin-left:auto` on the
   third child would pack the first two together and un-centre the step
   counter. */
.hv-bar { flex: none; display: flex; align-items: center; justify-content: space-between; gap: 10px; min-height: 26px; }
.hv-i { width: 18px; height: 18px; flex: none; fill: none; stroke: currentColor; stroke-width: 1.7; stroke-linecap: round; stroke-linejoin: round; }
.hv-back { display: inline-flex; align-items: center; gap: 4px; font-size: 13px; font-weight: 600; color: var(--euc); }
.hv-back .hv-i { width: 16px; height: 16px; stroke-width: 2.2; }
.hv-spk { color: var(--ink-3); }
.hv-pill { width: 26px; height: 26px; border-radius: 50%; flex: none; display: grid; place-items: center; background: var(--paper-2); color: var(--ink-2); }
.hv-pill .hv-i { width: 14px; height: 14px; stroke-width: 2.2; }
.hv-step { display: inline-flex; align-items: center; gap: 6px; font-size: 12.5px; font-weight: 500; color: var(--ink-2); }
.hv-stepdot { width: 6px; height: 6px; border-radius: 50%; background: var(--euc-mid); flex: none; }
/* The review screen's real segmented control. <b> is the selected half and
   <i> the unselected one — element choice carries the state, so there is no
   third class to keep in sync with the markup. */
.hv-seg {
  display: inline-flex; align-items: center; gap: 3px; flex: none;
  padding: 3px; border-radius: 999px; background: var(--paper-2);
  font-size: 12px; font-weight: 600;
}
.hv-seg b, .hv-seg i { display: inline-flex; padding: 5px 11px; border-radius: 999px; font-style: normal; }
.hv-seg b { background: var(--surface); color: var(--euc); box-shadow: 0 1px 3px -1px rgba(16,49,41,.22); }
.hv-seg i { color: var(--ink-3); }
.hv-prog { flex: none; height: 3px; border-radius: 3px; background: var(--line); margin-top: 12px; overflow: hidden; }
.hv-prog span { display: block; height: 100%; border-radius: 3px; background: var(--euc); }

.hv-q {
  flex: none; margin-top: 14px;
  font-family: var(--display); font-weight: 600;
  font-variation-settings: "SOFT" 18, "opsz" 30;
  font-size: 17px; line-height: 1.3; letter-spacing: -.012em;
  color: var(--ink);
}

/* ── beat 1 · the visit ──────────────────────────────────────────────
   Size budget, desktop (board 424 − 2 border − 36 padding = 386 of beat
   height): bar 26 + question 22 + 14 margin leaves 324 for the tiles;
   three columns, two rows, 12px gap = 156px rows against ~106px of tile
   content, so the tiles are generously airy rather than cramped.
   ──────────────────────────────────────────────────────────────────── */
.hv-tiles {
  flex: 1 1 auto; min-height: 0; margin-top: 14px;
  display: grid; grid-template-columns: repeat(3, minmax(0, 1fr));
  grid-auto-rows: 1fr; gap: 12px;
}
.hv-tile {
  display: flex; flex-direction: column; align-items: center; justify-content: center;
  gap: 3px; padding: 12px 8px; text-align: center;
  border-radius: 14px;
  background-color: var(--euc-wash);
  box-shadow: inset 0 0 0 1px rgba(33,122,106,.10);
}
.hv-ico {
  width: 44px; height: 44px; border-radius: 50%; flex: none;
  display: grid; place-items: center; margin-bottom: 4px;
  background: var(--surface); color: #38473F;
  box-shadow: 0 4px 10px -4px rgba(16,49,41,.26);
}
.hv-ico .hv-i { width: 22px; height: 22px; stroke-width: 1.5; }
.hv-tile b {
  font-family: var(--display); font-weight: 600;
  font-variation-settings: "SOFT" 16, "opsz" 20;
  font-size: 13.5px; line-height: 1.18; color: var(--ink);
}
/* --ink-2, not --ink-3: the --ink-3 token is documented as ">=4.55:1 on
   every surface incl. the tinted washes" but was tuned against --clay-wash,
   and the eucalyptus washes are darker. Opt up rather than trust it. */
.hv-tile i { font-size: 12px; font-style: italic; line-height: 1.25; color: var(--ink-2); }

/* ── beat 2 · just talk ──────────────────────────────────────────────
   Size budget, desktop: bar 26 + prog 15 + fragments 167 + mic 94 +
   "Listening" 26 = 328 in 386, so space-between leaves ~14px between each
   pair — enough that the outermost ripple (1.75 × a 76px button, i.e.
   26px of reach) clears the last fragment instead of washing over it.
   ──────────────────────────────────────────────────────────────────── */
.hv-beat[data-b="2"] { justify-content: space-between; }
.hv-frags { flex: none; display: grid; justify-items: start; gap: 10px; width: 100%; margin-top: 16px; }
.hv-frag {
  max-width: 84%;
  padding: 11px 15px;
  border-radius: 16px 16px 16px 5px;
  background: var(--surface);
  border: 1px solid var(--line);
  box-shadow: 0 8px 18px -12px rgba(16,49,41,.28);
  font-size: 14px; font-style: italic; line-height: 1.4; color: var(--ink-2);
}
/* One fragment lands on the other side, tail flipped — three identically
   placed bubbles read as a list, alternating ones read as a conversation. */
.hv-frag-r { justify-self: end; border-radius: 16px 16px 5px 16px; }

.hv-mic-wrap { flex: none; position: relative; width: 76px; height: 76px; margin: 18px auto 0; }
.hv-mic {
  position: absolute; inset: 0; border-radius: 50%;
  display: grid; place-items: center;
  background: var(--euc); color: #fff;
  box-shadow: 0 12px 26px -12px rgba(33,122,106,.7);
}
.hv-mic .hv-i { width: 32px; height: 32px; stroke-width: 1.8; }
/* Rings, not an animated box-shadow spread: transform+opacity only, and
   three of them can be staggered off one keyframe. */
.hv-mic-ring { position: absolute; inset: 0; border-radius: 50%; border: 2px solid var(--euc-mid); opacity: 0; }
.hv-listen {
  flex: none; margin-top: 10px;
  display: flex; align-items: center; justify-content: center; gap: 7px;
  font-size: 12.5px; font-weight: 700; letter-spacing: .09em; text-transform: uppercase;
  color: var(--euc);
}
.hv-listen i { width: 7px; height: 7px; border-radius: 50%; background: var(--clay); flex: none; }

/* ── beat 3 · it becomes a note ──────────────────────────────────────
   `margin: auto 0` rather than justify-content:center on the beat, so the
   bar stays pinned at the top and only the card centres in what is left.
   ──────────────────────────────────────────────────────────────────── */
.hv-note {
  margin: auto 0;
  padding: 18px 18px 20px;
  border-radius: 16px;
  background: var(--paper);
  border: 1px solid var(--line);
  box-shadow: 0 12px 28px -18px rgba(16,49,41,.3);
}
.hv-note-h {
  display: flex; align-items: center; gap: 10px;
  font-family: var(--display); font-weight: 600;
  font-variation-settings: "SOFT" 16, "opsz" 24;
  font-size: 15.5px; color: var(--ink);
}
.hv-note-h .hv-chip { margin-left: auto; }
/* Gold use #2 of exactly two in this stage (the other is the landing
   burst) — and it is the app's real "Draft" chip, not decoration. */
.hv-chip {
  flex: none; padding: 2px 9px; border-radius: 999px;
  background: var(--gold-wash); color: var(--gold-ink);
  font-family: var(--body); font-size: 12px; font-weight: 700;
  letter-spacing: .05em; text-transform: uppercase;
}
.hv-note-m { margin-top: 4px; font-size: 12px; line-height: 1.5; color: var(--ink-3); }
.hv-note-rule { height: 1px; background: var(--line); margin: 12px 0; }
.hv-note-l { position: relative; padding-right: 22px; font-size: 14px; line-height: 1.6; color: var(--ink-2); }
.hv-note-l + .hv-note-l { margin-top: 10px; }
.hv-pen { position: absolute; right: 0; top: 5px; width: 14px; height: 14px; color: var(--ink-3); }
/* The provenance highlight is a background IMAGE, not a background colour,
   so the sweep can be animated as background-size (paint only, no layout)
   from 0 to full width. box-decoration-break:clone keeps the padding and
   radius on both halves when a mark wraps across a line. */
.hv-note-l mark,
.hv-fold-b mark {
  background-color: transparent;
  background-image: linear-gradient(var(--euc-wash), var(--euc-wash));
  background-repeat: no-repeat;
  background-size: 100% 100%;
  color: inherit; border-radius: 3px; padding: 0 2px;
  -webkit-box-decoration-break: clone; box-decoration-break: clone;
}

/* ── beat 4 · safety, every time ─────────────────────────────────────
   The base style of `.is-pick` is the ANSWERED state — tinted row, euc
   ring, tick drawn. The animation runs from the unanswered state via
   `backwards` fill, which means a no-JS or reduced-motion reader sees a
   finished answer rather than an empty radio, and nothing has to be
   un-stranded when the animation is switched off.
   ──────────────────────────────────────────────────────────────────── */
/* flex:1 + align-content:center rather than a margin: the three options centre
   in whatever height is left under a question that wraps to one, two or three
   lines depending on width, instead of leaving 114px of dead paper at 1440px
   and none at 320px. */
.hv-opts {
  flex: 1 1 auto; min-height: 0;
  display: grid; align-content: center; gap: 10px; margin-top: 16px;
}
.hv-opt {
  display: flex; align-items: center; gap: 12px;
  padding: 13px 14px; border-radius: 12px;
  background-color: var(--paper-2);
  box-shadow: inset 0 0 0 1px rgba(22,36,31,.05);
  font-size: 14px; line-height: 1.45; color: var(--ink);
}
.hv-radio { position: relative; width: 24px; height: 24px; flex: none; border-radius: 50%; border: 1.6px solid var(--line-2); background: var(--surface); }
.hv-opt.is-pick { background-color: var(--euc-wash); box-shadow: inset 0 0 0 1.6px var(--euc-mid); }
.hv-opt.is-pick .hv-radio { border-color: var(--euc); }
/* NO vector-effect here. `non-scaling-stroke` resolves the dash pattern in
   screen space, which defeats the pathLength="1" normalisation the
   dashoffset draw depends on — the tick would never fully hide. */
/* A definite 15px box, not `inset: 3px`. An inline <svg> with a viewBox but no
   width/height attributes is a replaced element with an intrinsic ratio, and an
   absolutely-positioned replaced element with `width: auto` takes its INTRINSIC
   width and treats the opposite inset as over-constrained — the size would come
   out engine-dependent. 3 + 15 + 3 = 21 ≈ the radio's 20.8px content box. */
.hv-tick { position: absolute; top: 3px; left: 3px; width: 15px; height: 15px; }
.hv-tick path {
  fill: none; stroke: var(--euc); stroke-width: 3.2;
  stroke-linecap: round; stroke-linejoin: round;
  stroke-dasharray: 1; stroke-dashoffset: 0;
}

/* ── beat 5 · already there ──────────────────────────────────────────
   Three columns at desktop: the note card, the flight track, the desktop
   window. 176 + 16 + 296 = 488 against a 525–562px board, so it fits with
   room at every desktop width. Below 1350px (nav's hamburger breakpoint,
   raised from 1100 19 Aug 2026, see "6 · NAV") the same three things
   stack and the flight swaps to the vertical arc.
   ──────────────────────────────────────────────────────────────────── */
.hv-b5 {
  position: absolute; inset: 0;
  display: grid;
  grid-template-columns: minmax(0, 176px) minmax(16px, 1fr) minmax(0, 296px);
  align-items: center;
  opacity: 0;
  transition: opacity .45s var(--ease);
}
.hv-stage[data-scene="5"] .hv-b5 { opacity: 1; }

/* grid-row is stated, not left to auto-placement: two items with definite
   columns and auto rows land in row 1 only because the placement cursor happens
   to allow it, and that is not a thing to leave to chance in a 3-column track. */
.hv-fold {
  grid-column: 1; grid-row: 1; justify-self: center;
  width: 100%; max-width: 176px;
  padding: 14px 14px 15px;
  background: var(--surface);
  border: 1px solid var(--line);
  box-shadow: 0 16px 32px -18px rgba(16,49,41,.36);
}
.hv-fold-h { font-family: var(--display); font-weight: 600; font-variation-settings: "SOFT" 16, "opsz" 22; font-size: 14px; color: var(--ink); }
.hv-fold-m { margin-top: 2px; font-size: 12px; color: var(--ink-3); }
.hv-fold-rule { height: 1px; background: var(--line); margin: 9px 0; }
.hv-fold-b { font-size: 13px; line-height: 1.55; color: var(--ink-2); }

/* desktop — a plain browser-window chrome, same paper/white treatment. */
.hv-desktop { grid-column: 3; grid-row: 1; justify-self: center; width: 100%; }
.hv-desktop-shell {
  width: 100%; max-width: 296px; margin-inline: auto;
  border-radius: 16px; overflow: hidden;
  background: #fff;
  border: 1px solid rgba(16,49,41,.07);
  box-shadow:
    0 1px 0 rgba(255,255,255,.9) inset,
    0 30px 60px -24px rgba(16,49,41,.30),
    0 10px 22px -12px rgba(16,49,41,.16);
}
.hv-desktop-bar { display: flex; gap: 5px; padding: 10px 13px; background: var(--paper-2); border-bottom: 1px solid var(--line); }
.hv-desktop-bar i { width: 7px; height: 7px; border-radius: 50%; background: var(--line-2); }
.hv-desktop-screen { padding: 14px 14px 15px; }
.hv-dt-title { font-family: var(--display); font-weight: 500; font-size: 14.5px; color: var(--ink); margin-bottom: 11px; }
.hv-dt-row { padding: 9px 0 8px; border-top: 1px solid var(--line); }
.hv-dt-row.hv-dt-new { padding-top: 0; border-top: 0; }
.hv-dt-tag {
  display: inline-block; margin-bottom: 5px;
  padding: 2px 9px; border-radius: 999px;
  background: var(--euc-wash); color: var(--euc-deep);
  font-size: 12px; font-weight: 700; letter-spacing: .06em; text-transform: uppercase;
}
.hv-dt-h { font-size: 12.5px; font-weight: 600; color: var(--ink); }
.hv-dt-b { margin-top: 4px; font-size: 12px; line-height: 1.55; color: var(--ink-2); }
.hv-dt-b mark { background: var(--euc-wash); color: inherit; border-radius: 3px; padding: 0 2px; }
.hv-dt-old .hv-dt-h { font-size: 12px; font-weight: 500; color: var(--ink-3); }
/* The arriving row's content fades in on its own beat (the "catch"),
   separate from the desktop window's own entrance. */
.hv-dt-new-body { opacity: 0; }

/* ── THE FLIGHT ──────────────────────────────────────────────────────
   Founder, verbatim: "we want to make it feel special like it jumps over."
   The old version arced a small rectangle. This folds the note into a
   paper plane, flies it, and unfolds it as the row — the one place in this
   design where boldness is deliberately spent.

   The plane lives INSIDE the arc <svg>, not as an HTML overlay, so it
   inherits that svg's own responsive viewBox scaling; a px-based CSS
   offset-path on an HTML element cannot track a fluid path across
   breakpoints. It uses offset-rotate:AUTO — unlike the old note token,
   which deliberately stayed upright at 0deg. A card should not tumble; a
   plane that does not point where it is going is a plane falling sideways.

   The fold is a crossfade-and-scale swap, not a shape morph: the card
   shrinks and rotates away while the plane scales up at the same point.
   Full path morphing between a rounded rectangle and a folded triangle is
   not something CSS can do honestly, and faking it badly would cost more
   than the swap does.
   ──────────────────────────────────────────────────────────────────── */
.hv-arc { pointer-events: none; overflow: visible; }
/* Overlays the WHOLE board so the arc has real height to climb into. The
   viewBox aspect (128/100 = 1.28) is chosen to match the board's rendered
   aspect across every desktop width (525–562 / 424 = 1.24–1.33), so
   preserveAspectRatio="none" shears the plane by at most 4% — invisible on
   a 35px glyph, and the price of endpoints that stay pinned to the card
   and the row as the column flexes. */
.hv-arc-h { position: absolute; inset: 0; width: 100%; height: 100%; }
.hv-arc-v { display: none; }

/* Trail: nine discrete dashes sampled at even ARC-LENGTH intervals along
   the same curve the plane flies (even t would bunch them at the ends),
   each fading in as the plane passes. A single dashed <path> cannot be
   revealed progressively — stroke-dashoffset shifts the pattern rather
   than uncovering it — so the dashes are elements and the reveal is
   opacity. non-scaling-stroke is safe here (and wanted, so both
   orientations get the same weight) because nothing dashes this stroke. */
.hv-trail line {
  /* 2.1/round: at 1.7 the dashes sat close to the wash and the trail read as
     an accident of the background rather than the flight's wake — verified
     against a frozen mid-flight frame, where only 2 of 9 dashes registered. */
  stroke: var(--euc-mid); stroke-width: 2.1; stroke-linecap: round;
  opacity: 0; vector-effect: non-scaling-stroke;
}
.hv-burst {
  fill: none; stroke: var(--gold); stroke-width: 1.6;
  opacity: 0; transform-box: fill-box; transform-origin: center;
  vector-effect: non-scaling-stroke;
}
/* Two wings in two tones, meeting on the crease — the cheapest honest way
   to read as folded paper rather than as a "send" arrow. Symmetric about
   x=0 so the fill-box centre is the nose axis and the scale pivots true. */
.hv-plane { opacity: 0; transform-box: fill-box; transform-origin: center; }
.hv-plane-a { fill: var(--surface); stroke: var(--line-2); stroke-width: .35; vector-effect: non-scaling-stroke; }
.hv-plane-b { fill: var(--euc-wash2); stroke: var(--line-2); stroke-width: .35; vector-effect: non-scaling-stroke; }

@supports (offset-path: path('M0 0')) {
  .hv-plane {
    offset-path: path('M20 50 C 50 18, 72 28, 94 48');
    offset-distance: 0%;
    offset-rotate: auto;
  }
  .hv-plane-v { offset-path: path('M20 2 C 34 8, 34 19, 20 28'); }
  .hv-stage[data-scene="5"] .hv-plane { animation: hv-fly 1.35s var(--ease) 1.05s backwards; }
  /* `backwards` and NOT forwards: the plane's resting state is its own base
     style (opacity 0), so it is simply gone once it has landed and the row
     it became is what remains. */
  @keyframes hv-fly {
    0%   { offset-distance: 0%;   opacity: 0; transform: scale(.4);  filter: drop-shadow(0 2px 3px rgba(16,49,41,.22)); }
    11%  {                       opacity: 1; transform: scale(1); }
    52%  { offset-distance: 52%;              transform: scale(1.14); filter: drop-shadow(0 22px 22px rgba(16,49,41,.34)); }
    88%  {                       opacity: 1; transform: scale(1); }
    100% { offset-distance: 100%; opacity: 0; transform: scale(.72); filter: drop-shadow(0 3px 5px rgba(16,49,41,.2)); }
  }
}
/* Fallback: no motion path. Same five sampled points off the same curve,
   with their real tangent angles hard-coded, so the flight keeps its shape
   and its heading. CSS px on an SVG child are user units, so these
   translate values are viewBox coordinates. */
@supports not (offset-path: path('M0 0')) {
  .hv-stage[data-scene="5"] .hv-plane   { animation: hv-fly-h 1.35s var(--ease) 1.05s backwards; }
  .hv-stage[data-scene="5"] .hv-plane-v { animation: hv-fly-v 1.35s var(--ease) 1.05s backwards; }
  @keyframes hv-fly-h {
    0%   { opacity: 0; transform: translate(20px,50px)    rotate(-46.8deg) scale(.4); }
    11%  { opacity: 1; }
    25%  {             transform: translate(36.3px,36.1px) rotate(-32.2deg) scale(1); }
    50%  {             transform: translate(56.4px,29.4px) rotate(-1.5deg)  scale(1.14); }
    75%  {             transform: translate(76.9px,35.1px) rotate(29.5deg)  scale(1); }
    100% { opacity: 0; transform: translate(94px,48px)     rotate(42.3deg)  scale(.72); }
  }
  @keyframes hv-fly-v {
    0%   { opacity: 0; transform: translate(20px,2px)      rotate(23.2deg)  scale(.4); }
    11%  { opacity: 1; }
    25%  {             transform: translate(27.3px,6.7px)  rotate(45.7deg)  scale(1); }
    50%  {             transform: translate(30.5px,14.6px) rotate(94.7deg)  scale(1.14); }
    75%  {             transform: translate(26.7px,22.4px) rotate(131.4deg) scale(1); }
    100% { opacity: 0; transform: translate(20px,28px)     rotate(147.3deg) scale(.72); }
  }
}

/* ══ THE SEQUENCE ══════════════════════════════════════════════════════
   Driven by a `data-scene` attribute on .hv-stage that script.js steps
   1 → 5, not by a chain of animation-delays. A delay chain was fine for a
   one-shot intro but cannot be replayed: `forwards` fill makes the end
   state permanent and there is no way to rewind it without tearing the
   elements out of the DOM.

     1 THE VISIT      six real interaction types; "Home visit" is chosen
     2 JUST TALK      she says it out loud; three fragments land
     3 A NOTE         the fragments become the real draft, marks and all
     4 SAFETY         the question that is never skipped, answered
     5 ALREADY THERE  the note folds, flies, and lands on the desk

   hv-pop is NOT used in this section any more — it is kept here because
   §10b's .flow-node reuses it ("staggered along the line" without new
   keyframes). Do not delete it with the rest of the hero's keyframes.
   ──────────────────────────────────────────────────────────────────── */
@keyframes hv-pop   { 0% { opacity: 0; transform: scale(.2); } 50% { opacity: 1; transform: scale(1.8); } 74% { transform: scale(.85); } 100% { opacity: 1; transform: scale(1); } }
@keyframes hv-burst { 0% { opacity: .9; transform: scale(.4); } 100% { opacity: 0; transform: scale(3.6); } }
@keyframes hv-land  { 0%, 100% { transform: none; } 26% { transform: translateY(-5px) scale(1.018); } 58% { transform: translateY(2px) scale(.995); } }
@keyframes hv-dt-new-in { 0% { opacity: 0; transform: translateY(11px) scale(.93); } 62% { opacity: 1; transform: translateY(-3px) scale(1.025); } 100% { opacity: 1; transform: none; } }
@keyframes hv-fade-up { from { opacity: 0; transform: translateY(5px); } to { opacity: 1; transform: none; } }

/* — beat 1 — */
@keyframes hv-tile-in { from { opacity: 0; transform: translateY(12px) scale(.9); } to { opacity: 1; transform: none; } }
@keyframes hv-tile-out { from { opacity: 1; transform: none; } to { opacity: .32; transform: scale(.955); } }
@keyframes hv-tile-pick {
  0%   { transform: none;          background-color: var(--euc-wash);  box-shadow: inset 0 0 0 1px rgba(33,122,106,.10); }
  62%  { transform: scale(1.08); }
  100% { transform: scale(1.05);   background-color: var(--euc-wash2); box-shadow: inset 0 0 0 2px var(--euc-mid), 0 12px 26px -14px rgba(16,49,41,.34); }
}
/* Two animations, one element, no overlap in time: the entrance ends by
   0.94s at the latest and the choice starts at 1.35s, so neither has to
   out-cascade the other while both are active. */
.hv-stage[data-scene="1"] .hv-tile {
  animation: hv-tile-in .48s var(--ease-out) var(--sd, 0s) backwards,
             hv-tile-out .5s var(--ease) 1.5s forwards;
}
.hv-stage[data-scene="1"] .hv-tile.is-pick {
  animation: hv-tile-in .48s var(--ease-out) var(--sd, 0s) backwards,
             hv-tile-pick .6s var(--ease-out) 1.35s forwards;
}
.hv-tiles .hv-tile:nth-child(1) { --sd: .16s; }
.hv-tiles .hv-tile:nth-child(2) { --sd: .22s; }
.hv-tiles .hv-tile:nth-child(3) { --sd: .28s; }
.hv-tiles .hv-tile:nth-child(4) { --sd: .34s; }
.hv-tiles .hv-tile:nth-child(5) { --sd: .40s; }
.hv-tiles .hv-tile:nth-child(6) { --sd: .46s; }

/* — beat 2 — */
@keyframes hv-frag-in { from { opacity: 0; transform: translateY(10px) scale(.95); } to { opacity: 1; transform: none; } }
@keyframes hv-ripple { from { opacity: .5; transform: scale(1); } to { opacity: 0; transform: scale(1.75); } }
@keyframes hv-blink { 0%, 100% { opacity: 1; } 50% { opacity: .22; } }
.hv-stage[data-scene="2"] .hv-frag { animation: hv-frag-in .5s var(--ease-out) var(--sd, 0s) backwards; }
.hv-frags .hv-frag:nth-child(1) { --sd: .45s; }
.hv-frags .hv-frag:nth-child(2) { --sd: 1.15s; }
.hv-frags .hv-frag:nth-child(3) { --sd: 1.85s; }
.hv-stage[data-scene="2"] .hv-mic-ring { animation: hv-ripple 2.1s var(--ease-out) infinite; }
.hv-mic-wrap .hv-mic-ring:nth-child(2) { animation-delay: .7s; }
.hv-mic-wrap .hv-mic-ring:nth-child(3) { animation-delay: 1.4s; }
.hv-stage[data-scene="2"] .hv-listen i { animation: hv-blink 1.3s ease-in-out infinite; }

/* — beat 3 — */
@keyframes hv-line-in { from { opacity: 0; transform: translateY(9px); } to { opacity: 1; transform: none; } }
@keyframes hv-mark { from { background-size: 0 100%; } to { background-size: 100% 100%; } }
.hv-stage[data-scene="3"] .hv-note-l { animation: hv-line-in .45s var(--ease-out) var(--ld, 0s) backwards; }
/* nth-child, not nth-of-type: the heading, the metadata and the rule are
   siblings, so the first sentence is child 4. --md is set here too and
   inherits down to the <mark>s. */
.hv-note .hv-note-l:nth-child(4) { --ld: .28s; --md: 1.30s; }
.hv-note .hv-note-l:nth-child(5) { --ld: .62s; --md: 1.60s; }
.hv-note .hv-note-l:nth-child(6) { --ld: .96s; --md: 1.90s; }
.hv-stage[data-scene="3"] .hv-note-l mark { animation: hv-mark .6s var(--ease-out) var(--md, 0s) backwards; }

/* — beat 4 — */
@keyframes hv-opt-in { from { opacity: 0; transform: translateY(9px); } to { opacity: 1; transform: none; } }
@keyframes hv-opt-pick {
  from { background-color: var(--paper-2); box-shadow: inset 0 0 0 1px rgba(22,36,31,.05); }
  to   { background-color: var(--euc-wash); box-shadow: inset 0 0 0 1.6px var(--euc-mid); }
}
@keyframes hv-radio-on { from { border-color: var(--line-2); } to { border-color: var(--euc); } }
@keyframes hv-tick-draw { from { stroke-dashoffset: 1; } to { stroke-dashoffset: 0; } }
.hv-stage[data-scene="4"] .hv-opt { animation: hv-opt-in .45s var(--ease-out) var(--od, 0s) backwards; }
.hv-opts .hv-opt:nth-child(1) { --od: .22s; }
.hv-opts .hv-opt:nth-child(2) { --od: .36s; }
.hv-opts .hv-opt:nth-child(3) { --od: .50s; }
.hv-stage[data-scene="4"] .hv-opt.is-pick {
  animation: hv-opt-in .45s var(--ease-out) var(--od, 0s) backwards,
             hv-opt-pick .4s var(--ease) 1.25s backwards;
}
.hv-stage[data-scene="4"] .hv-opt.is-pick .hv-radio { animation: hv-radio-on .35s var(--ease) 1.25s backwards; }
.hv-stage[data-scene="4"] .hv-opt.is-pick .hv-tick path { animation: hv-tick-draw .5s var(--ease-out) 1.45s backwards; }

/* — beat 5 —
   The whole beat on one clock:
     0.00  the surface recedes, the desktop window slides in from the right
     0.10  the note card scales in at the left
     0.75  the card folds — tilt + shrink + dim  ─┐ crossfade-and-scale swap,
     1.05  the plane launches from the same point ─┘ peaking together
     1.10  the dashed trail draws itself dash by dash, behind the plane
     2.40  the plane is gone, the gold ring rings out, the row opens
     2.60  the row's content bounces in — the note, unfolded, on the record
   Ends at ~2.9s, which makes the full five-beat run ~15.9s. */
@keyframes hv-desk-in { from { opacity: 0; transform: translateX(26%); } to { opacity: 1; transform: none; } }
@keyframes hv-fold-in { from { opacity: 0; transform: translateX(-14px) scale(.9); } to { opacity: 1; transform: none; } }
/* The card tilts, shrinks and DIMS — it does not disappear.
   Two reasons, and the storyboard said "vanishes":
     · truth. The note does not leave the phone when it is shared; it is
       still there afterwards. A card that vanishes says the opposite.
     · composition. Beat 5 is the resting tableau. A fully vanished card
       leaves a blank third of the board at rest with a dashed trail
       starting from nowhere.
   The fold gesture and the plane launching from that exact point are both
   intact — this only changes where the card ENDS. To go back to the literal
   storyboard, take the 100% keyframe to opacity 0 / scale .22 / -22deg. */
@keyframes hv-fold-fold {
  0%   { opacity: 1;   transform: none; }
  55%  { opacity: .78; transform: scale(.86) rotate(-7deg); }
  100% { opacity: .34; transform: scale(.9) rotate(-4deg); }
}
@keyframes hv-dash-in { from { opacity: 0; } to { opacity: .72; } }
.hv-stage[data-scene="5"] .hv-desktop { animation: hv-desk-in .6s var(--ease-out) backwards; }
.hv-stage[data-scene="5"] .hv-fold {
  animation: hv-fold-in .5s var(--ease-out) .1s backwards,
             hv-fold-fold .55s var(--ease) .75s forwards;
}
/* `both`, not `backwards`: the trail has to STAY drawn after the plane has
   gone — it is the visible record of the route. */
.hv-stage[data-scene="5"] .hv-trail line { animation: hv-dash-in .3s var(--ease-out) var(--dd, 0s) both; }
.hv-trail line:nth-child(1) { --dd: 1.10s; }
.hv-trail line:nth-child(2) { --dd: 1.24s; }
.hv-trail line:nth-child(3) { --dd: 1.38s; }
.hv-trail line:nth-child(4) { --dd: 1.52s; }
.hv-trail line:nth-child(5) { --dd: 1.66s; }
.hv-trail line:nth-child(6) { --dd: 1.80s; }
.hv-trail line:nth-child(7) { --dd: 1.94s; }
.hv-trail line:nth-child(8) { --dd: 2.08s; }
.hv-trail line:nth-child(9) { --dd: 2.22s; }
.hv-stage[data-scene="5"] .hv-burst { animation: hv-burst .8s var(--ease-out) 2.4s forwards; }
.hv-stage[data-scene="5"] .hv-desktop-shell { animation: hv-land .6s var(--ease-out) 2.4s; }
/* The landing slot stays collapsed until the note actually arrives:
   reserving the space leaves a conspicuous blank band under the window
   title, which reads as a rendering fault rather than as anticipation.
   0fr → 1fr animates height with no hard-coded pixel value and costs no
   CLS — the card grows inside the fixed-height board, in its own column.
   The delay lives on the scene rule, not the base rule, so leaving scene 5
   (a replay) collapses it immediately instead of 2.4s later. */
/* The row's space is ALWAYS reserved — no 0fr→1fr opening. That trick was
   inherited from the old hero, where the desktop sat on screen for nine
   seconds and an empty slot read as a fault. Here the desktop only slides in
   at beat 5, so nobody ever sees the slot empty — and the grid-rows animation
   was real layout work that scored as CLS twice per run (once at boot when
   the no-js tableau collapsed to scene 0, once on landing). Reserved space +
   opacity/transform on the content shifts nothing, ever. */
.hv-stage[data-scene="5"] .hv-dt-new-body { animation: hv-dt-new-in .45s var(--ease-out) 2.6s forwards; }

/* — the no-JS floor, finished rather than mid-flight —
   The markup ships at beat 5, so without this the scene-5 choreography would
   run once on load and a blocked script would leave the visitor looking at an
   OPEN BUT EMPTY case-history slot for 2.6s before the row appeared. `.no-js`
   is on <html> and script.js removes it at boot (same mechanism .reveal uses),
   so this doubles as the pre-script paint: the static tableau is what shows
   until JS is definitely running, instead of an animation that starts and is
   then cancelled by hvSet(0).
   Selectors carry `.hv-stage[data-scene]` so they match the specificity of the
   scene-5 animation rules they are switching off, and win on document order —
   `!important` is banned in this file. */
.no-js .hv-plane,
.no-js .hv-burst { display: none; }
.no-js .hv-stage[data-scene] .hv-trail line { animation: none; opacity: .72; }
.no-js .hv-stage[data-scene] .hv-fold { animation: none; opacity: 1; transform: none; }
.no-js .hv-stage[data-scene] .hv-desktop { animation: none; }
.no-js .hv-stage[data-scene] .hv-desktop-shell { animation: none; }
.no-js .hv-stage[data-scene] .hv-dt-new-body { animation: none; opacity: 1; }

/* — the narrator — */
.hv-caption {
  margin-top: 16px;
  /* Two lines' worth of height is RESERVED, because the text changes five
     times and the longest line wraps at some widths. Without this the swap
     would move the replay button under it — a 0.05 CLS for free. */
  min-height: calc(2 * 1.5em);
  display: grid; place-items: center;
  font-family: var(--display);
  font-style: italic; font-weight: 400;
  font-variation-settings: "SOFT" 40, "opsz" 40;
  font-size: 15px; line-height: 1.5;
  color: var(--euc-deep);
  text-align: center;
}
.hv-stage[data-scene="0"] .hv-caption { opacity: 0; }
/* The cross-fade is an alternating pair of IDENTICAL keyframes, toggled by
   script.js. A transition cannot do it — the end state never changes, only
   the text — and re-adding the same animation name does not restart an
   animation; the computed animation-name has to actually differ. The other
   way to restart it is a forced reflow, i.e. a synchronous layout, on a
   page whose entire motion budget is "nothing that touches layout". */
.hv-caption.is-in { animation: hv-cap-in .5s var(--ease-out) both; }
.hv-caption.is-in.is-alt { animation-name: hv-cap-in-b; }
@keyframes hv-cap-in   { from { opacity: 0; transform: translateY(5px); } to { opacity: 1; transform: none; } }
@keyframes hv-cap-in-b { from { opacity: 0; transform: translateY(5px); } to { opacity: 1; transform: none; } }

/* — replay — */
.hero-visual { display: flex; flex-direction: column; align-items: center; }
.hero-visual > .hv-stage { width: 100%; }
.hv-replay {
  display: inline-flex; align-items: center; gap: 6px;
  margin: 14px auto 0; padding: 7px 14px;
  border-radius: var(--r-pill);
  border: 1px solid var(--line-2);
  background: rgba(255,255,255,.72);
  -webkit-backdrop-filter: blur(6px); backdrop-filter: blur(6px);
  font-family: var(--body); font-size: 12px; font-weight: 500;
  color: var(--ink-2); cursor: pointer;
  opacity: 0;
  animation: hv-fade-up .4s var(--ease-out) forwards;
  transition: color .25s var(--ease), border-color .25s var(--ease), background-color .25s var(--ease);
}
.hv-replay[hidden] { display: none; }
.hv-replay:hover { color: var(--ink); border-color: var(--euc-mid); background: #fff; }
.hv-replay:focus-visible { outline: 2px solid var(--euc); outline-offset: 3px; }
.hv-replay svg { color: var(--euc); }

/* Reduced motion: all five beats, none of the movement.
   Resting on one beat would strip the story from exactly the people least
   able to get it back, so the sequence simply does not auto-advance and the
   replay control becomes a manual stepper (script.js relabels it "Next
   screen"). Same beats, same order, under the reader's own thumb.

   `!important` is banned in this file, so every override below matches or
   beats the specificity of the rule it is switching off and wins on
   document order instead — `.hv-stage[data-scene]` mirrors the shape of
   `.hv-stage[data-scene="N"]` exactly, and any extra class on the animated
   rule is mirrored too. Every stagger here uses `backwards` fill, so
   `animation: none` alone would strand elements at opacity 0; opacity and
   transform are restored on the same line, every time. */
@media (prefers-reduced-motion: reduce) {
  .hv-board { opacity: 1; transform: none; transition: none; }
  .hv-frame { transition: none; }
  .hv-beat { transition: none; transform: none; }
  .hv-beat.is-past { transform: none; }
  .hv-b5 { transition: none; }

  .hv-stage[data-scene] .hv-tile { animation: none; opacity: 1; transform: none; }
  /* The chosen tile keeps its ANSWER, statically. Killing hv-tile-pick alone
     would leave beat 1 as six identical tiles and no visible choice — the one
     thing that beat is for. (The option in beat 4 needs no equivalent: its
     picked look is already the base style there.) */
  .hv-stage[data-scene] .hv-tile.is-pick {
    animation: none; opacity: 1; transform: none;
    background-color: var(--euc-wash2);
    box-shadow: inset 0 0 0 2px var(--euc-mid);
  }
  .hv-stage[data-scene] .hv-frag { animation: none; opacity: 1; transform: none; }
  .hv-stage[data-scene] .hv-mic-ring { animation: none; }
  .hv-stage[data-scene] .hv-listen i { animation: none; }
  .hv-stage[data-scene] .hv-note-l { animation: none; opacity: 1; transform: none; }
  .hv-stage[data-scene] .hv-note-l mark { animation: none; background-size: 100% 100%; }
  .hv-stage[data-scene] .hv-opt,
  .hv-stage[data-scene] .hv-opt.is-pick { animation: none; opacity: 1; transform: none; }
  .hv-stage[data-scene] .hv-opt.is-pick .hv-radio { animation: none; border-color: var(--euc); }
  .hv-stage[data-scene] .hv-opt.is-pick .hv-tick path { animation: none; stroke-dashoffset: 0; }

  /* No flight — the row is simply there. */
  .hv-plane { display: none; }
  .hv-burst { display: none; }
  /* The trail stays, drawn, because it is the only thing left saying the
     two halves of the picture are connected. */
  .hv-stage[data-scene] .hv-trail line { animation: none; opacity: .72; }
  .hv-stage[data-scene] .hv-fold { animation: none; opacity: 1; transform: none; }
  .hv-stage[data-scene] .hv-desktop { animation: none; opacity: 1; transform: none; }
  .hv-stage[data-scene] .hv-desktop-shell { animation: none; }
  .hv-dt-new-body { opacity: 0; animation: none; }
  .hv-stage[data-scene="5"] .hv-dt-new-body { opacity: 1; animation: none; }
  /* Stepper shows one beat at a time; the visibility gate must not lag. */
  .hv-beat { transition: none; }

  .hv-caption.is-in,
  .hv-caption.is-in.is-alt { animation: none; opacity: 1; transform: none; }
  .hv-replay { opacity: 1; animation: none; }
}

/* Narrow: the whole stage stacks and the flight rotates to vertical, at
   1350, matching the nav's breakpoint (raised from 1100 19 Aug 2026 to
   fit a tenth nav link without clipping, see "6 · NAV" for the
   measurements), so the top of the page changes shape once rather than
   in two separate steps. script.js's matchMedia mirrors this same
   value, see its comment.

   Size budget at 390px (board 350 wide × 512 tall, beat inner 320 × 482):
     beat 1  bar 26 + q 20 + 12 + tiles 3 rows × 135          = fits
     beat 2  bar 26 + prog 15 + frags 167 + mic 94 + 26 = 328 = fits
     beat 3  bar 26 + 16 + card 320                          = fits
     beat 4  bar 26 + prog 15 + q 44 + 14 + opts 198 = 297    = fits
     beat 5  fold 143 + arc 78 + window 244 + gaps 12 = 477   = fits
   At 320px the ≤420 block below drops the tile and body sizes a step and
   every beat still clears 482.
   ──────────────────────────────────────────────────────────────────── */
@media (max-width: 1350px) {
  .hero { min-height: 0; align-items: flex-start; padding: 92px 0 56px; }
  .hero-wrap { grid-template-columns: 1fr; }
  .hero-copy { max-width: none; }
  .hero-visual { margin-top: 30px; }

  .hv-board { height: 512px; }
  .hv-beat { padding: 14px 14px 16px; }
  .hv-tiles { grid-template-columns: repeat(2, minmax(0, 1fr)); gap: 10px; }

  .hv-b5 {
    grid-template-columns: 1fr;
    justify-items: center; align-content: center;
    row-gap: 6px;
  }
  .hv-fold { grid-column: 1; grid-row: 1; max-width: 240px; }
  .hv-arc-h { display: none; }
  .hv-arc-v {
    display: block; position: static;
    grid-column: 1; grid-row: 2;
    width: 100px; height: 78px;
  }
  .hv-desktop { grid-column: 1; grid-row: 3; }
  .hv-desktop-shell { max-width: 280px; }

  /* Three lines reserved, not two: the longest narrator line wraps further
     in a 350px stage than in a 562px one. */
  .hv-caption { min-height: calc(3 * 1.5em); font-size: 14.5px; }
}

@media (max-width: 420px) {
  .hv-tile b { font-size: 12.5px; }
  .hv-tile i { font-size: 11.5px; }
  .hv-ico { width: 38px; height: 38px; }
  .hv-ico .hv-i { width: 19px; height: 19px; }
  .hv-q { font-size: 15.5px; }
  .hv-note { padding: 14px 14px 16px; }
  .hv-note-l { font-size: 13.5px; }
  .hv-note-m { font-size: 11.5px; }
  .hv-opt { font-size: 13.5px; padding: 12px; }
  .hv-frag { font-size: 13.5px; max-width: 90%; }
  .hv-fold { max-width: 224px; }
  .hv-desktop-shell { max-width: 272px; }
  /* Beat 3's bar is the tightest row in the stage: "Back" (50) + the
     segmented control (169) = 231 against a 248px inner width at 320px.
     Trimming the control's padding buys back 16px of that. */
  .hv-seg { font-size: 11.5px; }
  .hv-seg b, .hv-seg i { padding: 5px 8px; }
}

/* ===========================================================================
   8 · SECTOR STRIP
   =========================================================================== */
.sectors {
  position: relative;
  background: var(--paper-2);
  border-top: 1px solid var(--line);
  border-bottom: 1px solid var(--line);
  overflow: hidden; padding: 18px 0;
  -webkit-mask-image: linear-gradient(90deg, transparent, #000 12%, #000 88%, transparent);
          mask-image: linear-gradient(90deg, transparent, #000 12%, #000 88%, transparent);
}
.sectors-track {
  display: flex; align-items: center; gap: 24px;
  width: max-content;
  animation: marquee 46s linear infinite;
}
.sectors-track span {
  font-size: 12.5px; font-weight: 500;
  letter-spacing: .07em; text-transform: uppercase;
  color: var(--ink-3); white-space: nowrap;
}
.sectors-track i { width: 4px; height: 4px; border-radius: 50%; background: var(--euc); opacity: .5; flex: none; }
@keyframes marquee { to { transform: translateX(-50%) } }
.sectors:hover .sectors-track { animation-play-state: paused; }

/* ===========================================================================
   9 · BANDS
   =========================================================================== */
.band { position: relative; padding: var(--band-y) 0; }
.band-paper  { background: var(--paper); }
.band-cream  { background: var(--paper-2); }
.band-deep   { background: var(--deep); color: var(--on-deep); }
.band-wash   { background: linear-gradient(172deg, var(--euc-wash) 0%, var(--paper) 78%); }

/* ===========================================================================
   10 · STEPS
   =========================================================================== */
.steps { display: grid; grid-template-columns: repeat(3, 1fr); gap: 18px; }
.step {
  position: relative;
  padding: 32px 28px 30px;
  border-radius: var(--r-lg);
  background: var(--surface);
  border: 1px solid var(--line);
  box-shadow: var(--lift-1);
  transition: transform .5s var(--ease-out), box-shadow .5s var(--ease-out), border-color .4s var(--ease);
}
.step:hover { transform: translateY(-5px); border-color: var(--euc-wash2); box-shadow: var(--lift-2); }
.step-n {
  position: absolute; top: 24px; right: 26px;
  font-family: var(--display); font-size: 40px; font-weight: 300;
  font-variation-settings: "opsz" 100;
  line-height: 1; color: var(--paper-3);
  transition: color .45s var(--ease);
}
.step:hover .step-n { color: var(--euc-wash2); }

.step-ico { width: 46px; height: 46px; border-radius: 13px; display: grid; place-items: center; margin-bottom: 22px; }
.step-ico[data-tone="clay"]  { background: var(--clay-wash); color: var(--clay); }
.step-ico[data-tone="euc"]   { background: var(--euc-wash);  color: var(--euc); }
.step-ico[data-tone="gold"]  { background: var(--gold-wash); color: var(--gold-ink); }

.step-t {
  font-family: var(--display); font-weight: 500;
  font-variation-settings: "SOFT" 22, "opsz" 40;
  font-size: 20.5px; letter-spacing: -.018em;
  margin-bottom: 11px; color: var(--ink);
}
.step-b { font-size: .96rem; line-height: 1.68; color: var(--ink-2); }

@media (max-width: 880px) { .steps { grid-template-columns: 1fr; } }

/* ===========================================================================
   10b · WALKTHROUGH — one continuous flow, restructured 17 Aug 2026.
   ---------------------------------------------------------------------------
   .flow-line is a single static SVG spine (no draw-in animation — this
   section is tall and scrolled past in stages, unlike the hero's short,
   fully-visible diagram, so a full draw-in would either finish off-screen
   or look unfinished above the fold). Same stroke language as the hero's
   dashed flight trail (.hv-trail — thin, non-scaling, hand-drawn bezier; the
   old .hv-line connector this used to cite was deleted in the 17 Aug rebuild)
   so it reads as the same visual family without replaying the hero's own
   choreography.

   .flow-stage is a two-column grid; .flow-node spans both columns and
   centers itself on the spine, .flow-content sits in EITHER column via the
   --l/--r modifier (bookends span both, centered). This is CSS placement
   only — DOM order stays Before→01→02→03→After regardless of visual side,
   so reading order for assistive tech never depends on which side a stage
   lands on. Motion is just the node popping in (reuses the hero's `hv-pop`
   keyframe, defined in §7 — kept alive for exactly this reuse when the old
   .hv-dot landing spark was deleted) as each stage's existing .reveal
   fires — "staggered along the line" without new keyframes.

   Below 900px the two-column alternation collapses to a single left-rail
   timeline (spine + node move to a fixed-width first column, content to a
   second column beside it) — genuinely vertical on mobile, not a squashed
   two-column grid.

   .jr-mock (the note-card look), .compare, .pillar-t/-b, .eyebrow, .ticks
   are all reused from #voice/#trust/#integration — only .flow-* and
   .jr-mock-head/.jr-mock-badge are new to this section.
   =========================================================================== */
.flow { position: relative; margin-top: clamp(8px, 2vw, 16px); }

.flow-line {
  position: absolute; top: 6px; bottom: 6px; left: 50%;
  width: 56px; margin-left: -28px;
  z-index: 0; pointer-events: none;
}
.flow-line svg { width: 100%; height: 100%; display: block; }
.flow-path { fill: none; stroke: var(--euc-wash2); stroke-width: 2; vector-effect: non-scaling-stroke; }

.flow-stages { position: relative; z-index: 1; display: grid; gap: clamp(52px, 7vw, 92px); }

.flow-stage {
  display: grid;
  grid-template-columns: 1fr 1fr;
  column-gap: clamp(28px, 4vw, 56px);
  row-gap: 18px;
  align-items: start;
}
.flow-node {
  grid-column: 1 / -1; justify-self: center;
  width: 42px; height: 42px; border-radius: 50%;
  display: grid; place-items: center;
  background: var(--surface); border: 2px solid var(--euc-wash2);
  font-family: var(--display); font-weight: 600; font-size: 14.5px; color: var(--euc-deep);
  box-shadow: var(--lift-1);
  opacity: 0; transform: scale(.3);
  transform-box: fill-box; transform-origin: center;
}
.flow-stage.is-in .flow-node { animation: hv-pop .4s var(--ease-out) forwards; }
.flow-node--dot { width: 13px; height: 13px; border: 0; background: var(--gold); box-shadow: none; }

.flow-content { grid-column: 2 / 3; }
.flow-stage--l .flow-content { grid-column: 1 / 2; }
/* Real app screens inside the walkthrough, added 2026-08-23. The section
   was entirely text while the hero carried every visual; these are the two
   moments worth seeing — a question with voice running, and the finished
   note. Captured on an iPhone 6.9" simulator against synthetic data, so no
   client information appears in them. Narrow max-width on purpose: a phone
   screenshot at full column width dwarfs the prose it illustrates. */
.flow-shot { margin: 18px 0 0; }
.flow-shot img {
  display: block;
  width: 100%;
  max-width: 252px;
  height: auto;
  border-radius: 20px;
  border: 1px solid var(--paper-3);
  box-shadow: 0 14px 34px rgba(32, 26, 18, .11);
}

.flow-stage--bookend .flow-content { grid-column: 1 / -1; max-width: 54ch; margin-inline: auto; text-align: center; }

.flow-label {
  font-size: 11.5px; font-weight: 600; letter-spacing: .13em; text-transform: uppercase;
  color: var(--ink-3); margin-bottom: 10px;
}
.flow-p { font-size: 1rem; line-height: 1.7; color: var(--ink-2); }
.flow-pain {
  font-size: .93rem; line-height: 1.55; font-style: italic;
  color: var(--ink-3); margin-bottom: 10px;
}
.flow-content .jr-mock,
.flow-content .compare { margin-top: 20px; }
.flow-aside { margin-top: 16px; font-size: .87rem; line-height: 1.62; color: var(--ink-3); }

@media (max-width: 900px) {
  .flow-line { left: 0; width: 42px; margin-left: 0; }
  .flow-stage {
    grid-template-columns: 42px 1fr;
    column-gap: 16px; row-gap: 0;
  }
  .flow-node { grid-column: 1; grid-row: 1; justify-self: center; align-self: start; margin-top: 2px; }
  .flow-content,
  .flow-stage--l .flow-content,
  .flow-stage--bookend .flow-content {
    grid-column: 2; max-width: none; text-align: left; margin-inline: 0;
  }
}

.jr-mock {
  padding: 22px 24px 24px;
  border-radius: var(--r-lg);
  background: var(--surface);
  border: 1px solid var(--line);
  box-shadow: var(--lift-1);
}
.jr-mock-head { display: flex; align-items: center; justify-content: space-between; gap: 10px; margin-bottom: 12px; }
.jr-mock-head .jr-mock-label { margin-bottom: 0; }
.jr-mock-badge {
  flex: none;
  font-size: 10px; font-weight: 600; letter-spacing: .06em; text-transform: uppercase;
  padding: 3px 9px; border-radius: 999px;
  background: var(--euc-wash); color: var(--euc-deep);
}
.jr-mock-label {
  font-size: 10.5px; font-weight: 700;
  letter-spacing: .09em; text-transform: uppercase;
  color: var(--euc); margin-bottom: 12px;
}
.jr-mock-q {
  font-family: var(--display); font-weight: 400;
  font-variation-settings: "SOFT" 18, "opsz" 20;
  font-size: 16px; line-height: 1.42; color: var(--ink);
  margin-bottom: 14px;
}
.jr-mock-a {
  padding-left: 14px;
  border-left: 2px solid var(--euc-wash2);
  font-size: .96rem; line-height: 1.62; font-style: italic;
  color: var(--ink-2);
}
.jr-mock-draft { font-size: .98rem; line-height: 1.64; color: var(--ink); }
.jr-mock-draft mark { background: var(--euc-wash); color: inherit; border-radius: 3px; padding: 0 1px; }

/* ===========================================================================
   11 · PULL QUOTE — dark moment #1
   =========================================================================== */
.band-deep.band-quote {
  text-align: center;
  padding: clamp(60px, 7.5vw, 100px) 0;
  background:
    radial-gradient(70% 60% at 50% 0%, var(--deep-2) 0%, transparent 70%),
    var(--deep);
}
.pull { margin: 0; }
.pull-q {
  font-family: var(--display); font-weight: 300;
  font-variation-settings: "SOFT" 42, "opsz" 144;
  font-size: clamp(2.15rem, 5.6vw, 3.9rem);
  line-height: 1.04; letter-spacing: -.03em;
  color: var(--on-deep);
}
.pull-q em {
  font-style: italic;
  font-variation-settings: "SOFT" 72, "opsz" 144;
  color: var(--euc-light);
}
.pull-b {
  font-size: clamp(1rem, 1.45vw, 1.1rem);
  line-height: 1.7; color: var(--on-deep-2);
  max-width: 56ch; margin: 26px auto 0;
}
.pull-c {
  display: block; margin-top: 26px;
  font-size: 11.5px; font-style: normal;
  letter-spacing: .11em; text-transform: uppercase;
  color: var(--on-deep-3);
  transition: color .25s var(--ease);
}
.pull-c span { color: var(--euc-light); transition: color .25s var(--ease); }
/* Now a link to training.html (19 Aug 2026, was a plain <cite>) — a hover
   cue of its own, since .band-deep otherwise has no link styling to fall
   back on. */
a.pull-c:hover { color: var(--on-deep); }
a.pull-c:hover span { color: #fff; }

/* ===========================================================================
   12 · SPLIT (why voice)
   =========================================================================== */
.split {
  display: grid;
  grid-template-columns: minmax(0,1fr) minmax(0,1fr);
  gap: clamp(36px, 5.5vw, 70px);
  align-items: center;
}
.split-copy .h2 { margin-bottom: 24px; }

.ticks { display: grid; gap: 17px; margin-top: 30px; }
.ticks li { position: relative; padding-left: 30px; font-size: .96rem; line-height: 1.66; color: var(--ink-2); }
.ticks li::before {
  content: ""; position: absolute; left: 0; top: 6px;
  width: 18px; height: 18px; border-radius: 50%;
  background: var(--euc-wash) url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='11' height='11' viewBox='0 0 24 24' fill='none'%3E%3Cpath d='m5 12 4.5 4.5L19 7' stroke='%23217A6A' stroke-width='3' stroke-linecap='round' stroke-linejoin='round'/%3E%3C/svg%3E") center / 11px no-repeat;
}
.ticks strong { color: var(--ink); font-weight: 600; }

/* Deliberately ONE column, stated explicitly.
   This grid previously declared no `grid-template-columns` at all, so it fell
   back to one implicit row per child and stacked — while `.compare-arrow` kept
   `justify-self: center` and a rightward SVG, both of which only make sense
   side-by-side. So the layout read as a broken two-column grid.
   Side-by-side is NOT the fix: `.compare` lives in the walkthrough's alternating
   timeline column and measures 522px at a 1440px viewport, so two columns leave
   ~183px of text width per card — about 23 characters a line, far below
   readable for the two quotes that carry this section's whole argument.
   Vertical is the right layout here; the arrow was the thing that was wrong. */
.compare { display: grid; grid-template-columns: 1fr; gap: 13px; align-items: center; }
.compare-col { padding: 24px; border-radius: var(--r-md); border: 1px solid var(--line); background: var(--surface); }
.compare-then { background: var(--clay-wash); border-color: #EEDBD1; }
.compare-now  { background: var(--euc-wash);  border-color: var(--euc-wash2); }
.compare-h { font-size: 11px; font-weight: 600; letter-spacing: .11em; text-transform: uppercase; margin-bottom: 11px; }
.compare-then .compare-h { color: var(--clay); }
.compare-now  .compare-h { color: var(--euc-deep); }
.compare-body {
  font-family: var(--display); font-weight: 400;
  font-variation-settings: "SOFT" 18, "opsz" 20;
  font-size: 15.5px; line-height: 1.5; color: var(--ink);
}
.compare-note {
  margin-top: 12px; padding-top: 11px;
  border-top: 1px dashed var(--line-2);
  font-size: 12.8px; line-height: 1.55; color: var(--ink-3);
}
.compare-arrow {
  justify-self: center; width: 34px; height: 34px; border-radius: 50%;
  display: grid; place-items: center;
  background: var(--surface); border: 1px solid var(--line-2); color: var(--euc);
}
/* Rotate the glyph, don't swap the path: the SVG stays a plain right-arrow in
   the markup, so there is one arrow asset and the direction is a layout
   concern expressed in the layout layer. Points first-pass → after-the-check,
   which is now downward. */
.compare-arrow svg { transform: rotate(90deg); }

@media (max-width: 900px) { .split { grid-template-columns: 1fr; } }

/* ===========================================================================
   13 · STATS
   =========================================================================== */
.stats { display: grid; grid-template-columns: repeat(3, 1fr); gap: 18px; }
.stat {
  padding: 30px 26px 28px;
  border-radius: var(--r-lg);
  background: var(--surface);
  border: 1px solid var(--line);
  box-shadow: var(--lift-1);
  transition: border-color .4s var(--ease);
}
/* A data card isn't clickable, so hover no longer lifts it like the .step/
   .pillar tiles — it just brings the card's own citation forward, since the
   source link is the one interactive thing here worth pointing at. */
.stat:hover { border-color: var(--euc-wash2); }
.stat:hover .stat-src a { color: var(--euc-deep); border-bottom-color: var(--euc-deep); }

.stat-n {
  display: flex; align-items: baseline; gap: 9px;
  font-family: var(--display); font-weight: 300;
  font-variation-settings: "SOFT" 20, "opsz" 144;
  font-size: clamp(2.6rem, 4.2vw, 3.5rem);
  line-height: 1; letter-spacing: -.035em;
  color: var(--euc); margin-bottom: 16px;
}
.stat-was { font-size: max(13px, .46em); color: var(--ink-3); text-decoration: line-through; text-decoration-thickness: 1px; }
.stat-to  { color: var(--ink-3); align-self: center; flex: none; }

.stat-t {
  font-family: var(--display); font-weight: 500;
  font-variation-settings: "SOFT" 20, "opsz" 40;
  font-size: 17.5px; letter-spacing: -.016em;
  color: var(--ink); margin-bottom: 10px;
}
.stat-b { font-size: .93rem; line-height: 1.66; color: var(--ink-2); }
.stat-src {
  margin-top: 15px; padding-top: 13px;
  border-top: 1px solid var(--line);
  font-size: 11.5px; line-height: 1.55; color: var(--ink-3);
}
.stat-src cite { font-style: italic; }
/* The citation link is the point of the block, so it gets the accent and its
   own line — a source you can actually open is what separates a claim from a
   number on a slide. */
.stat-src a {
  display: inline-flex; align-items: center; gap: 5px;
  margin-top: 6px;
  color: var(--euc); font-weight: 600;
  border-bottom: 1px solid var(--euc-wash2);
  padding-bottom: 1px;
  transition: color .25s var(--ease), border-color .25s var(--ease), gap .25s var(--ease-out);
}
.stat-src a:hover { border-bottom-color: var(--euc); gap: 8px; }
.stat-src a svg { flex: none; }

/* .caveat component removed 17 Aug 2026 — both of its instances (the
   integration "What this isn't" and the evidence "What the evidence does not
   yet show" asides) were cut at founder direction, and no HTML file uses the
   class any more (grepped all four pages). */

/* 1000, not 900, unlike its neighbours. The third stat is a flex row of three
   unbreakable strings ("51.9%" → "38.8%") whose font is clamped at its 2.6rem
   floor through this whole band, so at 950px it measured 224px of content in
   226px of card — it fits, but on two pixels, and only while Fraunces is the
   font actually rendering. Stacking at 1000px skips the band entirely rather
   than trusting that margin to survive a fallback serif. */
@media (max-width: 1000px) { .stats { grid-template-columns: 1fr; } }

/* ===========================================================================
   14 · VISIT TYPES
   =========================================================================== */
.visits { display: grid; grid-template-columns: repeat(3, 1fr); gap: 13px; }
.visit {
  display: grid;
  grid-template-columns: auto 1fr; grid-template-rows: auto auto;
  column-gap: 14px; align-items: center;
  padding: 20px 22px;
  border-radius: var(--r-md);
  background: var(--surface); border: 1px solid var(--line);
  transition: background-color .35s var(--ease), border-color .35s var(--ease), box-shadow .45s var(--ease-out);
}
/* Tinted by the same tone as the tile's own icon (see .visit[data-tone] below)
   rather than the generic lift — the wash is the differentiator, not motion. */
.visit:hover { border-color: var(--euc-wash2); box-shadow: var(--lift-2); }
.visit[data-tone="in-person"]:hover { background: var(--euc-wash); }
.visit[data-tone="remote"]:hover    { background: #E4EEF5; }
.visit[data-tone="attempted"]:hover { background: var(--clay-wash); }
.visit-ico { grid-row: 1 / 3; width: 42px; height: 42px; border-radius: 12px; display: grid; place-items: center; }
.visit[data-tone="in-person"] .visit-ico { background: var(--euc-wash); color: var(--euc); }
.visit[data-tone="remote"]    .visit-ico { background: #E4EEF5; color: #3B7699; }
.visit[data-tone="attempted"] .visit-ico { background: var(--clay-wash); color: var(--clay); }
.visit-l {
  font-family: var(--display); font-weight: 500;
  font-variation-settings: "SOFT" 18, "opsz" 30;
  font-size: 16.5px; letter-spacing: -.014em; color: var(--ink); align-self: end;
}
.visit-s { font-size: 12.8px; color: var(--ink-3); align-self: start; }

.visits-foot { margin-top: 24px; font-size: .94rem; line-height: 1.66; color: var(--ink-3); max-width: 62ch; }

@media (max-width: 880px) { .visits { grid-template-columns: 1fr 1fr; } }
@media (max-width: 560px) { .visits { grid-template-columns: 1fr; } }

/* ===========================================================================
   15 · TRUST PILLARS
   =========================================================================== */
.pillars { display: grid; grid-template-columns: repeat(2, 1fr); gap: 16px; }
.pillar {
  padding: 30px 28px;
  border-radius: var(--r-lg);
  background: var(--surface); border: 1px solid var(--line);
  box-shadow: var(--lift-1);
  transition: border-color .4s var(--ease), transform .5s var(--ease-out), box-shadow .5s var(--ease-out);
}
.pillar:hover { border-color: var(--euc-wash2); transform: translateY(-4px); box-shadow: var(--lift-2); }
.pillar-ico {
  display: grid; place-items: center;
  width: 46px; height: 46px; border-radius: 13px;
  background: var(--euc-wash); color: var(--euc);
  margin-bottom: 20px;
}
.pillar-t {
  font-family: var(--display); font-weight: 500;
  font-variation-settings: "SOFT" 20, "opsz" 40;
  font-size: 19px; letter-spacing: -.018em;
  color: var(--ink); margin-bottom: 11px;
}
.pillar-b { font-size: .94rem; line-height: 1.7; color: var(--ink-2); }
.pillar-b code { color: var(--euc-deep); background: var(--euc-wash); padding: 1px 5px; border-radius: 4px; }
.pillar-link {
  display: inline-flex; align-items: center; gap: 7px;
  margin-top: 15px; font-size: 13.5px; font-weight: 600;
  color: var(--euc); border-bottom: 1px solid var(--euc-wash2);
  padding-bottom: 2px;
  transition: border-color .3s var(--ease), gap .3s var(--ease-out);
}
.pillar-link:hover { border-color: var(--euc); gap: 11px; }

.trust-foot { margin-top: 28px; font-size: .94rem; color: var(--ink-3); line-height: 1.7; }
.trust-foot a { color: var(--euc); border-bottom: 1px solid var(--euc-wash2); }
.trust-foot a:hover { border-bottom-color: var(--euc); }

@media (max-width: 880px) { .pillars { grid-template-columns: 1fr; } }

/* 16 · GLIMPSE (the moved park-bench photo/loop, added 17 Aug 2026) was
   removed 18 Aug 2026 along with the #glimpse section in index.html that
   used it (reviewer direction). .glimpse-frame/.glimpse-plate had no other
   caller; the video it wrapped, "HERO LOOP" below, went with it for the
   same reason: no element in the page carries .hero-vid any more, and
   script.js's own handler for it was removed too. */

/* ===========================================================================
   17 · PRICING
   =========================================================================== */
.plans { display: grid; grid-template-columns: minmax(0,.88fr) minmax(0,1.12fr); gap: 18px; align-items: start; }
.plan {
  position: relative;
  padding: 34px 32px;
  border-radius: var(--r-lg);
  background: var(--surface); border: 1px solid var(--line);
  box-shadow: var(--lift-1);
}
.plan-feature {
  border-color: var(--euc-wash2);
  box-shadow: var(--lift-3);
  background: linear-gradient(172deg, #fff 0%, #F7FCFA 100%);
}
.plan-flag {
  position: absolute; top: -11px; left: 32px;
  padding: 5px 14px; border-radius: var(--r-pill);
  background: var(--euc); color: #fff;
  font-size: 10.5px; font-weight: 600;
  letter-spacing: .12em; text-transform: uppercase;
}
.plan-e {
  font-size: 11.5px; font-weight: 600;
  letter-spacing: .14em; text-transform: uppercase;
  color: var(--euc); margin-bottom: 14px;
}
.plan-p {
  display: flex; align-items: baseline; gap: 3px;
  font-family: var(--display); font-weight: 300;
  font-variation-settings: "SOFT" 18, "opsz" 144;
  font-size: clamp(2.9rem, 4.8vw, 3.8rem);
  line-height: 1; letter-spacing: -.035em; color: var(--ink);
}
.plan-cur { font-size: .46em; color: var(--ink-3); }
.plan-per {
  font-family: var(--body); font-size: 13.5px; font-weight: 500;
  letter-spacing: 0; color: var(--ink-3); margin-left: 6px;
}
.plan-sub { font-size: 13.5px; color: var(--ink-3); margin-top: 9px; }
.plan-b { font-size: .95rem; line-height: 1.68; color: var(--ink-2); margin-top: 18px; }

.plan-list { display: grid; gap: 9px; margin-top: 18px; }
.plan-list li { position: relative; padding-left: 24px; font-size: .91rem; color: var(--ink-2); }
.plan-list li::before {
  content: ""; position: absolute; left: 0; top: 6px;
  width: 14px; height: 14px; border-radius: 50%;
  background: var(--euc-wash) url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='9' height='9' viewBox='0 0 24 24' fill='none'%3E%3Cpath d='m5 12 4.5 4.5L19 7' stroke='%23217A6A' stroke-width='3.4' stroke-linecap='round' stroke-linejoin='round'/%3E%3C/svg%3E") center / 9px no-repeat;
}

.bands { margin-top: 22px; font-size: 14.5px; }
.bands th, .bands td { padding: 11px 12px; text-align: left; border-bottom: 1px solid var(--line); }
.bands thead th {
  font-size: 10.5px; font-weight: 600;
  letter-spacing: .12em; text-transform: uppercase;
  color: var(--ink-3); border-bottom-color: var(--line-2);
}
.bands tbody th { font-weight: 600; color: var(--ink); }
.bands tbody td { color: var(--ink-2); font-variant-numeric: tabular-nums; }
.bands tbody tr:last-child th, .bands tbody tr:last-child td { border-bottom: 0; color: var(--euc-deep); }

.plan-cta { width: 100%; margin-top: 24px; }
.plans-foot { margin-top: 24px; font-size: .92rem; line-height: 1.68; color: var(--ink-3); max-width: 62ch; }

@media (max-width: 900px) { .plans { grid-template-columns: 1fr; } }

/* ===========================================================================
   17b · FAQ — native <details>/<summary>, added 17 Aug 2026. No JS: the
   browser handles keyboard, focus and the open/close state for free.
   =========================================================================== */
.faq-list { display: grid; gap: 10px; margin-top: 8px; }
.faq-item {
  border: 1px solid var(--line);
  border-radius: var(--r-md);
  background: var(--surface);
  box-shadow: var(--lift-1);
  overflow: hidden;
}
.faq-item summary {
  list-style: none;
  cursor: pointer;
  display: flex; align-items: center; justify-content: space-between; gap: 16px;
  padding: 18px 22px;
  font-family: var(--display);
  font-weight: 500;
  font-variation-settings: "SOFT" 18, "opsz" 40;
  font-size: 1.03rem;
  letter-spacing: -.008em;
  color: var(--ink);
  transition: color .25s var(--ease);
}
.faq-item summary::-webkit-details-marker { display: none; }
.faq-item summary::after {
  content: "";
  flex: none;
  width: 10px; height: 10px;
  border-right: 2px solid var(--euc);
  border-bottom: 2px solid var(--euc);
  transform: rotate(45deg);
  transition: transform .3s var(--ease-out), margin .3s var(--ease-out);
}
.faq-item[open] summary::after { transform: rotate(-135deg); margin-top: 6px; }
.faq-item summary:hover { color: var(--euc-deep); }
.faq-item summary:focus-visible { outline: 2px solid var(--euc); outline-offset: -3px; border-radius: 8px; }
.faq-a { padding: 0 22px 20px; }
.faq-a p { font-size: .96rem; line-height: 1.68; color: var(--ink-2); max-width: 62ch; }
.faq-a p + p { margin-top: .9em; }
.faq-a a { color: var(--euc); border-bottom: 1px solid var(--euc-wash2); }
.faq-a a:hover { border-bottom-color: var(--euc); }
.faq-a code { color: var(--euc-deep); background: var(--euc-wash); padding: 1px 5px; border-radius: 4px; }

@media (max-width: 600px) {
  .faq-item summary { padding: 16px 18px; font-size: 1rem; }
  .faq-a { padding: 0 18px 18px; }
}

/* ===========================================================================
   18 · CLOSING
   =========================================================================== */
.band-close {
  position: relative; text-align: center; overflow: hidden;
  background:
    radial-gradient(64% 56% at 50% 108%, var(--euc-wash2) 0%, transparent 68%),
    radial-gradient(44% 38% at 86% -8%, var(--gold-wash) 0%, transparent 66%),
    var(--paper);
  border-top: 1px solid var(--line);
}
.close .eyebrow { justify-content: center; }
.close-lede {
  font-size: clamp(1rem, 1.45vw, 1.12rem);
  line-height: 1.68; color: var(--ink-2);
  max-width: 52ch; margin: 24px auto 0;
}
.close-actions { display: flex; flex-wrap: wrap; justify-content: center; gap: 12px; margin-top: 32px; }
.close-foot { margin-top: 24px; font-size: 13.5px; color: var(--ink-3); }
.close-foot a { color: var(--euc); border-bottom: 1px solid var(--euc-wash2); }
.close-who {
  display: flex; flex-wrap: wrap; justify-content: center; gap: 8px 10px;
  margin-top: 36px; padding-top: 28px; border-top: 1px solid var(--line);
}
.close-who li {
  padding: 7px 15px; border-radius: var(--r-pill);
  border: 1px solid var(--line-2); background: rgba(255,255,255,.6);
  font-size: 12.5px; color: var(--ink-3);
}

/* ===========================================================================
   19 · FOOTER — dark moment #2
   =========================================================================== */
.foot {
  background: var(--deep); color: var(--on-deep);
  padding: clamp(52px, 6.5vw, 78px) 0 32px;
}
.foot .brand { color: var(--on-deep); }
.foot .brand-glyph { color: var(--euc-light); }
.foot-grid { display: grid; grid-template-columns: minmax(0,1.1fr) minmax(0,2fr); gap: clamp(34px, 5vw, 60px); }
.brand-foot { margin-bottom: 16px; }
.foot-tag { font-size: .93rem; line-height: 1.66; color: var(--on-deep-3); max-width: 34ch; }
.foot-by { margin-top: 13px; font-size: 13px; color: var(--on-deep-3); }
.foot-by a { color: var(--euc-light); border-bottom: 1px solid rgba(79,192,166,.34); }

.foot-cols { display: grid; grid-template-columns: repeat(4, 1fr); gap: 24px; }
.foot-col { display: grid; align-content: start; gap: 10px; }
.foot-h {
  font-size: 10.5px; font-weight: 600;
  letter-spacing: .14em; text-transform: uppercase;
  color: var(--euc-light); margin-bottom: 3px;
}
.foot-col a {
  font-size: 14px; color: var(--on-deep-3); width: fit-content;
  padding: 9px 0; min-height: 24px;   /* WCAG 2.2 SC 2.5.8 — was 23px tall */
  display: flex; align-items: center;
  transition: color .25s var(--ease);
}
.foot-col a:hover { color: var(--on-deep); }

.foot-bottom {
  margin-top: clamp(40px, 5.5vw, 62px); padding-top: 22px;
  border-top: 1px solid rgba(237,244,241,.12);
  display: flex; flex-wrap: wrap; gap: 12px 28px;
  justify-content: space-between; align-items: baseline;
  font-size: 12.5px; color: var(--on-deep-3);
}
.foot-ack { max-width: 60ch; line-height: 1.6; }

@media (max-width: 880px) {
  .foot-grid { grid-template-columns: 1fr; }
  .foot-cols { grid-template-columns: 1fr 1fr; }
}
/* Every other multi-column grid on the page reaches a single column at its
   narrow breakpoint; .foot-cols stopped at two and stayed there to 320px,
   leaving ~130px per column for links like "Desktop app — Windows" that have
   no nowrap and so wrapped to three ragged lines each. */
@media (max-width: 560px) {
  .foot-cols { grid-template-columns: 1fr; gap: 28px; }
}

/* ===========================================================================
   20 · REVEAL
   =========================================================================== */
.reveal {
  opacity: 0; transform: translateY(20px);
  transition: opacity .8s var(--ease), transform .95s var(--ease-out);
  transition-delay: calc(var(--d, 0) * 90ms);
}
.reveal.is-in { opacity: 1; transform: none; }
.no-js .reveal { opacity: 1; transform: none; }

/* ===========================================================================
   21 · LEGACY TOKEN ALIASES — privacy / terms / data-residency
   ---------------------------------------------------------------------------
   Those three pages carry their own inline <style> block written against the
   PREVIOUS stylesheet's variable names. They are legally reviewed documents
   and aren't being redesigned in this pass, so the old names are re-pointed at
   the new palette here rather than editing three files of settled copy.
   =========================================================================== */
:root {
  --cream:       var(--paper);
  --cream-deep:  var(--paper-2);
  --sand:        var(--line);
  --warmblack:   var(--ink);
  --warm-gray:   var(--ink-3);
  --bark:        var(--euc);
  --bark-soft:   var(--euc-wash);
  --earth:       var(--euc-deep);
  --font-display: var(--display);
  --font-body:    var(--body);
  --type-small:   13.5px;
  --type-prose:   17px;
}

/* ===========================================================================
   22 · REDUCED MOTION
   =========================================================================== */
@media (prefers-reduced-motion: reduce) {
  html { scroll-behavior: auto; }
  *, *::before, *::after {
    animation-duration: .001ms !important;
    animation-iteration-count: 1 !important;
    transition-duration: .001ms !important;
  }
  .reveal { opacity: 1; transform: none; }
  .grain { display: none; }
  .sectors-track { animation: none; }
  /* Belt and braces: the module already bails on prefers-reduced-motion before
     it touches WebGL, so this only matters if the setting is changed after
     load. The wash underneath is what remains, which is the correct still. */
  .hero-water { display: none; }
  .hero::after { display: none; }
}

/* ===========================================================================
   23 · PRINT
   =========================================================================== */
@media print {
  .nav, .grain, .sectors, .hero-visual, .hero-water { display: none !important; }
  .hero::after { display: none !important; }
  /* The deep hero prints on white paper, so every inverted colour has to come
     back. These need the same .hero.is-deep specificity they are undoing —
     `.display { color: #000 }` below is (0,1,0) and loses to
     `.hero.is-deep .display` (0,2,1), which would put cream type on a white
     page and print a blank hero. */
  .hero.is-deep { background: #fff !important; }
  .hero.is-deep .display,
  .hero.is-deep .display em,
  .hero.is-deep .hero-facts b { color: #000 !important; }
  .hero.is-deep .hero-lede,
  .hero.is-deep .hero-facts li { color: #222 !important; }
  .hero.is-deep .eyebrow,
  .hero.is-deep .hero-facts svg { color: var(--euc-deep) !important; }
  .hero.is-deep .hero-facts { border-top-color: #bbb !important; }
  body { background: #fff; color: #000; }
  .band, .hero { padding: 24px 0; background: #fff !important; }
  .display, .h2, .h2-light, .pull-q { color: #000; }
  .reveal { opacity: 1; transform: none; }
}

/* ===========================================================================
   24 · TRAINING MODULE — training.html, added 19 Aug 2026
   ---------------------------------------------------------------------------
   Every rule below is built ONLY from tokens already declared in §1 — no new
   colour, no new font. `.tr-` prefixes the lot so nothing here can collide
   with (or accidentally restyle) a class from the rest of the site.

   Colour logic reused from elsewhere in this file, kept deliberately
   consistent rather than invented per-component:
     --euc / --euc-wash   "this is the good example / the right answer"
                           (already how .ticks and .compare-now read)
     --clay / --clay-wash "this is the failure mode / the absence"
                           (already how .compare-then reads)
     --gold / --gold-wash "pay attention, have a go, a caution"
                           (already how .pip and .plan-flag read)
   =========================================================================== */

/* — task / reveal tags, the "have a go" and "reveal" markers — */
.tr-task {
  display: inline-flex; align-items: center; gap: 8px;
  font-size: 11.5px; font-weight: 600; letter-spacing: .13em; text-transform: uppercase;
  color: var(--gold-ink); background: var(--gold-wash);
  padding: 6px 14px; border-radius: var(--r-pill);
  margin: 38px 0 16px;
}
.tr-task svg { width: 12px; height: 12px; flex: none; }
.tr-revealtag {
  display: inline-flex; align-items: center; gap: 8px;
  font-size: 11.5px; font-weight: 600; letter-spacing: .13em; text-transform: uppercase;
  color: var(--ink-3); background: var(--paper-2); border: 1px solid var(--line);
  padding: 6px 14px; border-radius: var(--r-pill);
  margin-bottom: 16px;
}

/* — numbered principle blocks — */
.tr-principle {
  position: relative; margin: 30px 0; padding-left: 26px;
  border-left: 3px solid var(--euc-wash2);
}
.tr-principle-n {
  font-size: 11px; font-weight: 700; letter-spacing: .15em; text-transform: uppercase;
  color: var(--ink-3);
}
.tr-principle-t {
  font-family: var(--display); font-weight: 500;
  font-variation-settings: "SOFT" 20, "opsz" 40;
  font-size: 1.28rem; letter-spacing: -.016em; line-height: 1.24;
  color: var(--ink); margin: 5px 0 11px;
}
.tr-principle p { font-size: .97rem; line-height: 1.72; color: var(--ink-2); max-width: 62ch; }
.tr-principle p + p { margin-top: .85em; }

/* — aside callouts (amber = caution/aside, teal = the point that matters) — */
.tr-aside { border-radius: var(--r-md); padding: 20px 24px; margin: 22px 0; }
.tr-aside p { font-size: .95rem; line-height: 1.7; color: var(--ink-2); margin: 0; max-width: 64ch; }
.tr-aside p + p { margin-top: .85em; }
.tr-aside-label {
  display: block; font-size: 11px; font-weight: 700;
  letter-spacing: .13em; text-transform: uppercase; margin-bottom: 9px;
}
.tr-aside--amber { background: var(--gold-wash); }
.tr-aside--amber .tr-aside-label { color: var(--gold-ink); }
.tr-aside--teal { background: var(--euc-wash); }
.tr-aside--teal .tr-aside-label { color: var(--euc-deep); }

/* — pull-style callout inside an exercise reveal — */
.tr-pullquote {
  font-family: var(--display); font-style: italic; font-weight: 400;
  font-variation-settings: "SOFT" 40, "opsz" 40;
  font-size: 1.2rem; line-height: 1.5; color: var(--euc-deep);
  margin: 26px 0; padding-left: 22px; border-left: 3px solid var(--euc);
}

/* — choice buttons: poll / trick question / quiz options — */
.tr-choice-group { display: grid; gap: 10px; margin: 16px 0; }
.tr-choice-group--2up { grid-template-columns: 1fr 1fr; }
@media (max-width: 560px) { .tr-choice-group--2up { grid-template-columns: 1fr; } }
.tr-choice {
  display: block; width: 100%; text-align: left; font: inherit;
  padding: 15px 19px; border-radius: var(--r-md);
  border: 1px solid var(--line); background: var(--surface);
  font-size: .98rem; line-height: 1.5; color: var(--ink-2); cursor: pointer;
  transition: border-color .25s var(--ease), background-color .25s var(--ease), color .25s var(--ease);
}
.tr-choice:hover { border-color: var(--euc); color: var(--ink); }
.tr-choice[aria-pressed="true"] { border-color: var(--euc); background: var(--euc-wash); color: var(--ink); }
.tr-choice.is-correct { border-color: var(--euc); background: var(--euc-wash); color: var(--ink); }
.tr-choice.is-incorrect { border-color: var(--clay); background: var(--clay-wash); color: var(--ink); }
.tr-choice:disabled { cursor: default; }
.tr-choice:disabled:hover { color: var(--ink-2); }

/* — sort exercise (I saw it / They said it / I concluded it) — */
.tr-sort { display: grid; gap: 10px; margin: 18px 0; }
.tr-stmt {
  display: flex; flex-wrap: wrap; align-items: center; justify-content: space-between;
  gap: 12px; padding: 16px 18px;
  border-radius: var(--r-md); border: 1px solid var(--line); background: var(--surface);
  transition: border-color .3s var(--ease), background-color .3s var(--ease);
}
.tr-stmt-txt { flex: 1 1 240px; font-size: .97rem; line-height: 1.5; color: var(--ink); }
.tr-stmt-opts { display: flex; gap: 6px; flex-wrap: wrap; }
.tr-chip {
  font: inherit; font-size: 12.5px; font-weight: 600;
  padding: 8px 13px; border-radius: var(--r-pill);
  border: 1px solid var(--line-2); background: transparent; color: var(--ink-2);
  cursor: pointer; transition: border-color .2s var(--ease), background-color .2s var(--ease), color .2s var(--ease);
}
.tr-chip:hover { border-color: var(--euc); color: var(--euc-deep); }
.tr-chip[aria-pressed="true"] { background: var(--euc); border-color: var(--euc); color: #fff; }
.tr-stmt.is-right { border-color: var(--euc); background: var(--euc-wash); }
.tr-stmt.is-wrong { border-color: var(--clay); background: var(--clay-wash); }
.tr-stmt-why {
  flex-basis: 100%; font-size: .9rem; line-height: 1.62; color: var(--ink-2);
  padding-top: 13px; margin-top: 2px; border-top: 1px dashed var(--line-2);
  display: none;
}
.tr-stmt.is-done .tr-stmt-why { display: block; }
.tr-sort-foot { display: flex; align-items: center; gap: 14px; flex-wrap: wrap; margin-top: 16px; }
.tr-sort-score { font-size: .92rem; color: var(--ink-2); }

/* — "cut it down" notebox — */
.tr-notebox {
  background: var(--surface); border: 1px solid var(--line); border-radius: var(--r-lg);
  padding: 24px 26px; margin: 18px 0; line-height: 2; box-shadow: var(--lift-1);
}
.tr-sen {
  font: inherit; font-size: .97rem; color: var(--ink-2);
  background: none; border: 0; padding: 2px 4px; margin: 0; border-radius: 4px;
  cursor: pointer; text-align: left; display: inline;
  transition: background-color .15s var(--ease), color .15s var(--ease);
}
.tr-sen:hover { background: var(--gold-wash); }
.tr-sen.is-cut { text-decoration: line-through; color: var(--ink-3); opacity: .55; }
.tr-counter { display: flex; gap: 24px; font-size: .88rem; color: var(--ink-2); margin-top: 14px; }
.tr-counter b { font-family: var(--display); font-size: 1.2rem; font-weight: 600; color: var(--euc); }

/* — pick-pair exercise ("same visit, two records") — */
.tr-pair { display: grid; grid-template-columns: 1fr 1fr; gap: 14px; margin: 20px 0; }
@media (max-width: 640px) { .tr-pair { grid-template-columns: 1fr; } }
.tr-pick {
  display: block; width: 100%; text-align: left; font: inherit;
  background: var(--surface); border: 1px solid var(--line); border-radius: var(--r-md);
  padding: 22px; cursor: pointer; transition: border-color .25s var(--ease), box-shadow .25s var(--ease);
}
.tr-pick:hover { border-color: var(--euc); }
.tr-pick[aria-pressed="true"] { border-color: var(--euc); box-shadow: 0 0 0 2px var(--euc-wash2); }
.tr-pick-label {
  font-size: 11px; font-weight: 700; letter-spacing: .12em; text-transform: uppercase;
  color: var(--ink-3); margin-bottom: 10px;
}
.tr-pick p { font-size: .92rem; line-height: 1.62; color: var(--ink-2); margin: 0; }

/* — verdict / description / record tiers (describe-exercise reveal) — */
.tr-tiers { display: grid; gap: 18px; margin: 18px 0; }
.tr-tier { display: flex; gap: 14px; padding-bottom: 18px; border-bottom: 1px solid var(--line); }
.tr-tier:last-child { border-bottom: 0; padding-bottom: 0; }
.tr-tier-badge {
  flex: none; font-size: 10.5px; font-weight: 700; letter-spacing: .1em; text-transform: uppercase;
  padding: 5px 10px; border-radius: 6px; margin-top: 2px;
}
.tr-tier-badge--verdict { background: var(--gold-wash); color: var(--gold-ink); }
.tr-tier-badge--desc    { background: var(--paper-2);   color: var(--ink-2); }
.tr-tier-badge--record  { background: var(--euc-wash);  color: var(--euc-deep); }
.tr-tier-q { font-family: var(--display); font-style: italic; font-weight: 400; font-size: 1.03rem; color: var(--ink); }
.tr-tier-n { font-size: .9rem; line-height: 1.62; color: var(--ink-2); margin-top: 5px; }

/* — before / after photographs — */
.tr-photos { display: grid; grid-template-columns: 1fr 1fr; gap: 16px; margin: 22px 0; }
@media (max-width: 640px) { .tr-photos { grid-template-columns: 1fr; } }
.tr-photo {
  border: 1px solid var(--line); border-radius: var(--r-lg); overflow: hidden;
  background: var(--surface); box-shadow: var(--lift-1); margin: 0;
}
.tr-photo img { width: 100%; height: auto; display: block; }
.tr-photo figcaption { padding: 11px 16px; font-size: .85rem; color: var(--ink-3); text-align: center; }

/* — jargon / plain / what-happened rows — */
.tr-jrows { display: grid; gap: 14px; margin: 22px 0; }
.tr-jrow { border: 1px solid var(--line); border-radius: var(--r-md); padding: 16px 19px; background: var(--surface); }
.tr-jline { display: flex; gap: 12px; padding: 5px 0; font-size: .93rem; line-height: 1.5; color: var(--ink-2); }
.tr-jb {
  flex: 0 0 108px; font-size: 10.5px; font-weight: 700; letter-spacing: .09em;
  text-transform: uppercase; color: var(--ink-3); padding-top: 3px;
}
.tr-jline--what { color: var(--ink); font-weight: 500; }
.tr-jline--what .tr-jb { color: var(--euc-deep); }

/* — testimony blockquote (Kaylah) — */
.tr-testimony {
  background: var(--surface); border: 1px solid var(--line); border-left: 3px solid var(--euc);
  border-radius: 0 var(--r-lg) var(--r-lg) 0; padding: 30px 32px; margin: 24px 0;
  box-shadow: var(--lift-1);
}
.tr-testimony p {
  font-family: var(--display); font-weight: 400;
  font-variation-settings: "SOFT" 16, "opsz" 30;
  font-size: 1.16rem; line-height: 1.58; color: var(--ink); margin: 0 0 16px;
}
.tr-testimony p:last-of-type { margin-bottom: 0; }
.tr-testimony-attrib {
  display: block; margin-top: 20px; padding-top: 15px; border-top: 1px solid var(--line);
  font-family: var(--body); font-size: .82rem; font-style: italic; color: var(--ink-3);
}

/* — "what's missing" list: a dash marker in --clay, deliberately not a
   checkmark (.ticks), because the point of this list is absence — */
.tr-list-x { display: grid; gap: 14px; margin-top: 22px; }
.tr-list-x li { position: relative; padding-left: 30px; font-size: .94rem; line-height: 1.64; color: var(--ink-2); }
.tr-list-x li::before {
  content: ""; position: absolute; left: 0; top: 5px;
  width: 18px; height: 18px; border-radius: 50%;
  background: var(--clay-wash) url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='10' height='10' viewBox='0 0 24 24'%3E%3Cpath d='M5 12h14' stroke='%23A05839' stroke-width='3.4' stroke-linecap='round'/%3E%3C/svg%3E") center / 10px no-repeat;
}

/* — filecard mockup reuses .jr-mock / .jr-mock-head / .jr-mock-badge /
   .jr-mock-label / .jr-mock-draft as-is; no new rules needed. — */

/* — reflection scale ("four questions before you start") — */
.tr-qlabel { font-size: 1.02rem; color: var(--ink); margin-bottom: 10px; font-weight: 500; }
.tr-scale { display: flex; gap: 8px; margin: 12px 0 6px; flex-wrap: wrap; }
.tr-scale button {
  flex: 1; min-width: 50px; font: inherit; font-size: .93rem;
  padding: 12px 4px; border-radius: var(--r-md);
  border: 1px solid var(--line-2); background: var(--surface); color: var(--ink-2);
  cursor: pointer; transition: border-color .2s var(--ease), background-color .2s var(--ease), color .2s var(--ease);
}
.tr-scale button:hover { border-color: var(--euc); }
.tr-scale button[aria-pressed="true"] { background: var(--euc); border-color: var(--euc); color: #fff; font-weight: 600; }
.tr-scale-ends { display: flex; justify-content: space-between; font-size: .83rem; color: var(--ink-3); margin-bottom: 30px; }

/* — free-text reflection (never submitted anywhere; the exercise is the
   act of writing it, same as the source course) — */
.tr-textarea {
  width: 100%; font: inherit; font-size: .98rem; line-height: 1.5;
  padding: 15px 17px; border-radius: var(--r-md);
  border: 1px solid var(--line-2); background: var(--surface); color: var(--ink);
  resize: vertical; min-height: 100px;
}
.tr-textarea:focus-visible { outline: 2px solid var(--euc); outline-offset: 1px; }

/* Step headings take focus programmatically on every step change, so a screen
   reader announces the new step instead of stranding the user at the bottom of
   the previous one. They are NOT interactive, so they must not paint the global
   focus ring: Chrome matched :focus-visible on the very first load and drew a
   box around "Making quality records" that vanished on the first click, which
   read as a rendering fault. Suppressing it here costs no accessibility, since
   every real control keeps its ring from the global rule. */
[data-step-heading]:focus,
[data-step-heading]:focus-visible { outline: none; }
.tr-hint { font-size: .84rem; color: var(--ink-3); margin-top: 8px; }

/* — practice quiz — deliberately NOT the same shape as the source course's
   graded test (no running % score, no 80%-to-pass gate, no submit-all step):
   each question reveals its own answer the moment you pick one, because
   this is a public page and the app's own graded assessment keeps its
   answer key server-side. See training.html's top comment. — */
.tr-quiz { display: grid; gap: 30px; margin-top: 10px; }
.tr-quiz-q { padding-bottom: 28px; border-bottom: 1px solid var(--line); }
.tr-quiz-q:last-child { border-bottom: 0; padding-bottom: 0; }
.tr-quiz-n {
  font-size: 11px; font-weight: 700; letter-spacing: .13em; text-transform: uppercase;
  color: var(--euc); margin-bottom: 11px;
}
.tr-quiz-text {
  font-family: var(--display); font-weight: 500;
  font-variation-settings: "SOFT" 18, "opsz" 30;
  font-size: 1.1rem; line-height: 1.42; color: var(--ink); margin-bottom: 16px;
}
.tr-quiz-opts { display: grid; gap: 9px; }
.tr-quiz-fb {
  margin-top: 15px; padding: 15px 17px; border-radius: var(--r-md);
  font-size: .92rem; line-height: 1.62; display: none;
}
.tr-quiz-fb.is-shown { display: block; }
.tr-quiz-fb--right { background: var(--euc-wash); color: var(--euc-deep); }
.tr-quiz-fb--wrong { background: var(--clay-wash); color: var(--ink-2); }

/* — in-page contents rail — */
.tr-toc { display: flex; flex-wrap: wrap; gap: 8px; margin-top: 30px; padding-top: 22px; border-top: 1px solid rgba(16,49,41,.14); }
.tr-toc a {
  font-size: 12.5px; font-weight: 600; padding: 8px 15px; border-radius: var(--r-pill);
  border: 1px solid var(--line-2); background: rgba(255,255,255,.6); color: var(--ink-2);
  transition: border-color .2s var(--ease), color .2s var(--ease), background-color .2s var(--ease);
}
.tr-toc a:hover { border-color: var(--euc); color: var(--euc-deep); background: #fff; }

/* — meta row under the training intro (time / cost / no login) — */
.tr-meta { display: flex; flex-wrap: wrap; gap: 8px 22px; margin-top: 26px; font-size: .87rem; color: var(--ink-3); }
.tr-meta b { color: var(--ink-2); font-weight: 600; }

@media (max-width: 900px) {
  .tr-principle p     { font-size: 1.02rem; line-height: 1.7; }
  .tr-aside p         { font-size: 1rem; line-height: 1.68; }
  .tr-choice          { font-size: 1.02rem; }
  .tr-stmt-txt        { font-size: 1.02rem; }
  .tr-chip            { font-size: 13.5px; padding: 9px 14px; }
  .tr-sen             { font-size: 1.02rem; }
  .tr-pick p          { font-size: .98rem; }
  .tr-tier-n          { font-size: .96rem; }
  .tr-jline           { font-size: .98rem; }
  .tr-testimony p     { font-size: 1.1rem; }
  .tr-list-x li       { font-size: 1rem; }
  .tr-quiz-text       { font-size: 1.08rem; }
  .tr-quiz-fb         { font-size: .98rem; }
  .tr-task, .tr-revealtag { font-size: 12.5px; }
  .tr-toc a           { font-size: 13.5px; }
  .tr-meta            { font-size: 14px; }
}

/* ══════════════════════════════════════════════════════════════════════
   MOBILE TYPOGRAPHY
   ──────────────────────────────────────────────────────────────────────
   The page was sized desktop-first and the small-screen rules only ever
   changed layout, never type. Measured on a real 390px viewport, reading
   copy landed at 14.7–15.4px and supporting copy at 11.5–12.8px — the
   hero's own proof points were 12.8px and the feature rail 12.4px.

   Phones are held closer than a laptop but the text is physically smaller,
   so body copy needs to go UP here, not down. Floors applied below:
     reading copy   >= 16.5px
     supporting     >= 14px
     tracked labels >= 12.5px   (uppercase + letter-spacing reads larger)
   ══════════════════════════════════════════════════════════════════════ */

@media (max-width: 900px) {
  /* Body / reading copy — was .93–.96rem, i.e. under 16px. */
  .prose            { font-size: 1.05rem; line-height: 1.7; }
  .sec-lede         { font-size: 1.07rem; line-height: 1.66; }
  .step-b           { font-size: 1.02rem; line-height: 1.66; }
  .stat-b           { font-size: 1rem;    line-height: 1.62; }
  .plan-b           { font-size: 1.02rem; line-height: 1.64; }
  .compare-body     { font-size: 1.02rem; line-height: 1.6; }
  .evidence-note p,
  .band-wash p      { font-size: 1.02rem; }

  /* Supporting copy — small by design, but not this small. */
  .hero-facts li    { font-size: 14.3px; line-height: 1.5; }
  .pillar-link      { font-size: 14.6px; }
  .plan-sub         { font-size: 14.2px; }
  .plan-per         { font-size: 14.2px; }
  .sectors span     { font-size: 13.4px; }

  /* Tracked uppercase labels — legible at a smaller size, but 11px is not it. */
  .eyebrow          { font-size: 12.5px; }
  .compare-h        { font-size: 12.2px; }

  /* Caught on a second sweep at 390px — all below the 12.5px label floor. */
  .plan-flag        { font-size: 12px; }
  .pull-c           { font-size: 13px; }
  .compare-note     { font-size: 13.8px; line-height: 1.55; }

  /* Third sweep, selectors confirmed against the live DOM rather than guessed:
     the pricing table is table.bands, and the footer column headings are
     .foot-h — both were rendering at 10.5px. */
  .bands thead th   { font-size: 13px; letter-spacing: .09em; }  /* .bands thead th beats .bands th */
  .foot-h           { font-size: 12.5px; }
  .foot-ack         { font-size: 13.5px; line-height: 1.6; }
  .plan-e           { font-size: 13px; }
  .visit-s          { font-size: 14px; line-height: 1.5; }

  /* .jr-mock (walkthrough note-card, added 17 Aug 2026) — same floors as
     the rest of this sweep: reading copy >= 16.5px, tracked labels >= 12.5px. */
  .jr-mock-q        { font-size: 1.05rem; line-height: 1.5; }
  .jr-mock-a        { font-size: 1.02rem; line-height: 1.6; }
  .jr-mock-draft    { font-size: 1.03rem; line-height: 1.62; }
  .jr-mock-label    { font-size: 12.5px; }

  /* .flow-* (walkthrough spine, restructured 17 Aug 2026) — same floors. */
  .flow-p           { font-size: 1.04rem; line-height: 1.66; }
  .flow-pain        { font-size: 1rem; line-height: 1.55; }
  .flow-label       { font-size: 12.5px; }
  .flow-aside       { font-size: .95rem; line-height: 1.6; }
}

@media (max-width: 600px) {
  /* Narrower still: hold the reading floor and give the mock more room —
     the section is stacked here, so the device can afford to be wider. */
  .prose            { font-size: 1.06rem; }
  .sec-lede         { font-size: 1.06rem; }
  .hero-lede        { font-size: 1.04rem; line-height: 1.58; }

  /* Long uppercase eyebrows wrap to three lines at this width and start to
     read as a paragraph; tighten the tracking rather than shrink the type. */
  .eyebrow          { letter-spacing: .1em; }
}

/* "HERO LOOP" (.hero-vid, the glimpse loop's video element) was removed
   18 Aug 2026 along with the #glimpse section in index.html: no element in
   the page carries that class any more, so the rule had nothing left to
   select. See the "16 · GLIMPSE" note above for the full removal. */

/* ===========================================================================
   25 · TRAINING STEPPER — training.html, rebuilt 19 Aug 2026
   ---------------------------------------------------------------------------
   Turns the course from one long scroll into one step at a time: a sticky
   progress bar, a Back/Next control on every step, and a reset control.
   Same rule as §24: every colour and size below is a token already declared
   in §1, nothing new. `.tr-step` visibility is driven from script, keyed off
   the same `.no-js` class the rest of the file already uses (§20/22), so a
   page with scripting disabled falls back to every step shown at once,
   exactly like the plain scrolling document this replaced.
   =========================================================================== */

/* Measured against the live nav (§6): nav-inner's 16px padding either side
   of its tallest row (the 42px mobile toggle) renders 75-76.5px across the
   breakpoints actually in use here. 78px covers both with a small buffer,
   used both to seat the progress bar right under the fixed nav (rather than
   in normal flow at the very top of the page, behind it) and to keep a
   scrolled-to step's own heading clear of the fixed nav + sticky bar. */
:root { --tr-nav-h: 78px; }

/* — default (no-js) baseline: every step is a normal, visible block — */
.tr-step { display: block; scroll-margin-top: calc(var(--tr-nav-h) + 76px); }

/* — once JS has removed .no-js, only the active step is shown — */
html:not(.no-js) .tr-step { display: none; }
html:not(.no-js) .tr-step.is-active {
  display: block;
  animation: tr-step-in .5s var(--ease-out);
}
@keyframes tr-step-in {
  from { opacity: 0; transform: translateY(14px); }
  to   { opacity: 1; transform: translateY(0); }
}

/* — stepper chrome (progress bar, Back/Next, reset) only makes sense once
     scripting can actually move between steps — */
.no-js .tr-progress,
.no-js .tr-stepctl { display: none; }

/* — progress bar, sticky under the fixed nav — */
.tr-progress {
  position: sticky; top: var(--tr-nav-h); z-index: 40;
  margin-top: var(--tr-nav-h);
  background: rgba(251,249,245,.9);
  backdrop-filter: blur(18px) saturate(1.4);
  -webkit-backdrop-filter: blur(18px) saturate(1.4);
  border-bottom: 1px solid var(--line);
  padding: 13px var(--gut) 12px;
}
.tr-progress-top {
  max-width: var(--wrap); margin-inline: auto;
  display: flex; align-items: center; justify-content: space-between;
  gap: 12px; flex-wrap: wrap;
}
.tr-progress-group {
  font-size: 11.5px; font-weight: 700; letter-spacing: .12em; text-transform: uppercase;
  color: var(--euc);
}
.tr-progress-count { font-size: 12.5px; color: var(--ink-3); flex: 1 1 auto; }
.tr-progress-reset {
  font: inherit; font-size: 12.5px; font-weight: 600;
  background: none; border: 0; padding: 4px 0; cursor: pointer;
  color: var(--ink-3); border-bottom: 1px solid var(--line-2);
  transition: color .2s var(--ease), border-color .2s var(--ease);
}
.tr-progress-reset:hover { color: var(--euc-deep); border-color: var(--euc); }
.tr-progress-track {
  max-width: var(--wrap); margin: 10px auto 0;
  height: 3px; border-radius: 3px; background: var(--line); overflow: hidden;
}
.tr-progress-fill {
  display: block; height: 100%; border-radius: 3px; background: var(--euc);
  transition: width .4s var(--ease);
}

/* — Back / Next control row, closing every step's content — */
.tr-stepctl {
  display: flex; align-items: center; justify-content: space-between;
  gap: 16px; margin-top: 44px; padding-top: 26px; border-top: 1px solid var(--line);
  /* Breathing room under Back/Next (founder, 19 Aug: "another 20mm of space
     under the bottom buttons"). On a phone the control otherwise sits close
     to the bottom edge, where the browser chrome and the home indicator
     crowd it; on desktop it keeps the eye from running straight from the
     button into the next band. mm is a real CSS unit (1mm = 96/25.4 px), so
     this is the asked-for measure rather than an approximation of it. */
  padding-bottom: 20mm;
}
.tr-stepctl--close { justify-content: flex-start; }
.tr-stepctl-back {
  display: inline-flex; align-items: center; gap: 6px;
  font: inherit; font-size: 14.5px; font-weight: 600;
  background: none; border: 0; padding: 8px 4px; cursor: pointer;
  color: var(--euc-deep);
  transition: color .2s var(--ease), transform .25s var(--ease-out);
}
.tr-stepctl-back:hover { color: var(--euc); transform: translateX(-2px); }
.tr-stepctl-next[disabled] {
  opacity: .4; cursor: not-allowed; pointer-events: none; box-shadow: none; transform: none;
}

/* — practice quiz results screen — */
.tr-results { display: grid; gap: 16px; margin-top: 8px; }
.tr-result {
  border: 1px solid var(--line); border-radius: var(--r-md); padding: 18px 20px;
  background: var(--surface);
}
.tr-result-head { display: flex; align-items: flex-start; gap: 10px; margin-bottom: 8px; }
.tr-result-mark {
  flex: none; width: 22px; height: 22px; border-radius: 50%;
  display: grid; place-items: center; margin-top: 1px;
}
.tr-result.is-right .tr-result-mark { background: var(--euc); }
.tr-result.is-wrong .tr-result-mark { background: var(--clay); }
.tr-result-q {
  font-family: var(--display); font-weight: 500;
  font-variation-settings: "SOFT" 18, "opsz" 30;
  font-size: 1.02rem; line-height: 1.4; color: var(--ink);
}
.tr-result-your { font-size: .88rem; color: var(--ink-3); margin: 0 0 4px; }
.tr-result-your b { color: var(--ink-2); font-weight: 600; }
.tr-result-fb { font-size: .92rem; line-height: 1.6; color: var(--ink-2); }
.tr-result.is-right { border-color: var(--euc-wash2); background: var(--euc-wash); }
.tr-result.is-wrong { border-color: #EEDBD1; background: var(--clay-wash); }
.tr-result-score {
  font-family: var(--display); font-weight: 500;
  font-variation-settings: "SOFT" 24, "opsz" 40;
  color: var(--euc-deep);
}

@media (max-width: 900px) {
  .tr-progress-group { font-size: 12.5px; }
  .tr-progress-count { font-size: 13px; }
  .tr-progress-reset { font-size: 13px; }
  .tr-stepctl-back { font-size: 15.5px; }
  .tr-result-q { font-size: 1.06rem; }
  .tr-result-your, .tr-result-fb { font-size: .98rem; }
}

@media (max-width: 560px) {
  .tr-stepctl { flex-wrap: wrap; }
  .tr-stepctl-next { flex: 1 1 auto; justify-content: center; }
}

/* ─── Early-access form (#access) ─────────────────────────────────────────────
   Replaces the two mailto buttons that used to sit in .close-actions. Fields
   map 1:1 onto public.waitlist columns; nothing here invents schema. Sizing
   follows the existing controls: --r-md corners like the cards, --r-pill on the
   submit like every other .btn, and a 46px min-height so every target clears
   the 44px floor the rest of the site holds to.
   ───────────────────────────────────────────────────────────────────────────── */
.cf-form { margin: 30px 0 22px; text-align: left; }

.cf-grid {
  display: grid; gap: 16px; margin-bottom: 16px;
  grid-template-columns: repeat(2, minmax(0, 1fr));
}
@media (max-width: 620px) { .cf-grid { grid-template-columns: 1fr; } }

.cf-field { display: flex; flex-direction: column; gap: 6px; margin-bottom: 4px; }

.cf-field label {
  font-family: var(--body); font-size: 14px; font-weight: 600;
  color: var(--ink); letter-spacing: -.005em;
}
/* "required" / "or just you" ride along inside the label so the cue is part of
   the accessible name, not a colour or an asterisk a screen reader skips. */
.cf-req, .cf-opt {
  font-weight: 500; font-size: 12.5px; margin-left: 6px;
  padding: 1px 7px; border-radius: var(--r-pill);
}
.cf-req { color: var(--euc-deep); background: var(--euc-wash); }
.cf-opt { color: var(--ink-3);    background: var(--paper-3); }

.cf-field input,
.cf-field textarea {
  font-family: var(--body); font-size: 16px;  /* 16px: iOS zooms anything less */
  color: var(--ink); background: var(--surface);
  border: 1.5px solid var(--line-2); border-radius: var(--r-md);
  padding: 12px 14px; min-height: 46px; width: 100%;
  transition: border-color .16s var(--ease), box-shadow .16s var(--ease);
}
.cf-field textarea { resize: vertical; min-height: 108px; line-height: 1.5; }
.cf-field input::placeholder,
.cf-field textarea::placeholder { color: var(--ink-3); }

.cf-field input:focus-visible,
.cf-field textarea:focus-visible {
  outline: none; border-color: var(--euc);
  box-shadow: 0 0 0 3px var(--euc-wash);
}
.cf-field input[aria-invalid="true"],
.cf-field textarea[aria-invalid="true"] { border-color: var(--clay); }
.cf-field input[aria-invalid="true"]:focus-visible,
.cf-field textarea[aria-invalid="true"]:focus-visible { box-shadow: 0 0 0 3px var(--clay-wash); }

.cf-hint { font-size: 13.5px; color: var(--ink-3); margin: 2px 0 0; }
.cf-err  { font-size: 13.5px; color: var(--clay); margin: 2px 0 0; font-weight: 500; }
.cf-noscript { margin-top: 12px; }

/* Honeypot: off-canvas rather than display:none. Some bots skip anything they
   can tell is hidden, and this must look ordinary in the DOM. */
.cf-hp {
  position: absolute; left: -9999px; width: 1px; height: 1px;
  overflow: hidden; opacity: 0; pointer-events: none;
}

.cf-actions {
  display: flex; align-items: center; gap: 16px; flex-wrap: wrap; margin-top: 20px;
}
#cf-submit[disabled] { opacity: .6; cursor: progress; }

.cf-status { margin: 0; font-size: 14px; color: var(--ink-2); min-height: 1.2em; }
.cf-status[data-state="error"] { color: var(--clay); font-weight: 500; }

/* Success panel, shown in place of the form. */
.cf-done {
  margin: 30px 0 22px; padding: 26px 24px; text-align: left;
  background: var(--euc-wash); border: 1px solid var(--euc-wash2);
  border-radius: var(--r-lg);
}
.cf-done:focus-visible { outline: 2px solid var(--euc); outline-offset: 3px; }
.cf-done-i { width: 30px; height: 30px; color: var(--euc); margin-bottom: 8px; }
.cf-done h3 {
  font-family: var(--display); font-size: 22px; font-weight: 600;
  color: var(--euc-deep); margin: 0 0 6px;
}
.cf-done p { margin: 0; color: var(--ink-2); font-size: var(--type-prose); line-height: 1.55; }

@media (prefers-reduced-motion: reduce) {
  .cf-field input, .cf-field textarea { transition: none; }
}

/* ═══════════════════════════════════════════════════════════════════
   WHY VOICE — citation line and the callout
   Added 2026-08-20 with the #voice rewrite.

   `.cite` sits inside a .ticks li, under the claim it supports. It is
   deliberately quiet: the claim is the argument, the citation is only there
   so a sceptical reader can check it.

   `.voice-callout` is NOT a fifth tick. It carries a different kind of
   argument from the four points above it (who the product is for, rather
   than what it does), so it gets its own weight and its own ground.
   ═══════════════════════════════════════════════════════════════════ */
.ticks li .cite {
  display: block;
  margin-top: 7px;
  font-size: .8rem;
  line-height: 1.55;
  color: var(--ink-3);
  opacity: .78;
}

.voice-callout {
  margin-top: 34px;
  padding: 22px 24px;
  background: var(--surface);
  border: 1px solid var(--line);
  border-left: 3px solid var(--euc);
  border-radius: var(--r-lg);
  box-shadow: var(--lift-1);
}
.voice-callout-h {
  margin: 0 0 8px;
  font-weight: 700;
  font-size: 1.02rem;
  line-height: 1.35;
}
.voice-callout-b {
  margin: 0;
  font-size: .95rem;
  line-height: 1.66;
  color: var(--ink-2);
}
@media (max-width: 620px) {
  .voice-callout { padding: 18px 18px; }
}
