/* ============================================================
   Milligram website v2 — design tokens + base
   Light base, dark AI-hero flip. Apple register.
   Spec: docs/superpowers/specs/2026-07-20-milligram-website-redesign-design.md
   ============================================================ */

:root {
  /* Dark base (app family) */
  --bg: #0F1012;
  --bg-wash: #151A22;
  --surface: #1A1D21;
  --line: rgba(255, 255, 255, 0.08);
  --ink: #ECEDF1;
  --ink-2: #A0A4AC;
  --ink-3: #6A6F78;

  /* Dark section (app-family surfaces) */
  --dk-bg: #0A0B0D;
  --dk-surface: #16181C;
  --dk-line: rgba(255, 255, 255, 0.08);
  --dk-ink: #ECEDF1;
  --dk-ink-2: #A0A4AC;

  /* Accents */
  --accent: #60A5FA;
  --cta: #E8EAF0;
  --cta-ink: #0B0D10;
  --star: #F5A623;

  /* Type */
  --font: -apple-system, BlinkMacSystemFont, "SF Pro Text", "Segoe UI", sans-serif;
  --mono: ui-monospace, "SF Mono", Menlo, monospace;
  /* ---- Type scale -------------------------------------------------------
     Below the two display steps this IS Apple's iOS ramp: 17 / 16 / 15 / 13 /
     12 / 11. It deliberately skips 14, which is why every step here is visibly
     distinct. The previous set had eleven sizes between 11 and 20 (13.5, 13.7,
     14, 14.5, 12.5, 11.5 ...) — half-pixel steps the eye cannot resolve, so
     they read as one muddled tier instead of a hierarchy.

     Each size ships with a matching tracking value. Tracking is size-specific:
     it tightens as type grows (large text reads too loose at default spacing)
     and opens up as type shrinks (small text needs air to stay legible). The
     ramp below is monotonic, which the old set was not: the 52px h2 was
     tracked TIGHTER than the 63px h1. */
  --fs-display:  clamp(40px, 4.4vw, 64px);
  /* The 24px floor matters: at a 28px floor the mobile h1 (33.8px) sat only
     1.21x above the h2, reintroducing at the small end exactly the weak
     separation this scale fixes at the large end. 24px restores a 1.41x gap. */
  --fs-title:    clamp(24px, 3.2vw, 42px);
  /* One rank above Title, for a feature panel that carries a section on its own.
     A real step in the ramp rather than a one-off, so it sits cleanly between
     the hero's Display (72) and a normal section heading (42). */
  --fs-title-lg: clamp(30px, 4vw, 54px);
  --fs-lead:     clamp(17px, 1.35vw, 20px);
  --fs-body:     17px;
  --fs-callout:  16px;
  --fs-subhead:  15px;
  --fs-footnote: 13px;
  --fs-caption:  12px;
  --fs-caption2: 11px;

  --tr-display:  -0.028em;
  --tr-title:    -0.022em;
  --tr-title-lg: -0.025em;   /* between Display and Title, keeping the ramp monotonic */
  --tr-lead:     -0.014em;
  --tr-body:     -0.011em;
  --tr-callout:  -0.010em;
  --tr-subhead:  -0.008em;
  --tr-footnote:  0em;
  --tr-caption:   0.002em;
  --tr-caption2:  0.006em;

  /* Leading tracks size inversely: tight on display, roomy on running text.
     UI labels that never wrap get their own tight value — inheriting the 1.6
     body leading was inflating every nav item, review meta row and footer
     link with phantom vertical space. */
  --lh-display: 1.06;
  --lh-lead:    1.5;
  --lh-body:    1.6;
  --lh-ui:      1.25;

  /* Legacy aliases: still referenced by the retired stats/features/AI/FAQ
     rules further down. Nothing live reads these. */
  --fs-h2: var(--fs-title);
  --fs-h3: var(--fs-lead);
  --fs-eyebrow: var(--fs-caption);

  /* Motion */
  --ease-out: cubic-bezier(0.16, 1, 0.3, 1);
  --dur-fast: 250ms;
  --dur-standard: 400ms;

  /* ---- Spacing scale ----------------------------------------------------
     2px steps through 12, then 4px steps. Every gap, padding and margin on the
     page snaps to this. The old set ran 3/5/7/9/10/14/15/18/22/26/30/38/50/54
     — arbitrary values that made two "equal" gaps differ by 4px and two
     unrelated gaps land 2px apart, so spacing carried no meaning. */
  --sp-2:  2px;   --sp-4:  4px;   --sp-6:  6px;   --sp-8:  8px;
  --sp-10: 10px;  --sp-12: 12px;  --sp-16: 16px;  --sp-20: 20px;
  --sp-24: 24px;  --sp-32: 32px;  --sp-40: 40px;  --sp-48: 48px;
  --sp-56: 56px;  --sp-64: 64px;

  /* Radius scales WITH the element it rounds, so a card and its enlarged twin
     keep the same corner-to-size ratio. */
  --r-icon: 6px;    /* 22px app icon */
  --r-card: 18px;   /* 340px review card */
  --r-pop:  22px;   /* 400px expanded card — 18 x (400/340) */
  --r-pill: 999px;

  /* One vertical rhythm for every band below the hero. */
  /* Section seam: full chapter-break air, Apple-product-page scale. Raised
     twice from the original 80-128 band, which read tight once the sections
     filled out — each section should land as its own scene. */
  --section-y: clamp(140px, 22vh, 260px);

  /* Layout.
     1160 left content at just 70% of a 1512px screen — 224px of dead space per
     side before anything started, so the page read narrow and boxed-in on any
     modern laptop. 1320 brings that to ~81% and a 144px margin, which still
     keeps a real gutter on ultrawide displays where the cap takes over.
     The pad floor is 24 (not 20) so small screens keep a proper edge margin. */
  --page-max: 1400px;
  --pad-x: clamp(24px, 4vw, 64px);
}

* { box-sizing: border-box; }
::selection { background: rgba(96, 165, 250, 0.28); color: #FFFFFF; }
:focus-visible { outline: 2px solid rgba(96, 165, 250, 0.7); outline-offset: 3px; border-radius: 4px; }

/* Fixed ambient atmosphere: two cool radials + fine grain, behind everything */
/* Overscanned so the layer can drift with the pointer (see polish.js) without
   ever exposing an edge. */
.atmosphere {
  position: fixed; inset: -60px; z-index: 0; pointer-events: none;
  will-change: transform;
  background:
    radial-gradient(50% 38% at 78% -4%, rgba(59, 130, 246, 0.05), transparent 70%),
    radial-gradient(42% 34% at 18% -6%, rgba(232, 234, 240, 0.03), transparent 72%);
}
.atmosphere::after {
  content: ""; position: absolute; inset: 0; opacity: 0.028;
  background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='140' height='140'%3E%3Cfilter id='n'%3E%3CfeTurbulence type='fractalNoise' baseFrequency='0.9' numOctaves='2'/%3E%3C/filter%3E%3Crect width='140' height='140' filter='url(%23n)'/%3E%3C/svg%3E");
}
main, #nav, #footer { position: relative; z-index: 1; }

/* Scroll-reveal primitives */
.rv { opacity: 0; transform: translateY(22px); transition: opacity 0.7s var(--ease-out), transform 0.7s var(--ease-out); }
.rv.on { opacity: 1; transform: none; }
@media (prefers-reduced-motion: reduce) { .rv { opacity: 1; transform: none; } }
html { scroll-behavior: auto; /* Lenis owns scrolling */ }

body {
  margin: 0;
  background: var(--bg);
  color: var(--ink);
  font-family: var(--font);
  font-size: var(--fs-body);
  line-height: var(--lh-body);
  letter-spacing: var(--tr-body);
  font-variant-numeric: tabular-nums;
  font-optical-sizing: auto;
  -webkit-font-smoothing: antialiased;
  text-rendering: optimizeLegibility;
}

h1, h2, h3 { margin: 0; }
h1, h2 {
  text-wrap: balance;
  /* Display leading. 1.02 set the two ascender/descender rows nearly touching
     the moment a heading wrapped; 1.06 still reads tight but keeps a wrapped
     heading legible. */
  line-height: var(--lh-display);
  font-weight: 650;
}
/* Section headings sit a clear step below the hero: 42px against the hero's
   64px is a 1.52x gap, where the old 52px was only 1.21x and read as a peer. */
h2 { font-size: var(--fs-title); letter-spacing: var(--tr-title); }
h3 { font-weight: 600; letter-spacing: -0.015em; }
p { margin: 0; text-wrap: pretty; }
a { color: inherit; }
img, video { max-width: 100%; display: block; }

.wrap { max-width: var(--page-max); margin: 0 auto; padding: 0 var(--pad-x); }
/* Attribution kicker above a section heading. Carries the real Apple mark,
   since the line is a claim about WHERE the reviews come from — the mark makes
   that attribution visual instead of asking the reader to take the words for it.
   Dropped the monospace: mono uppercase micro-type reads as a technical spec
   sheet, and the system face is what Apple sets its own kickers in. */
.eyebrow {
  display: inline-flex; align-items: center; gap: var(--sp-8);
  font-size: var(--fs-eyebrow);
  font-weight: 600;
  line-height: var(--lh-ui);
  /* 0.14em, up from 0.12em. Uppercase needs more tracking than lowercase to
     stop the caps colliding, and the old value was tuned for a monospace that
     already carries its own even advance widths. */
  letter-spacing: 0.14em;
  text-transform: uppercase;
  /* One step brighter than ink-3: at 12px the dimmest ink read as disabled
     rather than quiet. */
  color: var(--ink-2);
}
/* Optically sized against the cap height, not the font size, and eased back a
   touch so the mark sits WITH the text rather than leading it. */
.eyebrow-mark { height: 12px; width: auto; flex: none; opacity: 0.92; margin-top: -1px; }

/* ============================================================
   Nav — translucent material pill, scroll-edge fade, no hairline
   ============================================================ */
/* Nav: one element, two poses, blended by a single scroll-driven progress
   var. --p 0 = a wide transparent toolbar resting on the hero (brand left,
   links centered, CTA right, spread across the band). --p 1 = the compact
   floating glass pill. Width, glass alpha, blur radius, padding and shadow
   all derive from --p, so the bar CONDENSES under the reader's scroll as one
   continuous morph — nav.js scrubs the var, nothing here transitions. */
#nav {
  --p: 0;
  position: fixed; inset: 0 0 auto 0; z-index: 50;
  display: flex; justify-content: center;
  padding: var(--sp-16) var(--pad-x) 0;
  pointer-events: none;
}
#nav::after { /* scroll-edge fade instead of a border */
  content: ""; position: absolute; inset: 0 0 -24px 0; z-index: -1;
  background: linear-gradient(to bottom, rgba(15,16,18,0.9), rgba(15,16,18,0));
  opacity: var(--p);
}
.nav-pill {
  pointer-events: auto;
  display: flex; align-items: center; justify-content: space-between; gap: var(--sp-24);
  width: 100%;
  /* The condensed width (1240 − this) has to be retuned whenever the nav links
     change, because `justify-content: space-between` distributes only what is
     left over. "About" → "Calculator" added ~35px of link, which at the old
     843 left the pill exactly full: gaps collapsed to their 24px minimum and a
     slightly wider system font would have overflowed it. 808 restores the same
     free space the pill had before, so it still reads as a hugged pill rather
     than a packed one. */
  max-width: calc(1240px - 808px * var(--p));
  background: rgba(21, 23, 26, calc(0.62 * var(--p)));
  backdrop-filter: blur(calc(20px * var(--p))) saturate(calc(1 + 0.6 * var(--p)));
  -webkit-backdrop-filter: blur(calc(20px * var(--p))) saturate(calc(1 + 0.6 * var(--p)));
  border-radius: var(--r-pill);
  /* Asymmetric by design: the right inset is small because the CTA pill sits
     inside it, so the two pills share a consistent 10px shell. */
  padding: calc(14px - 5px * var(--p)) var(--sp-10) calc(14px - 5px * var(--p)) var(--sp-24);
  box-shadow:
    0 1px 0 rgba(255, 255, 255, calc(0.08 * var(--p))) inset,
    0 8px 32px rgba(0, 0, 0, calc(0.35 * var(--p)));
}
/* Brand lockup: ONE component, so nav and footer share size, weight, gap AND
   icon dimensions. They previously diverged on all four (16/17px text,
   22/20px icon, 6/5px radius, 9px gap) — see .footer-brand, which now just
   inherits this rule instead of redefining it. */
.brand-lockup,
.nav-brand, .footer-brand {
  display: inline-flex; align-items: center; gap: var(--sp-8);
  font-weight: 500; font-size: var(--fs-callout); line-height: var(--lh-ui);
  letter-spacing: var(--tr-callout); text-decoration: none;
}
.nav-brand img, .footer-brand img { width: 22px; height: 22px; border-radius: var(--r-icon); }
/* Nav lockup runs a size up from the footer's, and the wordmark rides the
   condensation: as --p climbs it thins, fades, and COMPRESSES INTO a vertical
   hairline that takes its place — icon ┃ links in the compact pose. Gaps are
   margins scaled by --p (a flex gap would survive the word's collapse). */
.nav-brand { gap: 0; }
.nav-brand img { width: 32px; height: 32px; border-radius: 8px; }
.nav-brand-word {
  display: inline-block; overflow: hidden; white-space: nowrap;
  margin-left: calc(9px * (1 - var(--p)));
  max-width: calc(110px * (1 - var(--p)));
  opacity: clamp(0, calc(1 - var(--p) * 1.6), 1);
}
.nav-divider {
  width: 1px; height: 18px; flex: 0 0 auto;
  /* Compact pose: 24px to the icon, matching the pill's 24px flex gap to the
     About link on its other side — the line sits dead-centre between them,
     which only holds if the pill width leaves no slack for space-between to
     distribute (the width curve is tuned to the exact hug). */
  margin-left: calc(12px + 12px * var(--p));
  background: rgba(255, 255, 255, 0.16);
  opacity: var(--p);
  transform: scaleY(calc(0.4 + 0.6 * var(--p)));
}
.nav-links { display: flex; gap: var(--sp-32); }
.nav-links a { font-size: var(--fs-subhead); line-height: var(--lh-ui); letter-spacing: var(--tr-subhead); color: var(--ink-2); text-decoration: none; }
.nav-links a:hover { color: var(--ink); }
/* Current page: the platinum step hover resolves to, held permanently. Colour
   only — bumping the weight would change each link's width, so the nav pill
   would be a different size on About than on Contact. The marker is stamped
   per page by tools/sync-chrome.mjs, never by the shared partial.
   Not applied to .nav-brand: a highlighted logo reads as a bug, not a place. */
.nav-links a[aria-current="page"] { color: var(--ink); }
/* Skip link: invisible until keyboard focus, then a glass chip pinned top-left
   above everything. The page's first tab stop. */
.skip-link {
  position: fixed; left: var(--sp-16); top: var(--sp-16);
  z-index: 100;
  transform: translateY(-200%);
  padding: 10px 16px;
  border-radius: var(--r-pill);
  background: rgba(26, 30, 37, 0.9);
  border: 1px solid rgba(255, 255, 255, 0.18);
  -webkit-backdrop-filter: blur(14px) saturate(160%);
          backdrop-filter: blur(14px) saturate(160%);
  color: var(--ink); font-size: var(--fs-footnote); font-weight: 600;
  text-decoration: none;
  transition: transform 0.25s var(--ease-out);
}
.skip-link:focus-visible { transform: none; }
#main:focus { outline: none; }

.nav-links a { padding: 4px 2px; transition: color var(--dur-fast) var(--ease-out); }
@media (max-width: 900px) { .nav-links { display: none; } }

.nav-cta {
  display: inline-flex; align-items: center; gap: var(--sp-8);
  background: var(--cta); color: var(--cta-ink);
  /* Same step as the nav links; the 600 weight carries the emphasis, not size. */
  font-size: var(--fs-subhead); line-height: var(--lh-ui);
  letter-spacing: var(--tr-subhead); font-weight: 600; text-decoration: none;
  padding: var(--sp-10) var(--sp-16); border-radius: var(--r-pill);
  transition: transform 100ms ease-out;
}
.nav-cta-ic { height: var(--sp-16); width: auto; }
.nav-cta:active { transform: scale(0.97); }
@media (prefers-reduced-transparency: reduce) {
  .nav-pill { background: #15171A; backdrop-filter: none; -webkit-backdrop-filter: none; border: 1px solid var(--line); }
}
@media (prefers-contrast: more) {
  .nav-pill { border: 1px solid var(--ink-2); }
}

/* ============================================================
   Hero — light wash, centered stack, phone rising into the fold
   ============================================================ */
#hero {
  position: relative;
  padding-top: clamp(128px, 16vh, 172px);
  overflow: hidden;
  /* No opaque base colour. The body already supplies --bg, and #hero sits in
     `main` at z-index 1 while the pointer glow sits at z-index 0 — so painting
     an opaque base here hid the glow across the whole hero, which is exactly
     where the cursor spends its time. Only the top radial remains. */
  background: radial-gradient(55% 40% at 50% 0%, rgba(59, 130, 246, 0.07), transparent 75%);
  /* Self-contained stacking context so the canvas below can never interact with
     anything outside this section — `isolation` rather than a z-index, because
     it changes nothing about how #hero itself is painted in `main`. */
  isolation: isolate;
}
/* The WebGL layer. Bottom of the hero's stack, no hit testing, and it inherits
   the section's overflow clip so it can never spill past the band. Sized in CSS
   only — the module reads clientWidth/Height, so layout stays the sole owner of
   its box. */
#heroGL {
  position: absolute; inset: 0; z-index: 0;
  width: 100%; height: 100%;
  display: block; pointer-events: none;
}
/* The content sits above it. Both hero children are lifted, since on mobile the
   wrap is a plain block and would otherwise share the canvas's level. */
#hero > .wrap { position: relative; z-index: 1; }
.hero-inner { text-align: center; position: relative; z-index: 2; }
/* Capped so the headline holds a single line at the 1160px wrap. Tighter
   tracking as it scales up, per the type rule that large display text reads
   too loose at default spacing. */
#hero h1 { font-size: clamp(38px, 6.4vw, 88px); letter-spacing: var(--tr-display); }
#hero h1 .line > span {
  background: linear-gradient(180deg, #FFFFFF 30%, #B9C0CC 100%);
  -webkit-background-clip: text; background-clip: text;
  -webkit-text-fill-color: transparent; color: transparent;
}
/* The reveal wrapper clips its span, so descenders (y, p, g) need slack BELOW
   the line box or they get cut. That slack must live on the wrapper's padding,
   never on the span's line-height/padding: those set the line-to-line advance,
   and folding the clip-guard into them silently inflated the leading to 1.30em.
   The negative margin cancels the padding so the advance is exactly 1.06em. */
#hero h1 .line { display: block; overflow: hidden; padding-bottom: 0.18em; margin-bottom: -0.18em; }
#hero h1 .line > span { display: inline-block; line-height: 1.06; will-change: transform; }
.hero-sub {
  font-size: var(--fs-lead); color: var(--ink-2);
  /* Lead copy sits between display and body: looser than the headline, tighter
     than the 1.6 body default (which reads gappy on a centred 3-line block).
     Tracking a shade tighter than body's -0.011em because it renders larger. */
  line-height: var(--lh-lead); letter-spacing: var(--tr-lead);
  /* 40px DECLARED lands ~29px OPTICAL: the headline's last .line carries a
     -0.18em descender-slack margin (~11px at 63px type) that leaks downward.
     The declared 32px was really rendering as a 21px gap. Headline and lead
     are one unit, so this stays well under the 56px break below. */
  max-width: 640px; margin: var(--sp-40) auto 0;
}
/* Stacked layouts let the lead wrap naturally: the spans are inline and the
   deliberate rag below is a desktop-only composition. */
.hero-sub span { display: inline; }
/* Largest gap in the hero. With the laurels gone the block is pill + headline +
   lead (one message) and then the CTA, so this is the only real group break:
   24 pill->headline, 40 headline->lead, 48 lead->CTA. */
.hero-cta-row { display: flex; justify-content: center; margin-top: var(--sp-48); }
.store-badge img { height: 54px; width: auto; }
.store-badge { display: inline-block; transition: transform 100ms ease-out; }
.store-badge:active { transform: scale(0.97); }
/* Social-proof pill above the headline. Replaced the twin laurels, which took a
   52px-tall block plus 56px of margin to carry two numbers — this states the
   same proof in one line and hands that vertical space back to the headline. */
.hero-eyebrow {
  /* Built in the site's OWN grammar rather than as a bordered pill. Every other
     small label above a heading here is a .kicker: uppercase, 0.14em tracked,
     --ink-3, no container. The hero was the only place using a rounded pill with
     a gold star, so it read as imported from a template rather than native — and
     that star was the single warm accent in a page that is platinum and cool by
     rule (--star otherwise appears only on the actual review stars, where a gold
     star is the thing being depicted). */
  display: inline-flex; align-items: center; gap: var(--sp-12);
  margin: 0 0 var(--sp-20);
  font-size: var(--fs-caption2); font-weight: 600; line-height: var(--lh-ui);
  letter-spacing: 0.14em; text-transform: uppercase;
  color: var(--ink-3);
  white-space: nowrap;
}
/* The two numbers ARE the proof, so they carry the brighter ink; the connective
   words stay quiet. */
.hero-eyebrow strong { color: var(--ink-2); font-weight: 600; }
/* The same 1px rule the proof row uses to separate its two stats, so the top and
   bottom of the page state the same numbers in the same voice. */
.eyebrow-div { width: 1px; height: 11px; background: var(--line); flex: none; }

/* One device, centred. The 3-phone carousel is gone: its two flanking phones
   sat at 50% opacity and 65% brightness, so they were unreadable — noise
   without information — and the stage's edge mask clipped them once the hero
   became a half-width column. A single device reads larger at the same
   footprint, and the rotation moves to the SCREEN instead of the hardware. */
.hero-stage {
  position: relative; height: 480px; margin-top: 88px;
  display: grid; place-items: center;
  /* The general guard for the `fr` blowout described on .hero-phones below: an
     `fr` track will not shrink past its content's min-content width unless the
     item is allowed to. Explicit here so anything dropped into this stage later
     cannot silently steal width from the copy column. */
  min-width: 0;
}
/* The floor disc that used to live here is gone. It was a hardcoded
   rgba(96,165,250,.11) ellipse 840px wide against phones that are 419px, centred
   on the STAGE rather than the hardware, and it implied a floor in a composition
   where the devices float. Its job — grounding, and light carrying down into the
   volume — is now done by the broad pass of .hero-phones-bleed, which takes its
   shape and colour from the render itself instead of being placed under it. */
/* Two overlapping devices: front phone nearest the copy, back phone peeking
   from the upper right. Offsets are variables so a breakpoint only has to
   retune the numbers, not restate the transforms.
   The back phone dims to 82% brightness — enough to read as depth, but nowhere
   near the retired carousel's 50% opacity + 65% brightness, which made its
   flanking phones literally unreadable. */
/* No perspective and no rotation live here any more. Both are baked into the
   render, where a real camera resolved them for two real objects at two real
   depths. What is left is placement. */
.phone-pair {
  position: relative; width: 100%; height: 100%;
  display: grid; place-items: center;
}
/* ABSOLUTE, and that is load-bearing rather than a positioning convenience.
   The hero columns are `1.12fr 0.88fr`, and an `fr` track is really
   `minmax(auto, 1fr)` — it cannot shrink below its content's min-content
   width. In flow, this image's intrinsic 1100px became that floor, forcing the
   phone column to 1100px and crushing the copy column from ~613px to 297px.
   Out of flow it contributes no intrinsic width, so the ratio holds.

   Height-driven for the same reason the old pair was: the render is portrait
   (0.637) inside a stage that is wider, so constraining width would leave the
   phones small with dead air either side. Slightly over 100% so the cluster
   spills past the stage the way the two devices used to — #hero clips it, so
   the overflow is safe. max-width defeats the global `img { max-width: 100% }`,
   which would otherwise squash the aspect once the stage got narrow. */
.hero-phones,
.hero-phones-bleed {
  position: absolute;
  left: 50%; top: 50%;
  display: block;
  height: 106%;
  width: auto;
  max-width: none;
  pointer-events: none;
  -webkit-user-select: none; user-select: none;
}
.hero-phones { transform: translate(-50%, -50%); }
/* Scaled past the hardware so the light falls OUTSIDE the silhouette, which is
   the whole point — light that stops at the edge is just a lighter phone.
   `screen` because the backdrop is near-black: it adds and never darkens, so
   the helix stays visible through the glow instead of being veiled by it. */
.hero-phones-bleed {
  /* 1.44 is not a taste value: the asset carries 22% padding on each side so the
     broad blur can fall to zero before the frame edge, which puts its phone
     content at 1/1.76 of the frame. Scaling by exactly that re-registers the glow with the
     hardware it came from. The spread past the silhouette is the blur's.

     Straight alpha, NOT a blend mode. `screen` would be the textbook choice for
     additive light, but it is unreachable here: GSAP's scroll parallax puts a
     transform on .hero-stage, a transform creates a stacking context, and a
     blend inside one composites against its own transparent backdrop rather
     than the canvas — which rendered the layer as an opaque black box. So the
     asset is unpremultiplied instead: alpha carries the falloff and colour is
     normalised, which means colour x alpha reproduces the glow exactly. Over a
     near-black page that is arithmetically what screen would have produced. */
  /* Offsets come in as custom properties rather than GSAP writing `transform`
     directly, because the scale and the centring live here. One transform, one
     owner: CSS composes it, JS only publishes the two numbers. */
  /* The vars are multiplied by 1px rather than declared as lengths: GSAP writes
     custom properties UNITLESS, and `calc(-50% + var(--glx, 0px))` fed a bare
     number is invalid, which silently drops the entire transform — losing the
     centring and the scale along with the offset. */
  --glx: 0; --gly: 0;
  transform: translate(calc(-50% + var(--glx) * 1px), calc(-50% + var(--gly) * 1px))
             scale(1.76);
  opacity: 0.52;
}

/* Atmosphere in FRONT of the hardware. The helix's dust only ever renders
   behind the phones, which is precisely what gives away that they occupy a
   different plane — in a real volume some particles pass in front of the
   subject. One tiled element rather than a swarm of nodes: four dots in a
   260px tile, drifting exactly one tile per cycle so the loop is seamless, and
   the whole thing is a single compositor layer moving on transform alone.
   Masked to an ellipse so the field dissolves instead of ending on the stage's
   rectangular edge. */
.hero-air {
  position: absolute; left: -20%; top: -30%;
  width: 140%; height: 200%;
  pointer-events: none;
  opacity: 0.55;
  background-repeat: repeat;
  background-size: 260px 260px;
  background-image:
    radial-gradient(circle 1.7px at 18% 22%, rgba(196, 219, 255, 0.55), transparent 100%),
    radial-gradient(circle 1.1px at 68% 44%, rgba(196, 219, 255, 0.40), transparent 100%),
    radial-gradient(circle 2.1px at 42% 78%, rgba(196, 219, 255, 0.26), transparent 100%),
    radial-gradient(circle 1.2px at 86% 88%, rgba(196, 219, 255, 0.45), transparent 100%);
  -webkit-mask-image: radial-gradient(ellipse 62% 46% at 50% 50%, #000 35%, transparent 78%);
          mask-image: radial-gradient(ellipse 62% 46% at 50% 50%, #000 35%, transparent 78%);
  animation: heroAirDrift 52s linear infinite;
  will-change: transform;
}
@keyframes heroAirDrift {
  from { transform: translate3d(0, 0, 0); }
  to   { transform: translate3d(0, -260px, 0); }
}
@media (prefers-reduced-motion: reduce) {
  .hero-air { animation: none; }
}

.notif {
  position: absolute; left: var(--nx); top: var(--ny);
  width: 250px; z-index: 3;
  background: rgba(255, 255, 255, 0.88);
  backdrop-filter: blur(24px) saturate(160%);
  -webkit-backdrop-filter: blur(24px) saturate(160%);
  border-radius: 16px;
  padding: 11px 14px 12px;
  box-shadow: 0 12px 40px rgba(14, 17, 22, 0.13);
  text-align: left;
}
.notif-head { display: flex; justify-content: space-between; align-items: center; margin-bottom: 4px; }
.notif-app { display: inline-flex; align-items: center; gap: 6px; font-size: 12px; color: var(--ink-2); }
.notif-app img { border-radius: 5px; }
.notif-when { font-size: 11.5px; color: var(--ink-3); }
.notif-title { font-size: 13.5px; font-weight: 600; letter-spacing: -0.01em; }
.notif-body { font-size: 12.5px; color: var(--ink-2); line-height: 1.45; }
@media (prefers-reduced-transparency: reduce) {
  .notif { background: #FFFFFF; backdrop-filter: none; -webkit-backdrop-filter: none; border: 1px solid var(--line); }
}

/* Card positions live here (not inline) so breakpoints can override them. */
.notif[data-n="0"] { --nx: max(16px, calc(50% - 420px)); --ny: 10px; }
.notif[data-n="1"] { --nx: max(16px, calc(50% - 460px)); --ny: 158px; }
.notif[data-n="2"] { --nx: min(calc(100% - 266px), calc(50% + 170px)); --ny: 30px; }
.notif[data-n="3"] { --nx: min(calc(100% - 246px), calc(50% + 210px)); --ny: 195px; }

@media (max-width: 900px) {
  .notif[data-n="1"], .notif[data-n="3"] { display: none; }
  .notif[data-n="0"] { --nx: max(8px, calc(50% - 330px)); }
  .notif[data-n="2"] { --nx: min(calc(100% - 258px), calc(50% + 90px)); --ny: 190px; }
}
@media (max-width: 640px) {
  /* Tracking is size-specific: the -0.028em tuned for 88px reads cramped once
     the headline drops to ~34px, so it eases back toward neutral here. */
  #hero h1 { font-size: clamp(32px, 9vw, 40px); letter-spacing: -0.018em; }
  /* Tighter pair so the two phones still clear a 375px screen. */
  .hero-stage { height: 440px; }
  .notif { width: 218px; }
  .notif[data-n="0"] { --nx: 10px; --ny: 0; }
  .notif[data-n="2"] { --nx: calc(100% - 228px); --ny: 236px; }
}

/* ---- Hero layout: stacked to 900px, copy-left / phones-right above --------
   The stage moved inside .wrap so it can be a grid cell, so on the stacked
   layout it reclaims the wrap's side padding to stay edge-to-edge as before. */
@media (max-width: 900px) {
  .hero-stage {
    margin-left: calc(var(--pad-x) * -1);
    margin-right: calc(var(--pad-x) * -1);
  }
}
@media (min-width: 901px) {
  .hero-grid {
    display: grid;
    /* Capped tighter than the page container. Both hero columns carried real
       slack at the full 1279px band (129px in the copy column, 109px in the
       phone column), and pulling the band in also closes most of the gap to the
       labs card below, which sits at 1060. Done here rather than on --page-max
       so the review marquee and footer keep the wider band: narrowing globally
       would drop content back to ~74% of the viewport, which is the dead-space
       problem the 1400 container was widened to solve. */
    /* Expressed as content + padding so the BAND is 1150 regardless of how the
       fluid pad-x resolves. A plain max-width is a border-box figure, so the
       padding eats out of it and the band lands ~120px narrower than intended. */
    max-width: calc(1150px + var(--pad-x) * 2);
    margin-inline: auto;
    /* Copy takes the larger share: it carries the display type, and the phone
       column only needs to hold the centre device at full size. */
    grid-template-columns: 1.12fr 0.88fr;
    align-items: center;
    column-gap: clamp(32px, 4vw, 56px);
  }
  .hero-inner { text-align: left; }
  /* Everything below is sized against the COLUMN, so all of it steps up with
     the wider 1320 container: a 64px headline and a 500px measure were tuned
     for the old ~564px copy column and read undersized in a ~654px one. */
  #hero h1 { font-size: clamp(40px, 4.8vw, 72px); }
  /* Each span becomes its own line, giving the lead a deliberate descending
     rag: 552 / 437 / 380 / 312px. The top line matches the headline's 556px
     and the last lands on the pill's 297px, so the block is anchored to the
     two elements bracketing it and the taper points at the CTA.
     Breaks were chosen by measuring every possible split and taking the one
     with the most even steps (sd 25px) — an uneven rag reads as an accident.
     max-width is only a safety net here; the rag is content-driven, and the
     slack keeps a 552px line from ever wrapping and collapsing the shape. */
  .hero-sub { max-width: 620px; margin-left: 0; margin-right: 0; }
  .hero-sub span { display: block; }
  .hero-cta-row { justify-content: flex-start; }
  /* No stacked top gap any more: the stage centres against the copy block.
     The device is HEIGHT-constrained (aspect 1380/2880 ~ 2.09), so a bigger
     phone needs a taller stage, not a wider column: 288 x 2.09 = 601. */
  .hero-stage { height: 620px; margin-top: 0; }
  /* Enough room for the floor glow to fall off before the clip (57px was too
     little; the light was still near full strength at the boundary), but
     sized so the hero->first-section seam lands on the page's 128px rhythm
     rather than the 164 it sat at. */
  #hero { padding-bottom: clamp(64px, 8.5vh, 104px); }
}

/* ============================================================
   Device composite — real bezel PNG over the screen content.
   Window measured from alpha: left 4.35% / top 2.5% / 91.2% x 95%.
   ============================================================ */
.device {
  --dw: 280px;
  position: relative;
  width: var(--dw);
  aspect-ratio: 1380 / 2880;
  filter: drop-shadow(0 30px 70px rgba(0, 0, 0, 0.55));
}
.device .bezel { position: absolute; inset: 0; width: 100%; height: 100%; z-index: 2; pointer-events: none; }
.device .screen {
  position: absolute; left: 4.35%; top: 2.5%;
  width: 91.2%; height: 95%;
  object-fit: cover;
  border-radius: calc(var(--dw) * 0.115);
  background: #0A0B0D;
  z-index: 1;
}
/* Silver iPhone 17 Pro frame (opaque screen area): bezel sits UNDER the
   video, which is inset onto the black screen rect measured from the asset
   (x 24-997, y 18-2063 of 1022x2082). object-fit: contain + near-black
   letterbox keeps the full recording visible; bars vanish on the dark screen. */
.device--silver { aspect-ratio: 1022 / 2082; }
.device--silver .bezel { z-index: 1; }
.device--silver .screen {
  left: 2.35%; top: 0.86%;
  width: 95.2%; height: 98.2%;
  object-fit: contain;
  background: #050505;
  border-radius: calc(var(--dw) * 0.13);
  z-index: 2;
}

/* ============================================================
   Stats bar
   ============================================================ */
#stats { padding: clamp(48px, 7vh, 84px) 0; }
.stats-row {
  display: grid; grid-template-columns: repeat(4, 1fr); gap: 24px;
  text-align: center;
}
.stat-value {
  font-family: var(--mono); font-weight: 500;
  font-size: clamp(26px, 3vw, 40px); letter-spacing: -0.02em;
}
.stat-label { font-size: var(--fs-caption); color: var(--ink-2); margin-top: 4px; }
@media (max-width: 640px) { .stats-row { grid-template-columns: repeat(2, 1fr); gap: 28px 12px; } }

/* ============================================================
   Feature scenes
   ============================================================ */
#features { padding-top: clamp(40px, 8vh, 100px); }
.features-head { text-align: center; max-width: 640px; margin: 0 auto clamp(48px, 8vh, 90px); }
.features-head h2 { font-size: var(--fs-h2); margin-top: 12px; }

.scene { padding: clamp(40px, 8vh, 90px) 0; }
.scene-grid {
  display: grid; grid-template-columns: 1fr 1fr;
  gap: clamp(32px, 5vw, 80px); align-items: center;
}
.scene-copy h3 { font-size: var(--fs-h2); line-height: 1.05; letter-spacing: -0.025em; }
.scene-copy h3 b { font-weight: 650; }
.scene-copy p { color: var(--ink-2); margin-top: 16px; max-width: 46ch; }
.scene-copy .eyebrow { display: block; margin-bottom: 12px; }
.scene-media { display: flex; justify-content: center; }
@media (max-width: 860px) {
  .scene-grid { grid-template-columns: 1fr; }
  .scene-media { order: -1; }
}

/* PK scene (pinned) */
#pk-scene { background: linear-gradient(180deg, var(--bg), #F3F6FA 50%, var(--bg)); }
.pk-stage { max-width: 860px; margin: 0 auto; text-align: center; }
.pk-readout {
  font-family: var(--mono); font-size: clamp(34px, 4.5vw, 56px); font-weight: 500;
  letter-spacing: -0.02em; margin-top: 8px;
}
.pk-readout small { font-size: 0.45em; color: var(--ink-3); font-weight: 400; }
#pk-svg { width: 100%; height: auto; margin-top: 18px; overflow: visible; }
#pk-svg .axis { stroke: var(--line); stroke-width: 1; }
#pk-svg .curve { stroke: var(--accent); stroke-width: 3; fill: none; stroke-linecap: round; }
#pk-svg .dose-marker { fill: var(--ink); opacity: 0; transform: scale(0.2); transform-origin: center; transform-box: fill-box; transition: opacity 0.3s var(--ease-out), transform 0.4s var(--ease-out); }
#pk-svg .dose-marker.on { opacity: 1; transform: scale(1); }
#pk-svg .axis-label { font-family: var(--mono); font-size: 11px; fill: var(--ink-3); }

/* Doses vignette */
.sched-card {
  width: min(400px, 100%);
  background: var(--dk-surface); border-radius: 20px; padding: 18px;
  box-shadow: 0 24px 70px rgba(14, 17, 22, 0.25);
}
.sched-card .sc-head { font-family: var(--mono); font-size: 11px; letter-spacing: 0.1em; color: var(--dk-ink-2); text-transform: uppercase; margin-bottom: 12px; }
.sched-row {
  display: flex; align-items: center; gap: 12px;
  padding: 12px 10px; border-radius: 12px;
}
.sched-row + .sched-row { margin-top: 4px; }
.sc-check {
  width: 26px; height: 26px; border-radius: 13px; flex: none;
  border: 1.5px solid rgba(255,255,255,0.25); display: grid; place-items: center;
}
.sc-check svg { width: 13px; height: 13px; }
.sc-check svg path { stroke: #30D158; stroke-width: 2.5; fill: none; stroke-linecap: round; stroke-linejoin: round; stroke-dasharray: 20; stroke-dashoffset: 20; }
.sched-row.logged .sc-check { border-color: rgba(48, 209, 88, 0.6); background: rgba(48, 209, 88, 0.12); }
.sched-row.logged .sc-check svg path { stroke-dashoffset: 0; transition: stroke-dashoffset 0.45s var(--ease-out); }
.sc-name { color: var(--dk-ink); font-size: 15px; font-weight: 600; }
.sc-dose { font-family: var(--mono); color: var(--dk-ink-2); font-size: 13px; }
.sc-state { margin-left: auto; font-size: 13px; color: var(--dk-ink-2); }
.sched-row.logged .sc-state { color: #30D158; }
.sched-row .sc-name s { text-decoration-color: rgba(255,255,255,0.5); }

/* Syringe scene */
.syringe-wrap { width: min(460px, 100%); }
#syr-svg { width: 100%; height: auto; overflow: visible; }
#syr-svg .barrel { fill: none; stroke: var(--ink-2); stroke-width: 2; }
#syr-svg .tick { stroke: var(--ink-3); stroke-width: 1; }
#syr-svg .tick-label { font-family: var(--mono); font-size: 9px; fill: var(--ink-3); }
#syr-svg .fill { fill: rgba(96, 165, 250, 0.35); }
#syr-svg .plunger { stroke: var(--ink); stroke-width: 3; }
.syr-readout { font-family: var(--mono); font-size: 22px; margin-top: 14px; text-align: center; }
.syr-readout span { color: var(--accent); }

/* Trio cards */
.trio { display: grid; grid-template-columns: repeat(3, 1fr); gap: 20px; margin-top: clamp(40px, 6vh, 70px); }
.trio-card {
  background: var(--surface); border: 1px solid var(--line); border-radius: 24px;
  overflow: hidden; text-align: center; padding-top: 26px;
}
.trio-card h3 { font-size: var(--fs-h3); }
.trio-card p { font-size: var(--fs-caption); color: var(--ink-2); margin: 6px auto 20px; max-width: 30ch; }
.trio-card img { width: 74%; margin: 0 auto; border-radius: 22px 22px 0 0; box-shadow: 0 -1px 0 var(--line), 0 10px 40px rgba(14,17,22,0.10); }
@media (max-width: 860px) { .trio { grid-template-columns: 1fr; } .trio-card img { width: 60%; } }

/* ============================================================
   AI hero (dark flip)
   ============================================================ */
#ai {
  background:
    radial-gradient(55% 45% at 50% 105%, rgba(34, 211, 238, 0.10), transparent 70%),
    radial-gradient(40% 35% at 22% 0%, rgba(96, 165, 250, 0.05), transparent 75%),
    var(--dk-bg);
  color: var(--dk-ink);
  padding: clamp(80px, 12vh, 140px) 0;
}
.ai-head { text-align: center; max-width: 700px; margin: 0 auto clamp(40px, 7vh, 70px); }
.ai-head .eyebrow { color: #22D3EE; }
.ai-head h2 { font-size: var(--fs-h2); margin-top: 14px; }
.ai-head h2 .dim { color: var(--dk-ink-2); display: block; }
.ai-head p { color: var(--dk-ink-2); margin-top: 16px; }

.chat-mock { max-width: 560px; margin: 0 auto; min-height: 380px; }
.chat-user {
  margin-left: auto; width: fit-content; max-width: 80%;
  background: #2C2F36; color: var(--dk-ink);
  border-radius: 18px 18px 4px 18px; padding: 11px 16px; font-size: 15px;
}
.chat-steps { margin: 18px 0 0 4px; }
.chat-step {
  font-family: var(--mono); font-size: 12.5px; color: var(--dk-ink-2);
  padding: 4px 0; opacity: 0; transform: translateY(6px);
  transition: opacity 0.3s var(--ease-out), transform 0.3s var(--ease-out);
}
.chat-step.on { opacity: 1; transform: none; }
.chat-step::before { content: "› "; color: #22D3EE; }
.chat-answer {
  margin-top: 16px; background: var(--dk-surface);
  border: 1px solid var(--dk-line);
  border-radius: 4px 18px 18px 18px; padding: 14px 18px;
  font-size: 15px; line-height: 1.6; color: var(--dk-ink);
  min-height: 120px;
}
.chat-answer .caret { display: inline-block; width: 7px; height: 16px; background: #22D3EE; vertical-align: -2px; margin-left: 2px; animation: caret-blink 0.8s steps(1) infinite; }
@keyframes caret-blink { 50% { opacity: 0; } }

/* ============================================================
   Three-up pillars
   Copy held at the top of each card, one device below it running past the
   card's bottom edge. The card clips it, and that clip is the whole effect:
   the card's height is a design decision, and the device simply carries on
   past it rather than being sized to fit inside.
   ============================================================ */
/* Only a small top pad: #hero already carries 96-140px of bottom padding, and
   #labs below carries a full --section-y of its own. Adding a third full band
   here would leave ~270px of dead air on each side of this section. */
/* Coupled to the hero (its head opens right under the fold), so it takes a
   HALF seam: clearly airier than before, still tighter than a full chapter
   break. */
#pillars { padding: clamp(48px, 9vh, 112px) 0 0; }
/* The section head is shared with #labs (.section-head), so both sections open
   on the same kicker / heading / lead block at the same rank. */

.pillars-grid {
  display: grid;
  grid-template-columns: repeat(3, minmax(0, 1fr));
  gap: var(--sp-20);
  /* The hero's and the labs cards' band. One edge down the whole page. */
  max-width: 1150px;
  margin: 0 auto;
}

.pillar {
  position: relative;
  display: flex; flex-direction: column;
  overflow: hidden;                  /* clips the device — the effect itself */
  border-radius: 26px;               /* matches .ai-card; .labs-card's 32 is for a panel 3x this wide */
  border: 1px solid var(--line);
  background: linear-gradient(158deg, #1A1E24 0%, #12151A 56%, #0D0F13 100%);
  box-shadow:
    0 1px 0 rgba(255, 255, 255, 0.05) inset,   /* top edge catching light */
    0 30px 70px rgba(0, 0, 0, 0.42);
  padding: clamp(24px, 2.6vw, 34px);
  min-height: clamp(490px, 47vw, 610px);
  isolation: isolate;
  /* Sizes the device against its OWN column rather than the viewport, so the
     composition holds identically at 3-up, 1-up and every width between
     instead of needing a re-tuned vw value per breakpoint. */
  container-type: inline-size;
}
/* Light pooling around the device so it sits IN the card rather than on it.
   Centred inside its own box and faded out well before the card's bottom edge:
   a gradient positioned `at 50% 100%` puts its brightest point ON the boundary,
   which is what made the hero's floor glow cut hard. */
.pillar::before {
  content: "";
  position: absolute; z-index: 0; pointer-events: none;
  left: 50%; top: 54%; transform: translate(-50%, -50%);
  width: 460px; height: 460px;
  background: radial-gradient(closest-side, rgba(96, 165, 250, 0.10), transparent 76%);
}
/* The same pointer-lit bevel the review cards carry: the 1px edge catches a
   highlight from the direction the pointer actually is (fed --lx/--ly/--lit by
   polish.js). Extending the page's one signature light behaviour here is what
   makes the sections read as one material system rather than three designs.
   No interior wash variant: these cards already pool their own ambient glow
   behind the device, and two overlapping washes would muddy it.
   z-index 3 lifts the ring above the clipped device edge — light landing on the
   frame sits in FRONT of the object the frame cuts off. */
.pillar::after {
  content: "";
  position: absolute; inset: 0; z-index: 3;
  border-radius: inherit;
  padding: 1px;
  background: radial-gradient(260px circle at var(--lx, 50%) var(--ly, 50%),
              rgba(206, 222, 255, 0.70), rgba(206, 222, 255, 0.08) 44%, transparent 72%);
  -webkit-mask: linear-gradient(#000 0 0) content-box, linear-gradient(#000 0 0);
          mask: linear-gradient(#000 0 0) content-box, linear-gradient(#000 0 0);
  -webkit-mask-composite: xor;
          mask-composite: exclude;
  opacity: var(--lit, 0);
  pointer-events: none;
  /* Only the fade eases; position stays locked to the pointer (a transition on
     position lags the cursor and reads as rubbery). */
  transition: opacity 240ms var(--ease-out);
}
@media (prefers-reduced-transparency: reduce) { .pillar::after { display: none; } }

/* Arrive in sequence rather than as one block. The three cards share a top
   edge, so ScrollTrigger fires them on the same frame; the stagger has to come
   from the delay. */
.pillar:nth-child(2) { transition-delay: 70ms; }
.pillar:nth-child(3) { transition-delay: 140ms; }
/* Layered entrance: the device rises INSIDE the card while the card itself
   rises into the page — two offsets at two speeds, which is what reads as
   depth rather than a flat block sliding up. The device runs 0.2s longer than
   the card's 0.7s reveal on the same delay, so it settles just after its
   card does. CSS-owned end to end; nothing else writes this transform. */
.pillar.rv .pillar-device { transform: translateX(-50%) translateY(26px); transition: transform 0.9s var(--ease-out); }
.pillar.rv.on .pillar-device { transform: translateX(-50%) translateY(0); }
.pillar:nth-child(2) .pillar-device { transition-delay: 70ms; }
.pillar:nth-child(3) .pillar-device { transition-delay: 140ms; }
@media (prefers-reduced-motion: reduce) {
  .pillar.rv .pillar-device { transform: translateX(-50%); transition: none; }
}

.pillar-copy { position: relative; z-index: 2; }
/* The Title step's floor with a card-scale ceiling. --fs-title's 42px top end is
   tuned for a panel three times this wide and would read as a headline that
   escaped its container; --fs-lead is the next rung down but tops out at 20px,
   which against a 16px body is a 1.08x ratio and no hierarchy at all. So: 24 at
   the bottom (Title's own floor, so it stays on the ramp) and 27 at the top,
   which is ~7% of the card's width — the proportion the reference sets its card
   headings at. */
.pillar-copy h3 {
  font-size: clamp(24px, 2.2vw, 28px); letter-spacing: var(--tr-title);
  line-height: 1.15; font-weight: 650;
}
.pillar-copy p {
  margin-top: var(--sp-10);
  color: var(--ink-2);
  font-size: var(--fs-callout); letter-spacing: var(--tr-callout);
  line-height: var(--lh-lead);
}
/* Each body opens with the exact figures visible in its own clip (2.5 mg -> 50
   units, 10 mg + 3 spares, 24.4 ng/mL). Set a step brighter so the number the
   copy quotes and the number on the screen below it read as the same fact.
   The page's tabular-nums keeps these aligned like instrument readouts. */
.pillar-copy p strong { color: var(--ink); font-weight: 600; }

/* Absorbs whatever height the copy leaves, so the device always starts a fixed
   distance below the text and the amount of phone on show is set by the card. */
.pillar-stage { position: relative; z-index: 1; flex: 1; margin-top: clamp(18px, 2.2vw, 30px); }
/* Scoped: a bare .pillar-device ties .device on specificity (0,1,0), and that
   collision has silently dropped --dw twice in this file already. */
.pillar .pillar-device {
  --dw: clamp(190px, 90cqw, 278px);
  position: absolute; top: 0; left: 50%;
  transform: translateX(-50%);
  /* Layered rather than one shadow: a tight contact shadow plus two soft casts
     is what reads as a real object standing in front of a surface. */
  filter:
    drop-shadow(0 3px 8px rgba(0, 0, 0, 0.45))
    drop-shadow(0 30px 60px rgba(0, 0, 0, 0.55))
    drop-shadow(0 70px 120px rgba(0, 0, 0, 0.38));
}

/* Stacked: the same composition at a readable size rather than three columns
   squeezed to ~200px, where the device would land at 147px wide. The width cap
   keeps the card from stretching into a shape the centred device cannot hold. */
@media (max-width: 900px) {
  .pillars-grid { grid-template-columns: minmax(0, 1fr); gap: var(--sp-16); max-width: 420px; }
  .pillar { min-height: 440px; }
  .pillar:nth-child(2), .pillar:nth-child(3) { transition-delay: 0s; }
}

/* ============================================================
   Health trends
   One tall card: copy held on the left (headline top, body pinned bottom),
   four devices in two staggered columns on the right, both rows clipped by
   the card. Taller than the labs cards on purpose — four screens of
   instrumentation need the vertical room, and the extra height is what lets
   both rows show real content between the two cut edges.
   ============================================================ */
/* Back in the slot right after the pillars (which carry no bottom pad), so it
   owns its opening gap again; nothing below needed since #labs opens with one. */
#vitals { padding: var(--section-y) 0 0; }

.vitals-card {
  position: relative;
  overflow: hidden;                  /* both device rows run past the card */
  display: flex;
  border-radius: 32px;
  border: 1px solid var(--line);
  background: linear-gradient(158deg, #1A1E24 0%, #12151A 56%, #0D0F13 100%);
  box-shadow:
    0 1px 0 rgba(255, 255, 255, 0.05) inset,
    0 40px 90px rgba(0, 0, 0, 0.45);
  padding: clamp(30px, 3.7vw, 56px);
  min-height: clamp(480px, 47vw, 640px);
  max-width: 1150px;                 /* the band every card on the page shares */
  margin: 0 auto;
  isolation: isolate;
  container-type: inline-size;
}
/* Light pooled behind the device wall, centred in its own box and faded well
   before the edges so the overflow clip never cuts a visible band. */
.vitals-card::before {
  content: "";
  position: absolute; z-index: 0; pointer-events: none;
  right: -6%; top: 50%; transform: translateY(-50%);
  width: 900px; height: 900px;
  background: radial-gradient(closest-side, rgba(96, 165, 250, 0.10), transparent 76%);
}

/* Headline at the top, body pinned to the bottom: the vertical gap between
   them is the composition (borrowed from the reference), so the copy brackets
   the card instead of huddling in a corner of it. */
.vitals-copy {
  position: relative; z-index: 2;
  display: flex; flex-direction: column; justify-content: space-between;
  max-width: 420px;
}
/* Card rank (Title), matching the labs cards: the section-opener step moved up
   to the .section-head this card now sits under — a card heading larger than
   its section header would invert the hierarchy. */
.vitals-copy h3 {
  font-size: var(--fs-title); letter-spacing: var(--tr-title);
  line-height: var(--lh-display); font-weight: 650;
  margin-top: var(--sp-10);
}
.vitals-body {
  color: var(--ink-2);
  font-size: var(--fs-body); letter-spacing: var(--tr-body);
  line-height: var(--lh-lead);
  max-width: 40ch;
}
/* The readout between headline and body. The column's justify-between used to
   leave one tall void in the middle; these rows earn that space with the exact
   values showing on the four screens to the right, so the copy column reads as
   the wall's instrument panel rather than a caption with dead air in it.
   Same grammar as the labs spec table, but carrying DATA (page-wide
   tabular-nums keeps the values aligned like a real readout). */
.vitals-specs { margin: 0; padding: 0; }
.vspec {
  display: grid; grid-template-columns: 1fr auto;
  align-items: baseline;
  gap: var(--sp-16);
  padding: var(--sp-12) 0;
  border-top: 1px solid var(--line);
}
.vspec:last-child { border-bottom: 1px solid var(--line); }
.vspec dt {
  font-size: var(--fs-caption2); font-weight: 600; line-height: 1.5;
  letter-spacing: 0.1em; text-transform: uppercase; color: var(--ink-3);
}
.vspec dd {
  margin: 0;
  font-size: var(--fs-callout); font-weight: 600;
  letter-spacing: var(--tr-callout); line-height: 1.4;
  color: var(--ink);
}
/* Rows tick in one after another as the card reveals — a readout coming
   online, not a table that was always there. */
.vitals-card.rv .vspec { opacity: 0; transform: translateY(8px); transition: opacity 0.5s var(--ease-out), transform 0.5s var(--ease-out); }
.vitals-card.rv.on .vspec { opacity: 1; transform: none; }
.vitals-card.rv .vspec:nth-child(1) { transition-delay: 0.20s; }
.vitals-card.rv .vspec:nth-child(2) { transition-delay: 0.30s; }
.vitals-card.rv .vspec:nth-child(3) { transition-delay: 0.40s; }
.vitals-card.rv .vspec:nth-child(4) { transition-delay: 0.50s; }
@media (prefers-reduced-motion: reduce) {
  .vitals-card.rv .vspec { opacity: 1; transform: none; transition: none; }
}

.vitals-stage { position: absolute; inset: 0; z-index: 1; pointer-events: none; }
/* Two nested layers per column ON PURPOSE (same architecture as the labs
   devices): the OUTER .vitals-col is the only thing the scroll drift animates,
   the INNER carries the CSS centering transform. One transform, one owner —
   GSAP's y and the -50% centering can never capture each other's values. */
.vitals-col { position: absolute; top: 50%; will-change: transform; }
.vitals-col-inner {
  transform: translateY(-50%);
  display: flex; flex-direction: column;
  gap: clamp(26px, 3.8cqw, 40px);
}
.vitals-col--a { left: 47%; }
.vitals-col--b { left: 72%; }
/* The stagger rides the INNER (margin, not transform) so the outer stays
   clean for the drift. Column B sits higher, as in the reference — two seams
   at two heights read as a live wall rather than a grid of thumbnails. */
.vitals-col--b .vitals-col-inner { margin-top: -38px; }
.vitals-stage .vitals-device {
  --dw: clamp(180px, 24.5cqw, 256px);
  filter:
    drop-shadow(0 2px 6px rgba(0, 0, 0, 0.45))
    drop-shadow(0 26px 55px rgba(0, 0, 0, 0.55))
    drop-shadow(0 60px 110px rgba(0, 0, 0, 0.38));
}

/* Stacked: copy first, then the device wall at a fixed height the card still
   clips. The columns keep their stagger; only the geometry compresses. */
@media (max-width: 900px) {
  .vitals-card { display: block; min-height: 0; }
  .vitals-copy { max-width: 46ch; }
  .vitals-copy h3 { margin-bottom: var(--sp-12); }
  .vitals-stage { position: relative; inset: auto; height: 420px; margin-top: var(--sp-24); }
  .vitals-col--a { left: 6%; }
  .vitals-col--b { left: 53%; }
  .vitals-stage .vitals-device { --dw: clamp(150px, 42cqw, 210px); }
}

/* ---- Phone widths: the four trend screens ----
   At 375px the two columns collided. The clamp bottomed out at its 150px floor
   while the columns stayed at 6%/53% of a 265px stage, which put column A at
   71-221px and column B at 195-345px: a 26px overlap, and column B running 25px
   past the card's right edge.

   Measured, not eyeballed: the floor is what broke it. 42cqw resolves to ~111px
   at this width, so the clamp was holding the phones ~35% wider than the layout
   had room for. Sized down and the columns pushed apart to sit inside the card
   with real air between them. */
@media (max-width: 700px) {
  .vitals-stage .vitals-device { --dw: clamp(106px, 38cqw, 126px); }

  /* Centred as a CLUSTER, which is not the same as centring each column.
     The two columns are absolutely positioned, so nothing centres them on its
     own — at 2%/52% the four phones spanned 60-305px inside a 24-351px card,
     leaving 36px of air on the left against 46px on the right. Reading as
     off-centre by 10px is exactly what that is.

     Derived, not nudged. The two columns are drawn together to a 10px gutter,
     which makes the four screens read as one panel of trends rather than two
     separate stacks. That closes the cluster to 222px, so it sits 52.5px inside
     the 327px card: column A's left edge is 24 + 52.5 = 76.5px, which is 21.5px
     into the 265px stage = 8.1%. Column B follows 116px later (106 of phone +
     10 of gutter) and lands at 51.9%.

     10px is the floor. Below that the bezels touch and the four phones stop
     reading as four. All three values move together — change --dw and both
     percentages need re-deriving, or the balance breaks. */
  .vitals-col--a { left: 8.1%; }
  .vitals-col--b { left: 51.9%; }

  /* The device wall was 420px of a 722px card, and it is a CROPPED wall by
     design — two 228px phones per column can never fully fit, the stage clips
     them, and that crop is what makes it read as a wall rather than a gallery.
     So the height is free to come down: less of each phone shows, the same four
     screens still read, and the card loses ~90px it was spending on the bottom
     third of two phones nobody needed to see. */
  .vitals-stage { height: 330px; }
}

/* ---- The scan pair ----------------------------------------------------------
   Two half-band cards under the trends wall: body scan and food scan. The
   pillars' grammar (copy top, device below, clipped by the card, clip plays
   once and settles) at duo scale, plus one element of its own: a floating
   glass chip carrying the scan's ANSWER — the number the clip ends on, lifted
   off the screen and set onto the card. Copy makes the claim, the chip is the
   receipt, and the phone shows the work. */
.scan-grid {
  display: grid;
  /* Asymmetric on purpose: the body-scan card holds a two-phone before/after
     and needs the width; the food-scan card holds one phone and does not. */
  grid-template-columns: minmax(0, 1.3fr) minmax(0, 0.7fr);
  gap: var(--sp-20);
  max-width: 1150px;
  margin: var(--sp-20) auto 0;
}
.scan-card {
  position: relative;
  display: flex; flex-direction: column;
  overflow: hidden;                  /* the clip past the card IS the depth */
  border-radius: 26px;               /* pair rank, same as .ai-card */
  border: 1px solid var(--line);
  background: linear-gradient(158deg, #1A1E24 0%, #12151A 56%, #0D0F13 100%);
  box-shadow:
    0 1px 0 rgba(255, 255, 255, 0.05) inset,
    0 30px 70px rgba(0, 0, 0, 0.42);
  padding: clamp(26px, 3vw, 44px);
  min-height: clamp(470px, 44vw, 580px);
  isolation: isolate;
  container-type: inline-size;
}
.scan-card::before {
  content: "";
  position: absolute; z-index: 0; pointer-events: none;
  left: 50%; top: 58%; transform: translate(-50%, -50%);
  width: 520px; height: 520px;
  background: radial-gradient(closest-side, rgba(96, 165, 250, 0.10), transparent 76%);
}
.scan-card:nth-child(2) { transition-delay: 70ms; }

.scan-copy { position: relative; z-index: 2; max-width: 46ch; }
/* Between the pillar heads (27px, third-band cards) and the labs heads (42px,
   full-band cards): half-band cards take the step between the two. */
.scan-copy h3 {
  font-size: clamp(24px, 2.2vw, 28px); letter-spacing: var(--tr-title);
  line-height: 1.12; font-weight: 650;
}
.scan-copy p {
  margin-top: var(--sp-10);
  color: var(--ink-2);
  font-size: var(--fs-callout); letter-spacing: var(--tr-callout);
  line-height: var(--lh-lead);
  max-width: 44ch;
}

.scan-stage { position: relative; z-index: 1; flex: 1; margin-top: clamp(18px, 2vw, 28px); }
/* Scoped for the same specificity reason as every other *-device rule here. */
.scan-card .scan-device {
  --dw: clamp(230px, 56cqw, 300px);
  position: absolute; top: 0; left: 50%;
  transform: translateX(-50%);
  filter:
    drop-shadow(0 3px 8px rgba(0, 0, 0, 0.45))
    drop-shadow(0 30px 60px rgba(0, 0, 0, 0.55))
    drop-shadow(0 70px 120px rgba(0, 0, 0, 0.38));
}

/* Before/after duo (body scan): two smaller devices, the BEFORE seated lower
   and the AFTER higher and in front — the story reads up and to the right,
   with the twirl arrow carrying the eye across the gap between them. */
.scan-stage--duo .duo-device {
  --dw: clamp(150px, 36cqw, 216px);
  position: absolute;
  filter:
    drop-shadow(0 3px 8px rgba(0, 0, 0, 0.45))
    drop-shadow(0 26px 55px rgba(0, 0, 0, 0.55))
    drop-shadow(0 60px 110px rgba(0, 0, 0, 0.38));
}
.scan-stage--duo .duo-device--a { left: 7.5%; top: 48px; z-index: 1; }
.scan-stage--duo .duo-device--b { right: 7.5%; top: 0; z-index: 2; }
/* The between-states disc. Same glass recipe as .scan-chip and the nav pill —
   blur, hairline border, inset top light — in a circle, holding a real SF
   Symbol arrow. Sits in the seam at the phones' upper-mid height. */
.duo-link {
  position: absolute; z-index: 3;
  left: 50%; top: 41%;
  transform: translateX(-50%);
  width: 46px; height: 46px;
  display: grid; place-items: center;
  border-radius: 50%;
  background: rgba(26, 30, 37, 0.58);
  border: 1px solid rgba(255, 255, 255, 0.14);
  -webkit-backdrop-filter: blur(14px) saturate(160%);
          backdrop-filter: blur(14px) saturate(160%);
  box-shadow:
    inset 0 1px 0 rgba(255, 255, 255, 0.08),
    0 18px 40px rgba(0, 0, 0, 0.45);
  color: var(--ink);
}
.duo-link .sfsym { width: 17px; height: auto; display: block; }
/* Arrives a beat after the card, settling like the chips do. */
.scan-card.rv .duo-link { opacity: 0; transform: translateX(-50%) translateY(10px) scale(0.94); transition: opacity 0.5s var(--ease-out) 0.55s, transform 0.5s var(--ease-out) 0.55s; }
.scan-card.rv.on .duo-link { opacity: 1; transform: translateX(-50%); }
@media (prefers-reduced-motion: reduce) {
  .scan-card.rv .duo-link { opacity: 1; transform: translateX(-50%); transition: none; }
}
@media (prefers-reduced-transparency: reduce) {
  .duo-link { background: #181C22; -webkit-backdrop-filter: none; backdrop-filter: none; }
}

/* The receipt trail. A column of glass chips beside the device, and each one
   pops in AT THE CLIP SECOND its moment happens on screen (data-at, driven off
   the video's own currentTime in polish.js — a timer would drift the instant
   playback stalls). Earlier chips stay: the stack reads as a chronological
   log of what the app just did, not a caption that swaps. */
/* Not a column: the chips are scattered AROUND the device (alternating sides,
   stepping down with time), each carrying its own left/right + top inline so
   the placement reads in the HTML next to the moment it belongs to. The frame
   spans the whole card; chips overlap the phone's edges deliberately — pinned
   to the object, not captioned beside it. */
.scan-chips {
  position: absolute; inset: 0; z-index: 3;
  pointer-events: none;
}
.scan-chips .scan-chip { position: absolute; }
.scan-chip {
  display: inline-flex; align-items: baseline; gap: var(--sp-8);
  padding: 10px 16px;
  border-radius: var(--r-pill);
  background: rgba(26, 30, 37, 0.58);
  border: 1px solid rgba(255, 255, 255, 0.14);
  -webkit-backdrop-filter: blur(14px) saturate(160%);
          backdrop-filter: blur(14px) saturate(160%);
  box-shadow:
    inset 0 1px 0 rgba(255, 255, 255, 0.08),
    0 18px 40px rgba(0, 0, 0, 0.45);
  font-size: var(--fs-footnote); letter-spacing: var(--tr-footnote);
  line-height: var(--lh-ui); color: var(--ink-2);
  white-space: nowrap;
  margin: 0;
  /* Hidden until its moment: small rise + settle, no bounce. */
  opacity: 0; transform: translateY(10px) scale(0.97);
  transition: opacity 0.45s var(--ease-out), transform 0.45s var(--ease-out);
}
.scan-chip.on { opacity: 1; transform: none; }
.scan-chip b { font-size: var(--fs-callout); font-weight: 650; letter-spacing: var(--tr-callout); color: var(--ink); }
/* Status dots: .run = in-progress (quiet pulse), .ok = done (semantic green). */
.scan-chip i {
  align-self: center;
  width: 6px; height: 6px; border-radius: 50%;
}
.scan-chip i.run { background: var(--accent); animation: chip-pulse 1.6s ease-in-out infinite; }
.scan-chip i.ok { background: #34D399; }
@keyframes chip-pulse { 50% { opacity: 0.35; } }
@media (prefers-reduced-motion: reduce) {
  .scan-chip { transition: none; }
  .scan-chip i.run { animation: none; }
}
@media (prefers-reduced-transparency: reduce) {
  .scan-chip { background: #181C22; -webkit-backdrop-filter: none; backdrop-filter: none; }
}

@media (max-width: 900px) {
  .scan-grid { grid-template-columns: minmax(0, 1fr); gap: var(--sp-16); max-width: 420px; }
  .scan-card { min-height: 470px; }
  .scan-card:nth-child(2) { transition-delay: 0s; }
  .scan-card .scan-device { --dw: clamp(210px, 66cqw, 260px); }
  /* NB: no left/right override here. The frame must keep its inset:0 — the
     chips are absolutely positioned inside it, so overriding `left` made the
     box shrink-wrap to zero content width, pinned it to the card's right edge,
     and every chip's percentage offset then resolved against nothing and hung
     off the card to be sliced by its clip. */
}

/* ============================================================
   Bloodwork feature
   Copy left, two tilted devices bleeding off the card's right edge. The card
   clips them, which is what sells the depth: they read as objects lying past
   the panel rather than images pasted onto it.
   ============================================================ */
/* Top pad only now that this sits directly above #reviews, which opens with a
   full --section-y of its own — keeping the bottom pad here doubled the seam. */
#labs { padding: var(--section-y) 0 0; }

.labs-card {
  position: relative;
  overflow: hidden;                 /* the clip IS the effect; devices run past it */
  border-radius: 32px;
  border: 1px solid var(--line);
  background: linear-gradient(158deg, #1A1E24 0%, #12151A 56%, #0D0F13 100%);
  box-shadow:
    0 1px 0 rgba(255, 255, 255, 0.05) inset,   /* top edge catching light */
    0 40px 90px rgba(0, 0, 0, 0.45);
  padding: clamp(30px, 3.7vw, 56px);
  min-height: clamp(340px, 32vw, 424px);
  /* Narrower than the page container, and centred. The device positions are
     percentage-based, so the whole composition scales with the card rather than
     needing to be re-tuned. */
  /* Matched to the hero's 1150 band so the two feature sections share one edge.
     At 1060 the card sat 110px inside the hero, which read as two grids rather
     than one page. */
  max-width: 1150px;
  margin: 0 auto;
  isolation: isolate;
  /* Lets the devices be sized against the CARD rather than the viewport. Below
     ~1170px the card shrinks with the container while the max-width cap is no
     longer binding, so a vw-based device shrank at a different rate and the
     composition drifted apart: at 1120px the back device stopped 13px short of
     the edge and lost its bleed entirely. */
  container-type: inline-size;
}
/* Light pooling behind the devices so they sit IN the panel, not on it.
   Centred inside its own box and faded out well before the edge, so the card's
   overflow clip never cuts a visible band (the mistake the hero glow made). */
.labs-card::before {
  content: "";
  position: absolute; z-index: 0; pointer-events: none;
  right: -10%; top: 50%; transform: translateY(-50%);
  width: 880px; height: 880px;
  background: radial-gradient(closest-side, rgba(96, 165, 250, 0.10), transparent 76%);
}

.labs-copy {
  position: relative; z-index: 2;
  display: flex; flex-direction: column;
  /* Sized with the card's 424 cap: 424 minus the 2x56 padding. Anything larger
     silently re-inflates the card past its min-height. */
  min-height: clamp(240px, 22vw, 310px);
  /* Widened from 400px. The spec table below needs a real measure: at 400 its
     label and value columns crowded each other while the right half of the card
     sat empty. (The old 400 was sized for a two-line headline that no longer
     exists.) */
  max-width: 476px;
}
/* Section head. Sits one rank ABOVE the two cards it introduces: it takes the
   Title-lg step the first card used to own, and the cards step down to Title.
   That card's heading was the section's main line before this existed; now it
   is a card heading, and a card heading larger than the section header would
   invert the hierarchy. */
/* Centred when stacked, left-aligned on desktop. The block is capped to the
   CARDS' 1150 band rather than the page container, so the heading starts on the
   same vertical line the cards do instead of hanging 30px outside it. */
.section-head { text-align: center; max-width: 46ch; margin: 0 auto clamp(28px, 3.4vw, 48px); }
@media (min-width: 901px) {
  .section-head { text-align: left; max-width: 1150px; }
  /* Runs most of the band rather than sitting in a narrow measure. At display
     size a long line is the point: it fills the width the cards below occupy,
     so the head reads as the section's opening statement rather than a caption
     floating above them. */
  /* A share of the band, not a ch count: ch resolves against the h2's own 50px
     font, so a "generous" 22ch was still only 510px and capped the line at 45%
     of the width the cards occupy. */
  /* pretty, not the inherited balance. Balance evens the line lengths, which on
     a long headline produced three short lines sitting at 51% of the allowance
     instead of filling it. pretty keeps orphan protection without that. */
  .section-head h2 { max-width: 88%; text-wrap: pretty; }
  .section-head p { max-width: 58%; }

  /* Centred variant. Only the desktop rules above need undoing: .section-head is
     already centred at narrow widths, and it is this breakpoint that swings it
     left. Same specificity as the rules it overrides (0,1,1), so it has to come
     AFTER them — source order is what decides here, not weight.
     margin-inline:auto is the load-bearing half: h2 and p keep a measure, and
     without it those constrained boxes would stay pinned to the left edge and
     only their text would centre inside them. */
  .section-head--center { text-align: center; max-width: 780px; margin-inline: auto; }
  .section-head--center h2 { max-width: none; }
  /* balance, not pretty: a centred subhead that wraps must have EVEN lines, or
     the second line hangs short under a centred first and the whole block reads
     lopsided. pretty only guards orphans, which is the right call for the
     left-aligned heads above but not here. */
  .section-head--center p { max-width: 60ch; margin-inline: auto; text-wrap: balance; }
}
.section-head h2 { font-size: var(--fs-title-lg); letter-spacing: var(--tr-title-lg); }
.section-head p {
  color: var(--ink-2); font-size: var(--fs-body);
  line-height: var(--lh-lead); letter-spacing: var(--tr-body);
  margin-top: var(--sp-12);
}
.labs-copy h3 {
  font-size: var(--fs-title); letter-spacing: var(--tr-title);
  line-height: var(--lh-display); font-weight: 650;
}
.labs-body {
  margin-top: var(--sp-12);
  color: var(--ink-2);
  line-height: var(--lh-lead);
  max-width: 42ch;
}

/* A spec table rather than bullets. Bullets here would only restate the line
   above them; label/value rows carry density the prose cannot, and they read in
   the clinical register the product actually lives in. It is also a different
   shape to the timeline list in the card below, so the two cards do not land as
   the same layout twice.
   Pinned to the bottom so the headline still holds the top of the block and the
   vertical tension of the original composition survives. */
/* Four rows at a natural height, not three stretched to fill. The earlier
   version used flex:1 to absorb the leftover space, which made each row 78px
   tall with its text stranded in the middle. Space should be earned with
   content, so the fourth row does the work and the rows sit at a normal size. */
.labs-specs { margin: var(--sp-24) 0 0; padding: 0; }
.spec-row {
  display: grid; grid-template-columns: 104px 1fr;
  align-items: baseline;
  gap: var(--sp-16);
  padding: var(--sp-12) 0;
  border-top: 1px solid var(--line);
}
.spec-row:last-child { border-bottom: 1px solid var(--line); }
.spec-row dt {
  font-size: var(--fs-caption2); font-weight: 600; line-height: 1.6;
  letter-spacing: 0.12em; text-transform: uppercase; color: var(--ink-3);
}
/* Values step up to bright Subhead against the whisper labels — the same
   label/value contrast the trends readout runs, tuned down half a step
   because these are phrases rather than figures. */
.spec-row dd {
  margin: 0;
  font-size: var(--fs-subhead); font-weight: 500; line-height: 1.45;
  letter-spacing: var(--tr-subhead); color: var(--ink);
}

/* z3, ABOVE the copy: the spec rows' hairlines run underneath the devices,
   so the phones read as objects lying on the panel's ruled surface rather
   than floating behind a printed table. pointer-events stays off, so the
   text under them remains selectable. */
.labs-stage { position: absolute; inset: 0; z-index: 3; pointer-events: none; }
/* Two nested elements on purpose. The WRAPPER carries position and is the only
   thing the scroll parallax animates; the inner .labs-device carries the tilt.
   Putting both on one node would mean the parallax's `y` and the CSS `rotate`
   fight over a single transform, which is the same one-property-two-owners bug
   that made the hero phone jolt on first scroll. */
.labs-stage .labs-phone { position: absolute; will-change: transform; }
/* Scoped under .labs-stage: a bare .labs-device ties .device on specificity
   (0,1,0) and .device is declared EARLIER, so --dw would silently lose to its
   280px default. */
.labs-stage .labs-device {
  /* Ties the devices to the card so the whole composition scales as one unit.
     cqw resolves against the container's CONTENT box, so this is a share of the
     card minus its padding, not of its full 1060px width. */
  --dw: clamp(196px, 28.5cqw, 270px);
  /* Both share one angle so they read as a single stack, not two loose objects.
     The scroll parallax derives its rail from this value at runtime, so the
     drift direction follows automatically whenever the angle is retuned. */
  transform: rotate(-25deg);
  /* Layered rather than one shadow: a tight contact shadow plus two soft casts
     is what reads as a real object lying at an angle above a surface. */
  filter:
    drop-shadow(0 2px 5px rgba(0, 0, 0, 0.45))
    drop-shadow(0 28px 55px rgba(0, 0, 0, 0.55))
    drop-shadow(0 64px 120px rgba(0, 0, 0, 0.40));
}
.labs-stage .labs-phone--front { left: 52%; top: 27%;  z-index: 2; }
.labs-stage .labs-phone--back  { left: 71%; top: -32%; z-index: 1; }

/* ---- The Intelligence panel -------------------------------------------------
   One device, oversized, running off the top AND bottom of the card, with the
   copy held on the right.

   The crop is vertical on purpose. Cutting the left edge would slice the start
   of every line of chat text, and legibility is the entire reason this clip is
   here. Cropping top and bottom costs nothing: those strips are the nav bar and
   the input bar. So the device can be enormous and still fully readable.

   The copy is not prose sitting next to a video. The clip has three distinct
   phases, and each beat below lights up as its phase plays (see polish.js), so
   the text works as a live legend for what is on screen. */
.ai-card {
  position: relative;
  overflow: hidden;
  display: grid;
  grid-template-columns: minmax(0, 0.98fr) minmax(0, 1.02fr);
  align-items: center;
  gap: clamp(24px, 3.4vw, 56px);
  max-width: 1150px;                 /* shares the card above's edge exactly */
  margin: var(--sp-20) auto 0;
  border-radius: 26px;
  border: 1px solid var(--line);
  background: linear-gradient(150deg, #191D24 0%, #111419 58%, #0D0F13 100%);
  box-shadow:
    0 1px 0 rgba(255, 255, 255, 0.05) inset,
    0 30px 70px rgba(0, 0, 0, 0.42);
  padding: clamp(30px, 3.6vw, 56px);
  /* Same clamp as .labs-card so the two panels are exactly the same height. */
  min-height: clamp(390px, 38vw, 500px);
  isolation: isolate;
}
/* Light behind the device, faded out well inside its own box so the card's
   overflow clip never cuts a visible band. */
.ai-card::before {
  content: "";
  position: absolute; z-index: 0; pointer-events: none;
  left: 4%; top: 50%; transform: translateY(-50%);
  width: 780px; height: 780px;
  background: radial-gradient(closest-side, rgba(96, 165, 250, 0.11), transparent 76%);
}

.ai-stage { position: relative; z-index: 1; align-self: stretch; }
.ai-card .ai-device {
  --dw: clamp(280px, 28vw, 410px);
  position: absolute;
  left: clamp(6px, 1vw, 16px);
  /* Top edge sits INSIDE the card and the device runs off the bottom only. The
     whole crop lands on the input bar rather than being split across the top
     chrome too, so the device reads as standing in the card instead of being
     a slice taken out of the middle of one. */
  top: 0;
  filter:
    drop-shadow(0 3px 8px rgba(0, 0, 0, 0.45))
    drop-shadow(0 34px 70px rgba(0, 0, 0, 0.55))
    drop-shadow(0 80px 140px rgba(0, 0, 0, 0.40));
}

.ai-copy { position: relative; z-index: 2; }
/* One kicker style for the section head and BOTH cards. Card two carried one and
   card one did not, which made a deliberate pair look unrelated. */
.kicker {
  display: block;
  font-size: var(--fs-caption2); font-weight: 600; line-height: var(--lh-ui);
  letter-spacing: 0.14em; text-transform: uppercase; color: var(--ink-3);
  margin-bottom: var(--sp-12);
}
.ai-copy h3 {
  font-size: var(--fs-title); letter-spacing: var(--tr-title);
  line-height: 1.1; font-weight: 650; margin-top: var(--sp-10);
}
.ai-lead {
  color: var(--ink-2); font-size: var(--fs-body);
  line-height: var(--lh-lead); letter-spacing: var(--tr-body);
  margin-top: var(--sp-12); max-width: 46ch;
}

/* ---- Synced beats ---- */
/* Tight on purpose: the panel has to finish at the same height as the card
   above, and with min-height already matched it is this list that decides
   whether it does. */
.ai-beats { list-style: none; margin: clamp(16px, 1.8vw, 24px) 0 0; padding: 0; }
.ai-beat {
  display: grid; grid-template-columns: 2px 1fr;
  gap: var(--sp-16);
  padding: var(--sp-6) 0;
}
/* The rule doubles as a progress track: the inner bar fills across the phase,
   so the list reads as a timeline rather than three static bullets. */
.beat-rule {
  position: relative; display: block;
  background: rgba(255, 255, 255, 0.09); border-radius: 2px;
}
.beat-rule i {
  position: absolute; inset: 0 0 auto 0; height: 0%;
  background: linear-gradient(180deg, #CFE0FF, #7FA9F0);
  border-radius: 2px;
  box-shadow: 0 0 12px rgba(150, 190, 255, 0.45);
}
.beat-text { display: block; }
.beat-text strong {
  display: block; font-size: var(--fs-subhead); font-weight: 650;
  letter-spacing: var(--tr-subhead); line-height: 1.3;
  color: var(--ink-3);
  transition: color 420ms var(--ease-out);
}
.beat-text em {
  display: block; font-style: normal;
  font-size: var(--fs-footnote); line-height: 1.5;
  letter-spacing: var(--tr-footnote); color: var(--ink-3);
  margin-top: var(--sp-4); max-width: 52ch;
  opacity: 0.55;
  transition: opacity 420ms var(--ease-out);
}
.ai-beat.is-live .beat-text strong { color: var(--ink); }
.ai-beat.is-live .beat-text em { opacity: 1; color: var(--ink-2); }

@media (prefers-reduced-motion: reduce) {
  /* No sync to follow, so every beat reads at full strength. */
  .beat-text strong { color: var(--ink); }
  .beat-text em { opacity: 1; color: var(--ink-2); }
  .beat-rule i { display: none; }
}

@media (max-width: 900px) {
  .ai-card {
    grid-template-columns: 1fr;
    min-height: 0;
    padding-top: 0;
  }
  /* Device first, cropped into a band, then the copy beneath it. The band has
     to CLIP: the device is ~440px tall against a ~270px band, so without this it
     ran straight down over the kicker, heading and body copy below it. */
  .ai-stage {
    order: -1;
    height: clamp(268px, 62vw, 340px);
    margin: 0 calc(clamp(30px, 3.6vw, 56px) * -1);
    overflow: hidden;
  }
  /* Centred, with air above the bezel.
     It was at `left: 6%` of a stage that bleeds 30px WIDER than the card on
     each side, so 6% landed outside the card's own left edge and the phone read
     as shoved sideways. And `top: 0` against a card whose top padding is zeroed
     here put the bezel hard against the card's top edge.
     Centred via left + a negative margin rather than translateX: GSAP owns the
     `transform` on this element for its scroll drift, and a CSS transform here
     would fight it (one transform, one owner). */
  .ai-card .ai-device {
    left: 50%;
    margin-left: calc(var(--dw) * -0.5);
    top: 30px;
  }
  .ai-card::before { left: 50%; transform: translate(-50%, -50%); }
}

@media (max-width: 900px) {
  /* Stacked: copy first, then the devices as a band beneath it. They keep the
     tilt and the bleed, just with far less overhang. */
  .labs-card { min-height: 0; }
  .labs-copy { min-height: 0; max-width: none; }
  .labs-body { margin-top: var(--sp-24); }
  .labs-stage {
    position: relative; inset: auto;
    height: clamp(300px, 62vw, 400px);
    margin: clamp(40px, 6vw, 48px) 0 calc(clamp(36px, 4.4vw, 68px) * -1);
  }
  /* The pair is tilted -25deg, which makes each phone's real footprint ~1.75x
     its own width: at 196px wide that is 346px across, inside a 342px card. Two
     of them at the same height could only ever collide, and they read as two
     objects bumping edge to edge rather than a stack.
     Sized down to the same tier as every other multi-device composition (44% of
     the card) and offset on BOTH axes, so the pair reads the way it does on
     desktop — one clearly in front and lower, one behind and higher, each
     cropped by the card. The back phone also loses its negative top: on desktop
     that lift is what puts the pair onto the spec rows on purpose, but here the
     copy sits ABOVE the stage, so it just printed a bezel over the last value. */
  .labs-stage .labs-device { --dw: 150px; }
  .labs-stage .labs-phone--front { left: 6%;  top: 30%; }
  .labs-stage .labs-phone--back  { left: 44%; top: 0; }
}

/* ---- The lab pair: a diagonal stack, sized to actually fit ----
   The earlier fault here was not the tilt, it was the tilt at the WRONG SIZE.
   A rotated element's real footprint is its bounding box, not its width: at
   150px wide and 306 tall, -25deg spans 150*cos25 + 306*sin25 = 265px. Two of
   those in a 265px stage had nowhere to go and collided by 201px, which reads
   as two objects jammed together rather than one in front of the other.

   Overlap is not the problem — overlap is the composition. Desktop overlaps
   too. What makes it read as depth rather than a collision is that the offset
   is DIAGONAL and the footprints leave room: front low and left, back high and
   right, so each phone's silhouette is legible against the other.

   Sized so that works. At 118px the bbox is 118*cos24 + 246*sin24 = 208px, and
   the pair is placed so the two bounding boxes span 298px centred in the 327px
   card. The vertical offset is what sells it: the stage is given enough height
   that the front phone can sit ~19% down while the back one starts at the top,
   putting a real diagonal between them instead of two phones at one height.

   transform-origin is centre, so each bbox overhangs its element by (208-118)/2
   = 45px per side. Every number below is derived through that, which is why the
   percentages look arbitrary — they are not, and they need re-deriving together
   if --dw or the angle changes. */
@media (max-width: 700px) {
  .labs-stage {
    /* Deliberately SHORTER than the pair needs, so the front phone runs off the
       bottom of the card and is cut by it. That crop is the point: a pair that
       terminates inside the card reads as a picture of two phones, one that
       leaves the frame reads as a composition continuing past it — the same
       thing desktop does with the top and bottom of its lab stack.

       Two things move when this number changes, which is why it is not a
       freehand value. Shrinking the stage by N shortens the CARD by N, but only
       lifts the front phone by 0.17N (its top is a percentage of this height),
       so the net crop gained is 0.83N. Going from 375 to ~334 buys ~34px of
       real crop and takes 41px off the card. */
    height: clamp(320px, 89vw, 385px);
    margin-bottom: 0;
  }
  .labs-stage .labs-device {
    /* Sized UP, and past what the card can hold on purpose. .labs-card is
       overflow:hidden with a 32px radius, so the outer edge of each phone crops
       against that curve — which is the same move desktop makes ("each cropped
       by the card"). A pair sized to fit entirely inside the card reads as two
       small objects placed in a box; a pair that runs off it reads as a real
       composition the card is a window onto.

       At 154px the bbox is 154 * 1.7622 = 271px, and the two sit 152px apart,
       so the cluster spans 423px in a 327px card — about 48px of crop per side.
       That sounds severe and is not: the tilt means each phone's actual body
       starts (271 - 154) / 2 = 58px inside its own bounding box, so the first
       48px being cut is almost entirely the empty rotated corner. The hardware
       itself only loses its outermost ~10px. */
    --dw: clamp(140px, 58cqw, 175px);
    /* Slightly shallower than desktop's -25deg. The same angle reads steeper at
       a third of the size, and the screen content starts to feel like it is
       sliding off the glass. */
    transform: rotate(-24deg);
    /* Keeps the layered cast — it is what makes the pair read as two objects at
       different depths rather than two flat cutouts. Scaled down with the
       hardware; the desktop blur radii are far too heavy at this size. */
    filter:
      drop-shadow(0 1px 3px rgba(0, 0, 0, 0.45))
      drop-shadow(0 14px 28px rgba(0, 0, 0, 0.55))
      drop-shadow(0 32px 60px rgba(0, 0, 0, 0.40));
  }
  /* 136px apart, up from 91.5 originally and 124 after the first widening.
     Two different overlaps matter here and they are easy to confuse. The
     ELEMENT overlap (unrotated wrappers, 138px each) is now 2px — effectively
     edge to edge. The VISUAL overlap is the bounding boxes, 240px each, so the
     silhouettes still cross by ~104px and the pair still reads as one in front
     of the other. That gap between the two numbers is why the tilt can be kept
     while the phones separate.

     Centred THROUGH the crop: the cluster's bbox spans 152 + 271 = 423px
     against a 327px card, so its left edge sits at 24 - 48 = -24px, and the
     element's own left is that plus (271 - 154) / 2 = 34.5px absolute, which is
     20.5px OUTSIDE the 265px stage = -7.7%. Back follows 152px later at 49.6%,
     landing its right edge the same 10px inside the card that the front one
     starts. Negative is correct, not a typo: the wrappers are absolutely
     positioned and the card, not the stage, is what crops them.

     These are the AT-REST positions. polish.js drifts both wrappers on scroll
     (that is what .labs-phone exists for, separately from .labs-device), so the
     rendered rects will not match these numbers at an arbitrary scroll offset —
     read getComputedStyle().left, not getBoundingClientRect(), when checking. */
  .labs-stage .labs-phone--front { left: -7.7%; top: 17%; }
  .labs-stage .labs-phone--back  { left: 49.6%; top: 0;   }
}

/* ============================================================
   Reviews
   ============================================================ */
#reviews { padding: var(--section-y) 0 0; overflow: hidden; }
.marquee-track {
  -webkit-mask-image: linear-gradient(to right, transparent, black 8%, black 92%, transparent);
          mask-image: linear-gradient(to right, transparent, black 8%, black 92%, transparent);
  /* The row is draggable, so say so. */
  cursor: grab;
  /* Horizontal gestures belong to the row; vertical ones stay with the page,
     so a touch drag never traps the user inside the reviews section. */
  touch-action: pan-y;
  /* Stops a drag across the cards from selecting their text. */
  user-select: none; -webkit-user-select: none;
}
.marquee-track:active { cursor: grabbing; }
.reviews-head { text-align: center; margin-bottom: clamp(28px, 4vh, 38px); }
.reviews-head h2 { margin-top: var(--sp-12); }
/* The proof pair: the two numbers the wall of quotes is evidence for. Set as a
   figure over its label with one hairline between them — the same two-up split
   the mobile menu row uses, so the page has one way of pairing things rather
   than a new device per section. Figures take the card-heading rank: large
   enough to be the claim, below the h2 that frames it. */
.proof-row {
  display: flex; align-items: center; justify-content: center;
  gap: clamp(24px, 4vw, 44px);
  margin-top: clamp(20px, 2.6vh, 30px);
}
.proof { display: flex; flex-direction: column; align-items: center; gap: 2px; }
.proof-n {
  display: inline-flex; align-items: center;
  font-size: clamp(24px, 2.5vw, 32px); font-weight: 650;
  letter-spacing: var(--tr-title); line-height: 1.05; color: var(--ink);
  font-variant-numeric: tabular-nums;
}
.proof-l {
  font-size: var(--fs-footnote); letter-spacing: 0.08em; text-transform: uppercase;
  line-height: var(--lh-ui); color: var(--ink-3);
}
.proof-div { width: 1px; height: 30px; background: var(--line); flex: 0 0 auto; }
/* Gutter deliberately equals the card's own side padding, so the rhythm across
   a row of cards is even: 20 padding | 20 gutter | 20 padding. It was 18 vs 20,
   two numbers close enough to look like a mistake rather than a relationship. */
.marquee { display: flex; gap: var(--sp-20); width: max-content; padding: var(--sp-10) 0; will-change: transform; }
.review-card {
  width: 340px; flex: none;
  position: relative;                    /* anchors the edge-light ring below */
  display: flex; flex-direction: column;
  background: linear-gradient(170deg, #1B1E23, #15171B 70%);
  border: 1px solid var(--line); border-radius: var(--r-card);
  /* Bottom is one step less than top: the .rc-foot rule already adds its own
     12px above the divider, so equal padding would read bottom-heavy. */
  padding: var(--sp-20) var(--sp-20) var(--sp-16);
  box-shadow: 0 1px 0 rgba(255, 255, 255, 0.05) inset;
  transition: transform var(--dur-fast) var(--ease-out), border-color var(--dur-fast) var(--ease-out);
}
.review-card:hover { transform: translateY(-4px); border-color: rgba(255, 255, 255, 0.16); }

/* ---- Pointer light on the card surfaces -----------------------------------
   Replaces the free-floating cursor glow. That was decoration drifting BEHIND
   the page that nothing responded to, which is why it read as a generic web
   effect no matter how well it was tuned.
   Here the light belongs to the material instead: each card's 1px edge catches
   a highlight from the direction the pointer actually is, and brightens as it
   approaches. It is the physical behaviour of a lit bevel, and it doubles as an
   affordance — the surface acknowledges you before you even reach it.

   The ring is a gradient clipped to the border by an xor mask: the element is
   filled edge to edge, then its own content-box is punched out, leaving light
   on the 1px frame only. --lx/--ly are the pointer's position in the CARD's
   local space, so every card is lit from its own angle rather than sharing one
   global position. */
.review-card::after,
.review-pop::after {
  content: "";
  position: absolute; inset: 0;
  border-radius: inherit;
  padding: 1px;
  background: radial-gradient(240px circle at var(--lx, 50%) var(--ly, 50%),
              rgba(206, 222, 255, 0.80), rgba(206, 222, 255, 0.10) 44%, transparent 72%);
  -webkit-mask: linear-gradient(#000 0 0) content-box, linear-gradient(#000 0 0);
          mask: linear-gradient(#000 0 0) content-box, linear-gradient(#000 0 0);
  -webkit-mask-composite: xor;
          mask-composite: exclude;
  opacity: var(--lit, 0);
  pointer-events: none;
  /* Only the fade is eased. Position must stay locked to the pointer with no
     transition, or the highlight lags behind the cursor and reads as rubbery. */
  transition: opacity 240ms var(--ease-out);
}
/* A faint interior wash so the light looks like it lands ON the card, not just
   around it. Far weaker than the edge — the bevel is what catches light. */
.review-card::before {
  content: "";
  position: absolute; inset: 0;
  border-radius: inherit;
  background: radial-gradient(300px circle at var(--lx, 50%) var(--ly, 50%),
              rgba(190, 212, 255, 0.055), transparent 68%);
  opacity: var(--lit, 0);
  pointer-events: none;
  transition: opacity 240ms var(--ease-out);
}
/* Both pseudo-elements are positioned, so without this the interior wash would
   paint OVER the card's own text. Lifting the real content one level keeps the
   light underneath it. */
.review-card > * { position: relative; z-index: 1; }
@media (prefers-reduced-transparency: reduce) {
  .review-card::after, .review-card::before, .review-pop::after { display: none; }
}
.rc-top { display: flex; justify-content: space-between; align-items: center; }
/* The card is the densest type cluster on the page: it previously ran
   12.5 / 11.5 / 14.5 / 13.5 / 12.5 / 11.5, six sizes inside 3px. It now uses
   four real steps — Subhead title, Footnote body, Caption name, Caption2 meta
   — so rank is legible at a glance instead of implied by half-pixels. */
.review-stars { color: var(--star); font-size: var(--fs-caption); line-height: 1; letter-spacing: 0.1em; }
.rc-date { font-size: var(--fs-caption2); line-height: var(--lh-ui); letter-spacing: var(--tr-caption2); color: var(--ink-3); font-variant-numeric: tabular-nums; }
.rc-title { font-size: var(--fs-subhead); line-height: 1.3; letter-spacing: var(--tr-subhead); font-weight: 650; color: var(--ink); margin-top: var(--sp-12); }
.review-text {
  /* Footnote tracking is 0: small text needs neutral-to-positive spacing, so it
     must not inherit the body's -0.011em down to this size. */
  font-size: var(--fs-footnote); color: var(--ink-2); line-height: 1.55;
  letter-spacing: var(--tr-footnote); margin-top: var(--sp-6);
  display: -webkit-box; -webkit-line-clamp: 5; -webkit-box-orient: vertical; overflow: hidden;
}
.rc-foot {
  display: flex; justify-content: space-between; align-items: center; gap: var(--sp-10);
  margin-top: var(--sp-16); padding-top: var(--sp-12); border-top: 1px solid var(--line);
}
.rc-name { font-size: var(--fs-caption); line-height: var(--lh-ui); letter-spacing: var(--tr-caption); font-weight: 500; color: var(--ink); }
.rc-country { font-size: var(--fs-caption2); line-height: var(--lh-ui); letter-spacing: var(--tr-caption2); color: var(--ink-3); display: inline-flex; align-items: center; gap: var(--sp-6); }
/* One step above its country label: emoji render optically smaller than text,
   so the flag needs the bump to sit level with 11px type. */
.rc-flag { font-size: var(--fs-footnote); line-height: 1; }

/* Floating expanded review (FLIP hover popover) */
.review-pop {
  position: fixed; z-index: 200; margin: 0; box-sizing: border-box;
  display: flex; flex-direction: column;
  background: linear-gradient(168deg, #22262D, #16181C 72%);
  border: 1px solid rgba(255, 255, 255, 0.16);
  /* Radius and padding both scale by the same ~1.18x the card itself grows
     (340 -> 400px), so the enlarged card keeps the resting card's proportions
     instead of just being the same card with more air. */
  border-radius: var(--r-pop);
  padding: var(--sp-24) var(--sp-24) var(--sp-20);
  box-shadow:
    0 1px 0 rgba(255, 255, 255, 0.10) inset,
    0 40px 90px rgba(0, 0, 0, 0.72),
    0 8px 24px rgba(0, 0, 0, 0.5);
  will-change: transform, opacity, filter;
  backface-visibility: hidden;
}
/* The expanded card steps every role up exactly ONE rung of the same scale
   (Footnote->Subhead, Subhead->Body, Caption2->Caption, Caption->Footnote)
   rather than inventing 14.5/16.5px one-offs, so the enlarged card keeps the
   resting card's internal proportions. */
.review-pop .review-text {
  -webkit-line-clamp: unset; display: block; overflow: visible;
  font-size: var(--fs-subhead); line-height: 1.6; letter-spacing: var(--tr-subhead);
  color: var(--ink-2); margin-top: var(--sp-8);
}
.review-pop .rc-title { font-size: var(--fs-body); letter-spacing: var(--tr-body); margin-top: var(--sp-16); }
.review-pop .rc-date { font-size: var(--fs-caption); letter-spacing: var(--tr-caption); }
.review-pop .rc-foot { margin-top: var(--sp-20); padding-top: var(--sp-16); }
.review-pop .rc-name { font-size: var(--fs-footnote); letter-spacing: var(--tr-footnote); }
.review-pop .review-stars { font-size: var(--fs-footnote); }
@media (prefers-reduced-transparency: reduce) { .review-pop { background: #1C1F24; } }

/* ============================================================
   CTA + footer
   ============================================================ */
#cta {
  /* The closer takes a DISPLAY-rank step, not the section step: it opens with
     the loudest heading on the page and deserves more air than a card head.
     Bottom padding stays 40 because the flat lay's masked fade provides the
     closing air. */
  position: relative; padding: clamp(168px, 26vh, 300px) 0 var(--sp-40); text-align: center;
  background:
    radial-gradient(46% 55% at 50% 100%, rgba(96, 165, 250, 0.07), transparent 72%),
    linear-gradient(180deg, var(--bg), #12151B);
}
#cta .wrap { position: relative; z-index: 2; }
#cta h2 { font-size: var(--fs-title-lg); letter-spacing: var(--tr-title-lg); }
#cta .cta-sub {
  /* Body, not Lead: this sits under a Title, whereas the hero sub sits under a
     Display, so it steps down with its heading rather than matching the hero. */
  font-size: var(--fs-body); color: var(--ink-2);
  line-height: var(--lh-lead); margin: var(--sp-16) auto var(--sp-32); max-width: 44ch;
}
.cta-row { display: flex; justify-content: center; align-items: center; gap: var(--sp-20); }
/* The terminal ask is the ONE custom button on the page: same pill + cta-token
   grammar as the nav CTA, stepped up a size because it closes the page. */
.cta-btn {
  display: inline-flex; align-items: center; gap: var(--sp-10);
  background: var(--cta); color: var(--cta-ink);
  font-size: var(--fs-callout); font-weight: 600; line-height: var(--lh-ui);
  letter-spacing: var(--tr-subhead);
  padding: var(--sp-16) 30px; border-radius: var(--r-pill);
  text-decoration: none;
  transition: opacity var(--dur-fast) var(--ease-out), transform 100ms ease-out;
}
.cta-btn:hover { opacity: 0.9; }
.cta-btn:active { transform: scale(0.97); }
.cta-btn .sfsym { width: 16px; height: auto; display: block; }
/* CTA flat lay: the hardware family laid out flat under the ask. The asset is
   graded to the page's obsidian at export (screens protected), so the long top
   fade + two-axis edge dissolve make the heading block and the photo read as
   one continuous surface rather than a pasted slab. */
/* The ask overlaps the scene: the flat lay pulls up under the heading block so
   the copy sits ON the surface the kit is lying on (its top fade is empty
   surface, so nothing collides). The wrap carries z-index to stay clickable. */
.cta-flatlay { margin: clamp(-48px, -2.5vw, -14px) auto 0; }
.cta-flatlay img {
  width: 100%; height: auto; display: block;
  -webkit-mask-image: linear-gradient(180deg, transparent 0, #000 34%, #000 86%, transparent 100%), linear-gradient(90deg, transparent 0, #000 14%, #000 86%, transparent 100%);
          mask-image: linear-gradient(180deg, transparent 0, #000 34%, #000 86%, transparent 100%), linear-gradient(90deg, transparent 0, #000 14%, #000 86%, transparent 100%);
  -webkit-mask-composite: source-in;
          mask-composite: intersect;
}

/* Footer: full sitemap grid (the Bevel/Flighty grammar on the dark system).
   Brand block holds the left, four link columns carry every public page, and
   the bottom row closes on socials + provenance. Link type matches the NAV
   links exactly (Subhead) — same component, one size. */
#footer { border-top: 1px solid var(--line); padding: clamp(56px, 8vh, 96px) 0 var(--sp-40); background: #0C0D0F; }
.footer-grid {
  display: grid;
  /* Track count MUST match the number of .footer-col elements in
     _partials/footer.html. Grid does not infer it from the children when the
     columns are declared explicitly: a stale 4 here against 3 columns leaves
     an empty track on the right and pulls the whole row off-centre. */
  grid-template-columns: 1.5fr repeat(3, minmax(0, 1fr));
  gap: var(--sp-40) var(--sp-24);
}
.footer-brand-col { display: flex; flex-direction: column; align-items: flex-start; gap: var(--sp-12); }
.footer-col { display: flex; flex-direction: column; align-items: flex-start; gap: var(--sp-16); }
.footer-col-title {
  font-size: var(--fs-subhead); font-weight: 600; line-height: var(--lh-ui);
  letter-spacing: var(--tr-subhead); color: var(--ink-3);
}
.footer-col a {
  font-size: var(--fs-subhead); line-height: var(--lh-ui); letter-spacing: var(--tr-subhead);
  color: var(--ink-2); text-decoration: none;
  transition: color var(--dur-fast) var(--ease-out);
}
.footer-col a:hover { color: var(--ink); }
/* Current page, same platinum step as the nav's. This is the ONLY location cue
   /privacy/ and /terms/ get — neither is in the nav — so the footer is doing
   real wayfinding work here, not decoration. Stamped per page by
   tools/sync-chrome.mjs; the shared partial carries no marker of its own. */
.footer-col a[aria-current="page"] { color: var(--ink); }
.footer-copy { font-size: var(--fs-caption); line-height: var(--lh-ui); letter-spacing: var(--tr-caption); color: var(--ink-3); }
.footer-bottom {
  margin-top: clamp(48px, 6vh, 72px);
  display: flex; justify-content: space-between; align-items: center; gap: var(--sp-16); flex-wrap: wrap;
}
.footer-social { display: flex; gap: var(--sp-20); }
.footer-social a { color: var(--ink-3); transition: color var(--dur-fast) var(--ease-out); }
.footer-social a:hover { color: var(--ink); }
.footer-social svg { width: 20px; height: 20px; display: block; }
/* Three link columns never divide into two. The old rule here dropped to a
   2-track grid, which laid Product and Company side by side and orphaned Legal
   onto a row of its own — a ragged L that read as a layout bug rather than a
   choice, and squeezed each column to 152px on a 375px screen.

   One track instead. The groups stack in source order and every link gets the
   full width, which is also what makes the 44px tap targets below usable: at
   152px the two-up columns put two independent tap targets within a thumb's
   width of each other. */
@media (max-width: 760px) {
  .footer-grid {
    grid-template-columns: minmax(0, 1fr);
    gap: var(--sp-32);
  }
  .footer-brand-col { grid-column: 1 / -1; }
}

/* Reduced motion: final states, no travel */
@media (prefers-reduced-motion: reduce) {
  *, *::before, *::after {
    animation-duration: 0.01ms !important;
    animation-iteration-count: 1 !important;
    transition-duration: 0.01ms !important;
  }
}

/* ============================================================
   Sub-pages (About / Legal / Contact)
   One shared scaffold: the landing page's nav and footer wrap a single prose
   column. The head takes the section-opener rank; body copy runs at Body on
   a 72ch measure; legal documents are the same system, just longer.
   ============================================================ */
.subpage { padding: clamp(150px, 20vh, 220px) 0 var(--section-y); }
/* A document, not a landing band: the column is CENTERED in the page rather
   than pinned to the left edge of the 1400px band. Left-pinning a 720px measure
   inside that band left the right half dead and read as text shoved sideways.
   The width still holds a ~72ch measure — the reading line stays correct, the
   air just sits evenly on both sides. */
.subpage .wrap { max-width: 900px; }
.page-head { margin-bottom: clamp(40px, 6vh, 72px); }
.page-head h1 {
  font-size: var(--fs-title-lg); letter-spacing: var(--tr-title-lg);
  line-height: var(--lh-display); font-weight: 650;
  margin-top: var(--sp-12);
  text-wrap: pretty;
}
.page-meta { margin-top: var(--sp-16); color: var(--ink-3); font-size: var(--fs-subhead); letter-spacing: var(--tr-subhead); line-height: var(--lh-ui); }
.page-sub { margin-top: var(--sp-16); color: var(--ink-2); font-size: var(--fs-lead); letter-spacing: var(--tr-lead); line-height: var(--lh-lead); }

/* ONE column, not a column inside a wider band: the head and the body share
   the same measure so the whole document is genuinely centred. A narrower
   measure inside a wider band left more air on the right than the left, which
   still read as text pushed sideways. The band width is set so the content
   lands at ~78ch — past ~80 characters the eye loses the line return. */
.prose { max-width: none; }
.prose h2 {
  font-size: var(--fs-title); letter-spacing: var(--tr-title);
  line-height: var(--lh-display); font-weight: 650;
  margin: clamp(48px, 7vh, 80px) 0 var(--sp-16);
}
/* Steps clear of the 19px body: at the Lead rank the two sat a single pixel
   apart and the subhead read as bold body text instead of a level. */
.prose h3 {
  font-size: clamp(19px, 1.6vw, 23px); letter-spacing: var(--tr-lead);
  line-height: var(--lh-ui); font-weight: 600; color: var(--ink);
  margin: var(--sp-40) 0 var(--sp-10);
}
/* Body steps up from the site's 17px Body rank. The column is wide enough that
   17px ran 93 characters per line; at 19px the same width reads ~83, which is
   inside the comfortable band. Size is the lever here, not measure — the
   document wanted to be wide. */
.prose p, .prose li {
  color: var(--ink-2);
  font-size: clamp(17px, 1.35vw, 19px); letter-spacing: var(--tr-body); line-height: 1.62;
}
.prose p { margin: var(--sp-16) 0 0; }
.prose ul, .prose ol { margin: var(--sp-16) 0 0; padding-left: 1.25em; }
.prose li { margin-top: var(--sp-8); }
.prose li::marker { color: var(--ink-3); }
.prose a { color: var(--ink); text-decoration: underline; text-underline-offset: 3px; text-decoration-color: var(--ink-3); transition: text-decoration-color var(--dur-fast) var(--ease-out); }
.prose a:hover { text-decoration-color: var(--ink); }
.prose strong { color: var(--ink); font-weight: 600; }

/* Contact's email actions: the site's glass chip, sized as a button. */
.mail-chip {
  display: inline-flex; align-items: center; gap: var(--sp-10);
  margin-top: var(--sp-16);
  padding: 12px 20px;
  border-radius: var(--r-pill);
  background: rgba(26, 30, 37, 0.58);
  border: 1px solid rgba(255, 255, 255, 0.14);
  -webkit-backdrop-filter: blur(14px) saturate(160%);
          backdrop-filter: blur(14px) saturate(160%);
  box-shadow: inset 0 1px 0 rgba(255, 255, 255, 0.08);
  color: var(--ink); font-size: var(--fs-subhead); font-weight: 600;
  letter-spacing: var(--tr-subhead); line-height: var(--lh-ui);
  text-decoration: none;
  transition: border-color var(--dur-fast) var(--ease-out);
}
.mail-chip:hover { border-color: rgba(255, 255, 255, 0.28); }
.prose .mail-chip { text-decoration: none; }

/* ============================================================
   MOBILE PASS
   Cross-cutting phone-width corrections. Grouped at the end deliberately:
   each rule below fixes a specific measured failure at 390px rather than
   restating the desktop system, so the desktop cascade stays readable.
   ============================================================ */

/* ---- Nav ---- */
/* The link row is hidden under 900px, which left the wordmark's divider
   pointing at nothing — a hairline with no second side. It goes with them. */
@media (max-width: 900px) {
  .nav-divider { display: none; }
}
/* Touch targets: the CTA measured 39px tall and the brand 32px. Padding does
   the work so the pill's own geometry (and the scroll morph) is untouched. */
@media (pointer: coarse) {
  .nav-cta { min-height: 44px; }
  .nav-brand { min-height: 44px; }
}

/* ---- Section rhythm ---- */
/* The desktop seam resolves to ~186px on a 844px-tall phone, which is a third
   of a screen of empty space at every join and turns the page into a scroll
   marathon. Phones get a proportional step down, still airy at ~100px. */
@media (max-width: 700px) {
  :root { --section-y: clamp(88px, 12vh, 132px); }
  #cta { padding-top: clamp(104px, 15vh, 148px); }
  .subpage { padding-top: clamp(112px, 15vh, 150px); }
}
@media (max-width: 640px) {
  /* The stage was carrying a full 88px above it on top of the CTA row's 48. */
  .hero-stage { margin-top: var(--sp-40); }
}

/* ---- Health trends: the device wall ---- */
/* The wall is two tall columns centred on the stage, so its own height (~900px)
   bled far above a 420px stage and printed the phones straight over the body
   copy — the card's clip sits too far out to catch it. The stage clips its own
   content, which is the same "window onto more" the cards already do. */
@media (max-width: 900px) {
  .vitals-stage { overflow: hidden; border-radius: 18px; }
  .vitals-copy { margin-bottom: var(--sp-8); }
}

/* ---- Spec tables (bloodwork) ---- */
/* A 104px label column against a phrase value leaves ~200px for the value, so
   every row wrapped to two lines and the table read as a wall. Stacked, the
   label becomes an eyebrow over its value: same information, one column. */
@media (max-width: 560px) {
  .spec-row {
    grid-template-columns: minmax(0, 1fr);
    gap: var(--sp-4);
    padding: var(--sp-12) 0 var(--sp-16);
  }
  .spec-row dd { text-align: left; }
}

/* ---- CTA flat lay ---- */
/* At 21:9 the full frame is only 165px tall on a phone, which shrinks the
   phone in the shot to ~53px and makes the whole scene unreadable. Cover-crop
   to the middle of the arrangement instead: the phone and watch roughly double,
   and the outer props fall away rather than being rendered as specks. The mask
   drops to vertical only, since the horizontal edges are now off-frame. */
@media (max-width: 700px) {
  .cta-flatlay { overflow: hidden; margin-top: var(--sp-20); }
  .cta-flatlay img {
    height: clamp(240px, 74vw, 330px);
    object-fit: cover;
    object-position: 47% 50%;
    -webkit-mask-image: linear-gradient(180deg, transparent 0, #000 22%, #000 86%, transparent 100%);
            mask-image: linear-gradient(180deg, transparent 0, #000 22%, #000 86%, transparent 100%);
    -webkit-mask-composite: source-over;
            mask-composite: add;
  }
}

/* ---- Footer + inline link touch targets ---- */
/* Footer links measured 19px tall. Padding grows the hit area to ~41px without
   moving the columns; the gap comes down so the rhythm stays even. */
@media (max-width: 760px) {
  .footer-col { gap: var(--sp-4); }
  .footer-col a { padding: 11px 0; }
  .footer-social a { padding: 10px; margin: -10px; }
  .footer-bottom { margin-top: clamp(32px, 5vh, 56px); }
}
@media (pointer: coarse) {
  .duo-link { padding-top: 10px; padding-bottom: 10px; }
}

/* Review copy sits at 13px on desktop, where the card is one of twelve moving
   past. On a phone the card is the whole width and gets read properly, so it
   steps up to the phone floor for body text. */
@media (max-width: 700px) {
  .review-text { font-size: 14.5px; }
  .rc-title { font-size: 16px; }
}

/* ---- Device scale ---- */
/* On desktop each mockup is sized against its own band, so a third-width
   pillar card and a full-width labs card can carry different device sizes and
   still read as one system. Below 900px every card collapses to the SAME
   width, so those band-relative sizes stop meaning anything: measured at
   390px the solo devices ran 77% / 76% / 61% of an identical 342px card while
   the multi-device compositions sat at 44%, which reads as random rather than
   hierarchical.
   Re-established by what the composition actually holds:
     solo hero device  ~63%  — an object in a card, with air either side
     tilted pair        57%  — two overlapping, already correct
     side-by-side pair  44%  — two abreast have to be smaller to both fit
     four-up wall       44%  — a wall of screens, smallest
   Only the solo tier needed correcting; the rest already agreed. */
@media (max-width: 900px) {
  .pillar .pillar-device,
  .ai-card .ai-device,
  .scan-card .scan-device { --dw: clamp(184px, 55vw, 228px); }
}

/* ============================================================
   MOBILE COMPOSITION
   Beyond the corrections above: the desktop compositions are built for a wide
   canvas, and a few of them stop being compositions at 390px. These are
   judgement calls about what the phone screen should hold, not bug fixes.
   ============================================================ */

/* ---- Spec lists: the screens already say it ----
   Both of these are label/value lists that restate what the phones beside them
   are showing. On desktop they sit next to the devices and read as a caption.
   Stacked on a phone they land ABOVE the screens as a wall of eight rows, so
   the same four numbers get read twice before the product is even visible.

   Hidden rather than deleted: the desktop layout is built around them, and the
   vitals rows are literally captions for the four screens ("Each row quotes the
   value on one of the four screens beside it"). display:none takes them out for
   assistive tech too, which is correct here — the values are still in the screen
   images' alt text, so nothing that was announced stops being announced. */
@media (max-width: 700px) {
  .vitals-specs,
  .labs-specs { display: none; }
  /* The body copy carried a top margin that assumed the list above it. */
  .labs-body { margin-top: var(--sp-16); }
}

/* ---- Hero: one phone, not a pair ----
   The pair used to be three separate objects here, so the narrow layout could
   drop the back phone and the watch and keep only the subject. It cannot now:
   both phones live in one render and neither can be hidden independently.
   The intent survives as scale instead: the render is sized off the stage, so
   the front phone still reads as the protagonist with the back one as depth
   behind it, rather than either being removed.

   BOTH layers are set together, and that is load-bearing. The render and its
   glow are two separate images that only line up because they are given the
   same height — the glow's scale(1.76) is calibrated against that, not against
   the stage. An earlier version of this rule set `.hero-phones` to 116% and
   left `.hero-phones-bleed` at its 106% default, which pulled the light 9% out
   of register with the hardware it was supposed to be coming from.

   116% also made the pair 362px wide on a 375px screen — 97% of the viewport,
   6px of air per side — and a 580px render in a 500px stage overhung 40px top
   and bottom, which #hero's overflow:hidden then sliced off. At 100% the pair
   lands at ~319px (85%), narrower than the 327px cards below it so the hero
   sits inside the same rhythm, and it fits the stage exactly so nothing clips.

   Measured, not guessed: hero-phones.webp is 1100x1726, a 0.6373 ratio, so
   width follows height directly. If that asset is ever re-exported at a
   different aspect, re-check this number. */
@media (max-width: 640px) {
  .hero-stage { height: 500px; }
  .hero-phones,
  .hero-phones-bleed { height: 100%; }
}

/* ---- Hero: get the product into the first screen ----
   135px of top padding plus a 40px eyebrow gap pushed the phone almost entirely
   below the fold, so the opening screen was four lines of body copy and a small
   badge. Reclaiming ~60px lets the device break the fold, which is what invites
   the scroll. */
@media (max-width: 640px) {
  #hero { padding-top: clamp(96px, 13vh, 124px); }
  .hero-eyebrow { margin-top: var(--sp-24); }
  .hero-sub { margin-top: var(--sp-24); }
  .hero-cta-row { margin-top: var(--sp-32); }
}

/* ---- Centred copy needs an even rag ----
   Every centred block on mobile (hero lead, section subs) was breaking into a
   ragged wedge — the widest line first, a two-word orphan last. Balancing the
   lines is what makes centred type look set rather than wrapped. */
@media (max-width: 700px) {
  .hero-sub, .section-head p, .cta-sub { text-wrap: balance; }
}

/* ---- Mobile menu ----
   Under 900px the links have nowhere to sit, so the nav becomes brand + a
   trigger, and everything else (links AND the download action) moves into a
   sheet. Three rules make it read as one object rather than a bolted-on panel:
   the sheet carries the pill's exact glass recipe, it scales out of its own
   trigger's top-right corner, and it materialises (blur + scale together)
   instead of fading, which is how a real surface arrives.
   Nothing here transitions on --p, so the scroll morph and the menu never
   contend for the same property. */
@media (max-width: 900px) {
  /* The wordmark stays for good at this width. Folding it into a hairline is a
     desktop move that buys back horizontal room for a link row; with no links
     there is nothing to buy, and a lone icon gives the brand nowhere to read. */
  .nav-brand-word { max-width: none; opacity: 1; margin-left: var(--sp-8); }
  .nav-cta { display: none; }

  /* ---- A bar, not a pill ----
     The hugged pill is a desktop shape: it works because there is empty canvas
     either side of it. At 375px there is none, so it reads as a floating slab
     with two slivers of page showing past its corners. Squared off and run
     edge to edge instead, which is what a phone nav is.

     #nav's own padding goes with it — that inset is what was holding the pill
     off the edges, so leaving it would produce a full-width bar that still
     stopped 24px short on both sides. The pill takes the padding back as its
     own, so the brand and burger keep the same inset from the screen edge that
     they had from the pill edge. */
  #nav { padding: 0; }
  .nav-pill {
    justify-content: space-between;
    max-width: none;
    width: 100%;
    border-radius: 0;
    padding: 14px var(--pad-x);
  }

  /* ---- The bar must not be blurred by its own scrim ----
     .nav-scrim is position:fixed, inset:0, and carries backdrop-filter. It is
     z-index 1; the pill had no z-index at all, so the scrim painted OVER it and
     blurred the wordmark and the burger along with the page — the bar went soft
     the moment the menu opened.

     Ordering the three layers explicitly fixes it: scrim (1) below, sheet (2)
     above it, bar (3) on top of both. The bar is the one thing that must stay
     legible while the menu is open, since it holds the control that closes it.

     z-index alone is enough here because .nav-pill is a flex item, and flex
     items honour z-index without being positioned — but position:relative is
     declared anyway so this does not quietly stop working if #nav ever stops
     being a flex container. */
  .nav-pill { position: relative; z-index: 3; }
  .nav-sheet { z-index: 2; }
  .nav-scrim { z-index: 1; }

  /* Keep the bar's OWN contents sharp; only what passes behind it blurs.
     backdrop-filter is supposed to do exactly that, but the filter plus a
     translucent background puts the pill's children on the blurred surface's
     own compositor layer, and iOS resamples them with it — so the wordmark and
     the burger pick up a softness they were never meant to have. Promoting each
     to its own layer takes them off that surface and back to crisp text and
     hairlines, with the page still blurring underneath.

     translateZ(0) rather than will-change: this is a permanent promotion, not a
     hint for an animation about to start, and will-change left on forever is
     what the property is explicitly documented not to be for.

     Only .nav-brand here. The matching .nav-burger rules CANNOT live in this
     block — see the note above the burger's own media query further down. */
  .nav-brand { transform: translateZ(0); }
}

/* The trigger: two rules, 44px of tap area, no chrome of its own — it sits on
   the pill's glass and needs no second surface under it. */
.nav-burger {
  display: none;
  align-items: center; justify-content: center;
  width: 44px; height: 44px;
  margin: -5px -6px -5px 0;      /* keeps the pill's height, not the button's */
  padding: 0; border: 0; background: none;
  color: var(--ink); cursor: pointer;
  -webkit-tap-highlight-color: transparent;
}
.nav-burger-i { position: relative; display: block; width: 18px; height: 12px; }
.nav-burger-i i {
  position: absolute; left: 0; width: 100%; height: 1.5px;
  border-radius: 2px; background: currentColor;
  transition: transform 0.34s var(--ease-out), opacity 0.2s var(--ease-out);
}
.nav-burger-i i:first-child { top: 0; }
.nav-burger-i i:last-child  { bottom: 0; }
/* Open: the two rules cross. Each one travels to the centre and rotates, so the
   x is built from the same two objects rather than swapped for an icon. */
#nav.menu-open .nav-burger-i i:first-child { transform: translateY(5.25px) rotate(45deg); }
#nav.menu-open .nav-burger-i i:last-child  { transform: translateY(-5.25px) rotate(-45deg); }
/* Declared after the base rule above, not with the other mobile overrides: both
   selectors are .nav-burger, so source order is the only thing that decides
   which display wins. Every .nav-burger override belongs HERE for that reason —
   putting the two below up with the rest of the mobile nav rules is exactly the
   mistake this note exists to prevent, and it silently lost to the base
   shorthand's `margin` when tried. */
@media (max-width: 900px) {
  .nav-burger { display: inline-flex; }

  /* Off the pill's right edge. The base shorthand's -6px pulled the button into
     the 10px inset, leaving 4px of air against a wordmark inset by 24px, which
     read as jammed into the corner. Zeroed, so it keeps the full inset.
     Only the right margin is touched: the vertical -5px is what holds the pill's
     height to the wordmark instead of to the 44px tap target. */
  .nav-burger { margin-right: 0; }

  /* Same layer promotion as .nav-brand above: keeps the hairlines crisp instead
     of being resampled with the pill's blurred backdrop. */
  .nav-burger { transform: translateZ(0); }
}

/* Desktop: links range right, next to the CTA, instead of being centred in the
   bar. With three children and space-between, the middle one lands mid-bar and
   reads as a third cluster; an auto margin absorbs the free space ahead of it so
   the bar becomes brand-left / everything-else-right — two groups, not three. */
@media (min-width: 901px) {
  .nav-links { margin-left: auto; }
}

.nav-sheet {
  position: absolute;
  top: 100%; right: var(--pad-x);
  margin-top: var(--sp-8);
  width: min(300px, calc(100vw - var(--pad-x) * 2));
  pointer-events: none;
  z-index: 2;
  padding: var(--sp-8);
  border-radius: 22px;
  /* The pill's material, one step more opaque: this surface has content behind
     it that must stay legible, and a menu should read as nearer than the bar. */
  background: rgba(21, 23, 26, 0.82);
  backdrop-filter: blur(24px) saturate(170%);
  -webkit-backdrop-filter: blur(24px) saturate(170%);
  box-shadow:
    0 1px 0 rgba(255, 255, 255, 0.08) inset,
    0 22px 60px rgba(0, 0, 0, 0.55);
  /* Grows out of the trigger, not from its own middle. */
  transform-origin: top right;
  opacity: 0;
  transform: scale(0.94) translateY(-8px);
  filter: blur(6px);
  visibility: hidden;             /* also takes the rows out of the tab order */
  transition:
    opacity 0.26s var(--ease-out),
    transform 0.34s var(--ease-out),
    filter 0.26s var(--ease-out),
    visibility 0.34s;
}
#nav.menu-open .nav-sheet {
  opacity: 1; transform: none; filter: blur(0px);
  visibility: visible; pointer-events: auto;
}
/* Two destinations on one row, split by a hairline, each taking half the width.
   The row therefore measures exactly the same as the Download button beneath it,
   so the sheet reads as two stacked bands of equal width rather than a list of
   short labels floating above a full-width button. */
.nav-sheet-links { display: flex; flex-direction: row; align-items: center; }
.nav-sheet-div {
  flex: 0 0 auto; width: 1px; height: 20px;
  background: rgba(255, 255, 255, 0.14);
}
.nav-sheet-links a {
  flex: 1;
  display: flex; align-items: center; justify-content: center; text-align: center;
  min-height: 48px; padding: 0 var(--sp-12);
  border-radius: 14px;
  font-size: var(--fs-callout); font-weight: 500;
  letter-spacing: var(--tr-callout); line-height: var(--lh-ui);
  color: var(--ink-2); text-decoration: none;
  transition: background-color var(--dur-fast) var(--ease-out), color var(--dur-fast) var(--ease-out);
}
.nav-sheet-links a:hover, .nav-sheet-links a:active { background: rgba(255, 255, 255, 0.05); color: var(--ink); }
/* In the sheet the chip is filled rather than just brightened: a menu row that
   stays lit reads as "selected", which is exactly what it is. */
.nav-sheet-links a[aria-current="page"] { background: rgba(255, 255, 255, 0.05); color: var(--ink); }
.nav-sheet-cta {
  display: flex; align-items: center; justify-content: center; gap: var(--sp-8);
  min-height: 48px; margin-top: var(--sp-8);
  border-radius: 14px;
  background: var(--cta); color: var(--cta-ink);
  font-size: var(--fs-subhead); font-weight: 600;
  letter-spacing: var(--tr-subhead); line-height: var(--lh-ui);
  text-decoration: none;
  transition: opacity var(--dur-fast) var(--ease-out);
}
.nav-sheet-cta:hover { opacity: 0.9; }
.nav-sheet-cta .sfsym { height: var(--sp-16); width: auto; }
/* The rows arrive just behind the surface they are on, in order. */
.nav-sheet-links a, .nav-sheet-cta {
  opacity: 0; transform: translateY(-6px);
  transition:
    opacity 0.3s var(--ease-out), transform 0.3s var(--ease-out),
    background-color var(--dur-fast) var(--ease-out), color var(--dur-fast) var(--ease-out);
}
#nav.menu-open .nav-sheet-links a, #nav.menu-open .nav-sheet-cta { opacity: 1; transform: none; }
#nav.menu-open .nav-sheet-links a:nth-child(1) { transition-delay: 0.05s; }
#nav.menu-open .nav-sheet-links a:nth-child(2) { transition-delay: 0.09s; }
#nav.menu-open .nav-sheet-links a:nth-child(3) { transition-delay: 0.13s; }
#nav.menu-open .nav-sheet-cta { transition-delay: 0.17s; }

/* A dim, tappable plate behind the sheet: it gives the menu focus and makes the
   whole rest of the page the dismiss target. */
.nav-scrim {
  position: fixed; inset: 0; z-index: 1;
  background: rgba(8, 9, 11, 0.5);
  -webkit-backdrop-filter: blur(2px);
          backdrop-filter: blur(2px);
  opacity: 0; visibility: hidden; pointer-events: none;
  transition: opacity 0.3s var(--ease-out), visibility 0.3s;
}
#nav.menu-open .nav-scrim { opacity: 1; visibility: visible; pointer-events: auto; }
/* Open state lifts the bar itself, so bar and sheet read as one assembly. */
#nav.menu-open .nav-pill {
  background: rgba(21, 23, 26, 0.82);
  backdrop-filter: blur(24px) saturate(170%);
  -webkit-backdrop-filter: blur(24px) saturate(170%);
}
@media (prefers-reduced-motion: reduce) {
  .nav-sheet, .nav-sheet-links a, .nav-sheet-cta, .nav-scrim, .nav-burger-i i {
    transition-duration: 0.01ms !important;
  }
  .nav-sheet { filter: none; transform: none; }
}
@media (prefers-reduced-transparency: reduce) {
  .nav-sheet, #nav.menu-open .nav-pill { background: #15171A; backdrop-filter: none; -webkit-backdrop-filter: none; }
}

/* ---- Safe areas ----
   The nav sits under the status bar / Dynamic Island when the site is opened
   from a home-screen bookmark; the sticky pill already respects the home
   indicator.

   The inset lives on the BAR'S PADDING, not on #nav's. Those are the same thing
   for a floating pill, and opposite things for a full-bleed bar: pushing #nav
   down leaves a strip of bare page above the bar, so the page scrolls through
   the gap beside the Dynamic Island. Padding the bar instead lets its
   background run to the very top of the screen while the wordmark and burger
   still sit clear of the island.

   This block must stay AFTER the mobile nav rules above — it is the same
   `#nav` / `.nav-pill` specificity, so source order is what decides. */
@media (max-width: 900px) {
  #nav { padding-top: 0; }
  .nav-pill { padding-top: calc(14px + env(safe-area-inset-top, 0px)); }
}
