/* ============================================================
   rawcloser.com — Landing page (Phase 7.A.2)
   HERO + THE PROBLEM (§2). Remaining sections 3–10 are built one at
   a time in later sub-phases; their styles are added here as each ships.

   Page-specific rules ONLY. Anything shared with future pages (nav,
   footer, buttons, card, section wrapper, brand/logo) lives in
   components.css. Consumes tokens.css only — never hardcode a color,
   spacing, radius, type, shadow, or motion value here.

   Note on raw numbers: three appear below, none a token violation.
   (1) the 60rem breakpoint (as a min-width query for the two-column
   layout, and its max-width complement for the mobile media cap — one
   structural dim, used both ways); (2) the hero image's 1/1 aspect-ratio (a structural dim
   matching the square asset — the token set defines no aspect token);
   (3) the hero fade duration, a raw ms motion value used because it
   runs a step longer than --dur-slower, the longest duration token
   (commented at its declaration, same as the rise-distance value).
   The token set defines no breakpoint, aspect, or longer-duration
   token, so these raw structural/motion values are correct here.
   ============================================================ */

/* Fixed-nav anchor offset: keep a jumped-to section heading clear of the top
   chrome. Held at --space-9 (7.A.13): the progress line is no longer inside the
   header, but it still floats fixed near the top (top:--space-8, below the nav),
   so a jumped-to heading must clear the fixed nav AND that floating line — the
   --space-9 offset does both. */
[id]{
  scroll-margin-top:var(--space-9);
}

/* ---------- Damped-scroll wrapper (7.A.11) ---------- */
/* .smooth wraps <main> + <footer> and is the ONLY element smooth.js
   transforms. It stays in NORMAL DOCUMENT FLOW (no fixed positioning, no
   height spacer) so the page keeps its real scrollable height and every
   native scroll affordance — scrollbar, keyboard/trackpad, find-in-page,
   Tab-to-focus, and #anchor jumps — keeps working; smooth.js only translates
   it by the small scroll LAG so the page glides to a halt when you stop.
   will-change:transform is the compositor hint (brief: on the wrapper only).
   The sticky nav/progress <header> is a SIBLING, never inside .smooth, so the
   transform never touches its sticky positioning. Under reduced motion
   smooth.js applies no transform at all, so the compositor hint is dropped. */
.smooth{
  will-change:transform;
}
@media (prefers-reduced-motion: reduce){
  .smooth{
    will-change:auto;
  }
}

/* ---------- Section progress line (7.A.13) ---------- */
/* The decorative sales-call progress line (Log.md §1.9 interaction model):
   a straight horizontal track with one node per landing section (nine),
   completed nodes filled, the current node enlarged, upcoming nodes inactive
   — the same read as the app's Selling Dashboard phase timeline, rebuilt here
   in the site's OWN tokens (never imported from the app).

   INDEPENDENT OF THE NAV (7.A.13). It is NO LONGER a child of <header>: in the
   markup it is a SIBLING of the header and the .smooth wrapper, and here it is
   its own position:fixed element. So it never inherits the header's
   hide-on-scroll-down opacity/transform — once progress.js activates it (from the
   problem section on) it stays VISIBLE AT ALL TIMES, including while the nav is
   hidden on scroll-down. Its top is a FIXED offset (--space-8) that clears the
   nav in BOTH its full and compact states, so the two never overlap and the line
   never has to move up/down as the nav comes and goes — it holds still; ONLY its
   own opacity fades at the section-1 boundary. z-index sits one step below the
   nav so, in the brief scroll window where a full-size nav and the active line
   coexist, the nav still wins the (non-)overlap.

   OWN GLASS PILL (7.A.13). The line + nodes sit inside a self-contained blurred
   translucent pill (.progress-inner) — the SAME --glass-bg + blur family as the
   nav hover pills (components.css .nav-links a:hover) — capped to a narrow 30rem
   measure and centred, with --radius-pill plus a hairline glass border and a
   small shadow so it reads as a quiet pill FLOATING over the content, not a
   full-width strip. pointer-events:none keeps the floating pill from intercepting
   clicks on the content beneath it.

   progress.js drives it via IntersectionObserver: it toggles .is-active on the
   bar (hidden over the hero, shown from the problem section on), sets --progress
   (0..1) on the track for the fill width, and toggles .is-done / .is-current on
   the nodes. The hidden/lit states are applied by JS, so with no JS the bar
   simply never activates and stays invisible; no page content is gated on it.

   SIZE: deliberately SMALL and quiet — a 6px node, a 1px line, the 3px = half-node
   insets, capped to a narrow 30rem measure and centred. The reduced scale from
   the previous pass is kept. The done nodes + fill use --color-text-secondary
   (restrained weight); only the single CURRENT node carries full --color-text.
   The raw px are structural motif dims — the same latitude the hero aspect-ratio
   and the how-it-works motif sizes take; none is a token-governed value. */
.progress{
  /* Independent fixed pill, pinned near the top of the viewport, decoupled from
     the header entirely (7.A.13). top:--space-8 (64px) clears the FULL nav (~56px)
     as well as the compact one, so nav and line never overlap in any state. The
     side padding keeps the pill off the screen edges on narrow viewports; the
     inner pill is centred within it. */
  position:fixed;
  top:var(--space-8);
  left:0;
  right:0;
  z-index:calc(var(--z-nav) - 1);
  padding-inline:var(--container-pad-x);
  pointer-events:none;
  /* Hidden while the hero is in view; progress.js adds .is-active from the
     problem section on. ONLY opacity toggles — never a translate — so the line
     holds its fixed position and does not drift as the nav hides/shows. Opacity
     is a visual-effect value (not token-governed), latitude per 7.A.1. Fades on
     --dur-slow / --ease-gentle; --dur-slow is zeroed under prefers-reduced-motion
     (base.css), so it snaps in. */
  opacity:0;
  transition:opacity var(--dur-slow) var(--ease-gentle);
}
.progress.is-active{
  opacity:1;
}

/* The pill: caps + centres the track to a NARROW measure (30rem) — deliberately
   shorter than the content width so it reads as a self-contained pill, never an
   edge-to-edge strip — and carries the blurred glass surface behind the line and
   nodes. The blur radius (10px) matches the nav hover pills; it is a visual-effect
   value, 7.A.1 latitude. The padding gives the pill a body around the thin track.
   30rem is a structural motif dim. */
.progress-inner{
  max-width:30rem;
  margin-inline:auto;
  padding-block:var(--space-2);
  padding-inline:var(--space-5);
  background:var(--glass-bg);
  backdrop-filter:blur(10px);
  -webkit-backdrop-filter:blur(10px);
  border:1px solid var(--glass-border);
  border-radius:var(--radius-pill);
  box-shadow:var(--shadow-sm);
}

.progress-track{
  position:relative;
  height:6px;                        /* = node diameter (structural motif dim) */
}

/* Base (unfilled) line. Inset half a node each side (3px) so its ends sit under
   the first/last node centres rather than the track edges. */
.progress-track::before{
  content:"";
  position:absolute;
  left:3px;
  right:3px;
  top:50%;
  height:1px;
  transform:translateY(-50%);
  background:var(--color-border);
  border-radius:var(--radius-pill);
}

/* Filled portion. --progress (0..1, set by progress.js) drives the width:
   left starts at the first node centre (3px) and (100% - 6px) is the exact
   span between the first and last node centres, so the fill's right edge lands
   precisely on the current node. Restrained weight (--color-text-secondary,
   not --color-text). Snaps under reduced motion (--dur-slow -> 0). */
.progress-fill{
  position:absolute;
  left:3px;
  top:50%;
  height:1px;
  width:calc((100% - 6px) * var(--progress, 0));
  transform:translateY(-50%);
  background:var(--color-text-secondary);
  border-radius:var(--radius-pill);
  transition:width var(--dur-slow) var(--ease-gentle);
}

/* The nodes sit above the base line + fill, evenly distributed end to end. */
.progress-nodes{
  position:relative;
  display:flex;
  justify-content:space-between;
  align-items:center;
  height:100%;
}

.progress-node{
  width:6px;
  height:6px;
  border-radius:var(--radius-pill);
  background:var(--color-surface);
  border:1px solid var(--color-border);
  /* Colour + scale ease on tokens that base.css zeroes under reduced motion,
     so a reduced-motion visitor still sees the correct lit state — it just
     snaps rather than animating. */
  transition:background var(--dur-base) var(--ease-gentle),
             border-color var(--dur-base) var(--ease-gentle),
             transform var(--dur-base) var(--ease-gentle);
}

/* Passed sections: filled at the restrained secondary weight. */
.progress-node.is-done{
  background:var(--color-text-secondary);
  border-color:var(--color-text-secondary);
}

/* Current section: full-weight and gently enlarged — the one node that reads
   at full --color-text, so the leading edge pops against the quieter trail. */
.progress-node.is-current{
  background:var(--color-text);
  border-color:var(--color-text);
  transform:scale(1.5);
}

/* Mobile: nine nodes cannot render legibly across a narrow strip, so below the
   site's shared 60rem breakpoint the individual nodes are DROPPED and only the
   filling LINE is kept (Log.md 7.A.11 brief). --progress still drives the fill,
   so the line still reflects scroll position. */
@media (max-width: 59.999rem){
  .progress-nodes{
    display:none;
  }
}

/* ---------- 1. Hero ---------- */
/* The hero fills the fold and centres its content, so on a laptop viewport the
   copy + MacBook read as vertically centred rather than pushed to the bottom of
   a tall section (7.A.2 changes 1+2). svh keeps mobile browser chrome from
   inflating the height; the plain-vh line above it is the fallback for engines
   without svh. Full 100vh/100svh now (7.A.12): the nav is position:fixed and no
   longer reserves flow height, so the hero fills the whole viewport (the earlier
   `- --space-9` compensated for the sticky nav's flow height and would now let
   section 2 peek at the fold). The transparent fixed nav overlays the hero top;
   the content stays vertically centred and clears it. The default section
   padding-block-start (the full --space-section-y ceiling) left a large dead band
   above the H1 — the hero has no section above it — so it does NOT use the full
   ceiling; it sits at --space-9 (96px) here, opened up from --space-7 on request
   for more blank space above the hero content (below the transparent fixed nav).
   The section-y bottom padding is kept as the rhythm gap down to section 2. Both
   are existing tokens, no raw px. */
/* Bottom padding trimmed --space-section-y -> --space-8 (7.A.2 nav polish
   change 4), then opened back up --space-8 -> --space-9 (round 4b): the hero
   and the problem section were EACH reserving a full fluid section-height and
   stacked into too large a gap, so both were capped; --space-8 then sat them
   too close, so the hero's share is lifted to --space-9 to add more breathing
   room between the two. Round 5 widens the gap further: the hero's bottom is
   restored to the full fluid --space-section-y while the problem's top stays
   capped at --space-9 — so only ONE side runs the full section-height, which
   adds the extra space asked for without returning to the oversized both-full
   stack. Existing token, no raw px. */
/* SHARED WITH /waitlist SECTION 1 (7.A.13). The waitlist page's first section
   (.wl-intro) MIRRORS this hero box exactly — same fold-filling, vertically
   centred frame, same --space-9 top / --space-section-y bottom rhythm — so the
   two pages' first sections read as one layout system. It is added to the
   selector rather than copied into waitlist.css: one definition, two consumers.
   The hero's own declarations are unchanged, so the hero renders identically. */
/* ALSO SHARED WITH /tester SECTION 1 (7.A.18). Same deal, third consumer: the
   tester page's first section (.ts-intro) takes the identical fold-filling,
   vertically centred frame and the same --space-9 top / --space-section-y bottom
   rhythm, so all three first sections are literally one box definition. The tester
   page reuses the WHOLE system, not just this box — its grid, media column, type
   steps and on-load stagger all join the shared selectors below, so its section 1
   is the waitlist's section 1 with a different right-hand visual (a CSS/SVG motif
   instead of a MacBook shot). Declarations unchanged again; hero and waitlist
   render identically. */
.hero,
.wl-intro,
.ts-intro{
  display:flex;
  flex-direction:column;
  justify-content:center;
  min-height:100vh;
  min-height:100svh;
  padding-block-start:var(--space-9);
  padding-block-end:var(--space-section-y);
}

/* Mobile-first: single column. The hero has no .container wrapper (see
   index.html), so the grid supplies its own page gutters here; the
   desktop rule below replaces them with the asymmetric inset.
   REVERSED 7.A.30: below this breakpoint the hero MacBook now SHOWS too,
   same as the waitlist shot and the tester motif — all three split only
   on SIZE (each capped to a smaller width in its own page file:
   .hero-shot below, waitlist.css §3, tester.css §4), never on
   visibility anymore. This ships the hero at FULL EXPORT WEIGHT on
   phones — a DELIBERATE trade (a visible hero beats a fast blank space
   on the page that converts), not an oversight: the four theme/DPI
   exports run 338-364KB each, and both theme <picture> elements sit in
   the DOM at once with only the inactive one display:none, so this
   costs roughly 710KB of eager WebP, accepted on purpose (7.A.27's
   payload HOLD is reversed; index.html's .hero-media comment has the
   full reasoning). Resized mobile sources plus eliminating the theme
   double-fetch would cut that cost — OUTSTANDING debt, logged at
   86f6c89, not a reason this is hidden. */
/* SHARED WITH .wl-grid (7.A.13): mobile-first single column. Neither the hero
   nor the waitlist section uses a .container — the grid supplies its own page
   gutters here (the desktop rule below swaps them for the centred inset block),
   so both pages inset to the same edge. /tester joins as a third consumer in
   7.A.18 — same reason, same gutters. */
.hero-grid,
.wl-grid,
.ts-grid{
  display:grid;
  gap:var(--space-8);
  align-items:center;
  grid-template-columns:1fr;
  padding-inline:var(--container-pad-x);
}

/* Mobile media position, Phase 7.A.31: the MacBook shot must sit directly
   after the H1 and before the subhead/CTAs on phones -- HERO ONLY, .wl-grid
   and .ts-grid are untouched. The desktop grid (min-width:60rem below) places
   .hero-copy and .hero-media in its two columns purely by DOM order, with no
   order property today (verified by grepping every order rule in this file --
   the only hits are the How It Works rows) -- so the markup cannot move
   without risking that column split. Solved with `order` instead, scoped to
   below 60rem only: .hero-copy is unboxed with display:contents so its
   children (h1, subhead, actions, offer, req) become direct items of
   .hero-grid alongside .hero-media, and order then sequences H1 (default
   order 0) -> .hero-media (1) -> subhead/actions/offer/req (2, tied, so they
   keep their own relative order) -- without moving a single element in the
   DOM. .hero-copy carries no rules of its own anywhere else in this file, so
   unboxing it here changes nothing else about it. At >=60rem there is no
   override, so .hero-copy reverts to a normal block box and the desktop grid
   keeps exactly the two source-ordered columns it always had. */
@media (max-width: 59.999rem){
  .hero-copy{
    display:contents;
  }
  .hero-media{
    order:1;
  }
  .hero-subhead,
  .hero-actions,
  .hero-offer,
  .hero-req{
    order:2;
  }
}

/* SHARED heading type scale (7.A.13; /tester joins 7.A.18): the waitlist and
   tester H1s take the SAME --text-6xl step as the hero H1 — one type system
   across all three first sections. */
.hero h1,
.wl-title,
.ts-title{
  font-size:var(--text-6xl);
}

/* SHARED supporting-line treatment (7.A.13; /tester joins 7.A.18): the waitlist
   and tester subheads match the hero subhead exactly — size, secondary tone,
   prose measure, and the --space-5 gap below the heading. */
.hero-subhead,
.wl-subhead,
.ts-subhead{
  font-size:var(--text-lg);
  color:var(--color-text-secondary);
  max-width:var(--measure-prose);
  margin-top:var(--space-5);
}

/* The CTA stands alone now (7.A.15): the secondary link that used to share this
   row was removed. Kept as `display:flex` (not a plain block) so the single
   inline-flex button stays a flex item — the container hugs its exact height with
   no inline line-box descender space beneath it, keeping the gap to .hero-offer
   clean. The old flex-wrap / gap / align-items governed the button+link pair and
   are dead with one child, so they were dropped. */
.hero-actions{
  display:flex;
  margin-top:var(--space-6);
}

/* Widen the lone CTA (7.A.15): more horizontal presence now that it no longer
   shares the row, opening the base .btn's --space-5 inline padding to --space-7.
   Width/prominence only — height (padding-block), the pill radius and the primary
   fill all stay the base .btn's. Not a full-bleed stretch: the button still sizes
   to its label + padding (~236px), well within the mobile copy column. */
.hero-actions .btn{
  padding-inline:var(--space-7);
}

/* Early-access offer line under the CTA (7.A.15). Small + muted so it stays
   secondary to the widened button, but a step MORE legible than the requirements
   fine-print below it (--color-text-secondary vs the req line's tertiary) — it is
   an offer, not a system note. Hugs the button (--space-4) so it reads as part of
   the CTA; the req line keeps its own --space-4 beneath it. On-load fade-and-rise
   via the shared hero-rise stagger (added to that list + its reduced-motion guard,
   with a beat below). */
.hero-offer{
  font-size:var(--text-sm);
  color:var(--color-text-secondary);
  margin-top:var(--space-4);
}

.hero-req{
  font-size:var(--text-sm);
  color:var(--color-text-tertiary);
  margin-top:var(--space-4);
}

/* ----- Hero load fade-up (7.A.2 change 3) -----
   The ONE deliberate exception to "never animate the hero": Daniel wants
   a fade-up on first paint. It is PURE CSS on purpose — a keyframe that
   runs on load, with NO JS-applied hidden state — so the hero can never
   get stuck invisible if JS fails to load. animation-fill-mode:both holds
   the opacity:0 / translateY start through any stagger delay and holds
   the opacity:1 / translateY(0) end afterwards; because a CSS animation
   applied to the element ALWAYS runs to completion, the resting state is
   always fully visible. Motion tuned for a calm, premium feel (7.A.2
   polish, then slowed further in 7.A.2 round 2): the duration is 900ms —
   well beyond --dur-slower (480ms, the longest duration token) so the
   settle reads slow, gentle and premium rather than quick; --ease-standard
   softens the onset (--ease-out started at full velocity, which read as
   abrupt); and the rise distance is opened up from 28px to 40px so the
   longer, gentler travel is clearly perceptible rather than snappy. Both
   900ms and 40px are raw motion values — there is no token above
   --dur-slower and none for a travel distance, same as the previous raw
   value. This hero LOAD animation stays a raw 900ms: it is a one-time
   on-load settle, not a scroll reaction, so 7.A.11's scroll-reaction
   softening left it alone. The on-scroll reveals were lengthened past it to
   --dur-reveal (1100ms, landing.css §2 reveal) and moved to --ease-gentle;
   both remain in the same slow, calm motion family. Under
   prefers-reduced-motion the animation is killed outright below, so this
   raw duration (which base.css cannot zero, unlike the tokens) is safe. */
@keyframes hero-rise{
  from{ opacity:0; transform:translateY(40px); }
  to{ opacity:1; transform:translateY(0); }
}

/* SHARED on-load stagger (7.A.13; /tester joins 7.A.18): the waitlist and tester
   section 1s reuse this exact `hero-rise` load animation — same keyframe, same raw
   900ms, same easing — so their sequences settle with the same motion family as the
   hero. Being the FIRST thing on screen, all three are on-LOAD, never
   scroll-triggered (they are not .reveal groups and reveal.js never touches them).
   The per-element stagger DELAYS differ (each page has its own element set) and
   live in each page's own file; only this shared base application (the 900ms
   value) is defined here, once. */
.hero h1,
.hero-subhead,
.hero-actions,
.hero-offer,
.hero-req,
.hero-media,
.wl-title,
.wl-subhead,
.wl-form,
.wl-notice,
.wl-fine,
.wl-tester,
.wl-media,
.ts-kicker,
.ts-title,
.ts-subhead,
.ts-form,
.ts-notice,
.ts-fine,
.ts-media{
  /* 900ms: well beyond --dur-slower (480ms) for a slow, calm settle. Raw
     motion value for the hero's one-time load animation (the scroll reveals
     use the slower --dur-reveal token) — see the block comment above. */
  animation:hero-rise 900ms var(--ease-standard) both;
}

/* Stagger: H1 leads, subhead a step behind, then the CTA cluster — button, its
   offer line, and the requirements fine-print — arrives together on one beat, and
   the MacBook lands last. A short sequence, not all at once. */
.hero-subhead{ animation-delay:var(--stagger); }
.hero-actions{ animation-delay:calc(var(--stagger) * 2); }
.hero-offer{   animation-delay:calc(var(--stagger) * 2); }
.hero-req{     animation-delay:calc(var(--stagger) * 2); }
.hero-media{   animation-delay:calc(var(--stagger) * 3); }

/* Mandatory: a reduced-motion visitor sees the hero instantly and
   completely — no fade, no transform, final position. base.css already
   zeroes the motion tokens, but we kill the animation outright here so
   there is zero chance of a fill-mode start state ever showing. */
@media (prefers-reduced-motion: reduce){
  .hero h1,
  .hero-subhead,
  .hero-actions,
  .hero-offer,
  .hero-req,
  .hero-media,
  .wl-title,
  .wl-subhead,
  .wl-form,
  .wl-notice,
  .wl-fine,
  .wl-tester,
  .wl-media,
  .ts-kicker,
  .ts-title,
  .ts-subhead,
  .ts-form,
  .ts-notice,
  .ts-fine,
  .ts-media{
    animation:none;
  }
}

/* Hero media mobile cap, Phase 7.A.30. REVERSES 7.A.27's payload HOLD: the
   hero no longer stays hidden below 60rem — it now SHOWS on mobile at FULL
   EXPORT WEIGHT, a DELIBERATE trade (a visible hero beats a fast blank
   space on the page that converts), not an oversight. The cost is real
   and accepted: roughly 710KB of eager WebP, because the four theme/DPI
   exports run 338-364KB each and both theme <picture> elements sit in the
   DOM at once with only the inactive one display:none (index.html's
   .hero-media comment has the full reasoning). Resized mobile sources
   plus eliminating the theme double-fetch would cut that cost —
   OUTSTANDING debt, logged at 86f6c89, not a reason this is capped.
   What IS capped here is SIZE, not visibility: below 60rem .hero-media is
   a plain block box (the grid's default stretch makes it as wide as the
   track — .hero-grid above), so left uncapped the 1:1 shot would run the
   full row width — a 345px square at a 393px viewport.
   WIDENED 60% -> 80% and CENTRED, Phase 7.A.33: 60% read too small next to
   the copy above it, and was left-flush (no auto margins), which put its
   left edge hard against the gutter with no visual weight of its own. 80%
   was chosen over 100%: full-track width would butt both edges against the
   grid gutters with no breathing room of its own — inconsistent with the
   Apple-inspired "generous margins" brand rule (Description.md §Core
   Identity) — while 80% still reads as a clear size jump from 60% and
   leaves a visible, even inset on both sides once centred. Centring a block
   box needs margin-inline:auto, not text-align (which does nothing to a
   block-level element's own box) — added below. This breaks the "matches
   /features/ and .wl-shot" parity the old comment described; .wl-shot is
   widened identically in the same commit so the two stay in step, and
   /features/ (features.css) is untouched — out of scope for 7.A.33. */
@media (max-width: 59.999rem){
  .hero-shot{
    max-width:80%;
    margin-inline:auto;
  }
}

/* SHARED with .wl-shot (7.A.13). */
.hero-shot,
.wl-shot{
  /* Fills its media track; the track width (capped by the centred grid
     below) is what fixes the render at 900px on wide viewports. */
  display:block;
}

/* High-res product render displayed far below its intrinsic size, so it
   stays crisp at any hero width. width:100% fills the grid column;
   aspect-ratio:1/1 matches the square (1:1) asset and reserves the box
   before load (no CLS), reinforcing the square intrinsic width/height
   attributes on the <img> in the markup. height:auto lets the
   aspect-ratio drive the height. */
/* SHARED sizing (7.A.13): both shots fill their track and are shown far below
   their intrinsic size so they stay crisp. width:100% fills the grid column;
   height:auto lets the aspect-ratio drive the height. The ASPECT-RATIO is the one
   thing that differs — it must match each asset's real shape (the hero export is
   square, the waitlist app-home export is 3:2), so it is set per-class below and
   in waitlist.css, NOT here.
   Selectors target .hero-shot/.wl-shot directly, not a nested `img` — Phase
   7.X collapsed the <picture>/<source> PNG-fallback wrapper to a plain <img>
   once the PNG exports were deleted, so the class now sits on the <img>
   itself rather than on a wrapping element. */
.hero-shot,
.wl-shot{
  width:100%;
  height:auto;
}

/* Hero asset is 1:1; reserves the box before load (no CLS), reinforcing the
   square intrinsic width/height in the markup. (.wl-shot is 3:2 — waitlist.css.) */
.hero-shot{
  aspect-ratio:1 / 1;
}

/* Theme swap: light shot on the light site, dark shot on the dark site.
   Two <img> elements sit in the DOM at once and CSS toggles which one is
   visible — a single element can't have its src swapped by CSS. SHARED with
   the waitlist shots (7.A.13) — same mechanism. */
.hero-shot--dark,
.wl-shot--dark{
  display:none;
}

[data-theme="dark"] .hero-shot--light,
[data-theme="dark"] .wl-shot--light{
  display:none;
}

[data-theme="dark"] .hero-shot--dark,
[data-theme="dark"] .wl-shot--dark{
  display:block;
}

/* The one deliberate breakpoint in the hero. Above it: an asymmetric two
   column layout — the copy is capped and inset to the site container's
   left edge, while the screenshot column runs all the way to the right
   edge of the viewport. That right-edge bleed is what lets the shot grow
   past the 1200px container so the on-screen UI is actually legible, and
   it keeps growing on wider monitors. Below the breakpoint the layout
   drops to a single column and the MacBook shrinks to a capped 60% width
   instead (Phase 7.A.30) — it is no longer dropped; see the .hero-shot
   cap rule above for the reasoning and the accepted payload cost. */
@media (min-width: 60rem){
  /* SHARED with .wl-grid (7.A.13): the waitlist section 1 takes the SAME
     asymmetric two-column block — copy capped at 28rem, media in the flexible
     track settling at 900px, the whole block capped at 92.25rem and centred with
     equal gutters. Identical proportions and the same 60rem breakpoint, so the
     two pages' first sections split at the same width and to the same ratio.
     /tester joins in 7.A.18 for the GRID ITSELF — same mobile-first single column,
     same page gutters, same 60rem split point — but it is the one consumer that
     restates most of these values in its own file (tester.css §5): it mirrors the
     column ORDER, widens the copy track 28rem -> 41rem, tightens the gap
     --space-6 -> --space-9, and REPLACES the centred 92.25rem cap with a pinned
     (clamped) 117px side margin. Its media column is likewise its own — a
     fixed-pitch dot field, height-matched to the copy and clipped to its track,
     rather than an image scaled to fill it. Every one of those overrides is scoped
     to .ts-* inside the same breakpoint and cannot reach the hero or the waitlist. */
  .hero-grid,
  .wl-grid,
  .ts-grid{
    /* A fixed-size, CENTRED content block (7.A.2 round 2, final). The
       image no longer bleeds to the viewport edge, so copy + gap + image
       is a known width; max-width + margin-inline:auto centre it, giving
       EQUAL left/right viewport gutters. The media track is minmax(0,1fr):
       on viewports narrower than the cap the image scales down with the
       track (never overflows — no horizontal scroll); once the viewport is
       wide enough for the block to reach its cap the grid stops growing
       and the image settles at 900px, filling its track (no leftover
       centre gap or right gutter).
         max-width math (border-box): 28rem copy + 2rem gap (--space-6)
         + 56.25rem image (900px) + 2 x 3rem gutter (--container-pad-x
         ceiling) = 92.25rem. Those raw values are structural layout dims,
         same class as the 60rem breakpoint — not design tokens.
       Text↔image gap opened --space-5 -> --space-6 (they read a touch
       cramped once the block was centred).
       NOTE: centring the block moves the H1 off the nav-logo left edge —
       the earlier H1<->logo alignment is deliberately traded here for
       balanced left/right gutters. */
    grid-template-columns:minmax(0, 28rem) minmax(0, 1fr);
    gap:var(--space-6);
    max-width:92.25rem;
    margin-inline:auto;
    padding-inline:var(--container-pad-x);
  }
}

/* ---------- 2. The problem ---------- */
/* Deliberately quiet: text only, no image or icon — the restraint is the
   design. The shared .section supplies the fluid vertical rhythm
   (--space-section-y, top + bottom) so the block breathes and reads as
   separate from the hero; the nested shared .container caps and centres the
   content on the same page gutter the footer and every later section use.
   The heading and body are CENTRED within that container (7.A.2 round 2):
   the block is horizontally centred and its text is centre-aligned, in a
   wide, text-wrap:balanced measure — the heading a touch wider than the body
   (see .problem-measure). This deliberately drops the earlier left-edge
   alignment — a centred quiet beat reads better here than one pinned to a
   page edge, and it no longer needs to chase the hero H1's (off-container,
   inset) left edge. */
/* Top gap to the hero (7.A.2 nav polish change 4, retuned rounds 4–6). The
   shared .section applies --space-section-y top AND bottom; earlier rounds
   capped this top to --space-9 to stop the two fluid section-heights from
   stacking into too large a gap. Round 6 removes that cap in response to a
   direct call for more space: the problem's top is restored to the full fluid
   --space-section-y, so both sides now run the full section-height. The bottom
   was already --space-section-y (the gap down to section 3). Existing token, no
   raw px. */
.problem{
  padding-block-start:var(--space-section-y);
}

/* Centred measure for the quiet beat. Widened in 7.A.2 round 3: the earlier
   tight caps wrapped the heading into very short 2-4-word lines that read
   cramped and choppy. The column is opened up so each line carries more words
   and the copy reads as flowing statements. .problem-measure sets the WIDER
   (heading) width and centres the whole block; the body is capped a little
   narrower inside it (below) for a comfortable reading measure. The token set
   has no measure this wide, so 56rem is a structural rem cap — same category
   as the hero's 28rem copy cap, not a design token. text-align centres the
   runs; margin-inline centres the block itself in the container. */
.problem-measure{
  max-width:56rem;            /* heading width; wider than the old 42rem block. structural rem, not a token */
  margin-inline:auto;
  text-align:center;
}

.problem-title{
  /* Fluid type scale, but a step below the hero's --text-6xl so it reads
     as a section heading — large, yet clearly subordinate to the hero. */
  font-size:var(--text-5xl);
  /* Fills the full (wider) .problem-measure width so the two-clause heading
     gets maximum horizontal room and carries more words per line. text-wrap:
     balance evens the line lengths so the centred wrap reads as balanced
     statements, not a ragged stack — a progressive enhancement, ignored
     where unsupported (wrap stays normal). */
  text-wrap:balance;
}

/* Body paragraphs mirror the hero subhead (size, secondary colour, rhythm)
   so the two sections feel like one system. Capped a little narrower than the
   heading (48rem vs the 56rem block) and re-centred with margin-inline:auto,
   for a comfortable reading measure while the heading stays wider. text-align
   is inherited from the block; text-wrap:balance keeps the centred lines even.
   48rem is a structural rem cap, same category as above. margin-top spaces the
   first paragraph off the heading and the second off the first. */
.problem-body{
  font-size:var(--text-lg);
  color:var(--color-text-secondary);
  max-width:48rem;            /* narrower than the heading; structural rem, not a token */
  margin-inline:auto;
  text-wrap:balance;
  margin-top:var(--space-5);
}

/* ============================================================
   GENERIC SCROLL REVEAL (7.A.12; per-element stagger 7.A.13) — the ONE reveal
   ruleset for the whole page. Replaces the seven near-identical per-section blocks
   (problem / how-it-works / features / privacy / pricing / faq / final-cta /
   footer) with a single definition keyed on .reveal + .r-rise, so EVERY content
   element that carries .r-rise inside ANY .reveal group fades-and-rises once, ON
   ITS OWN BEAT.

   HOW: reveal.js observes every .reveal element, applies .is-hidden on load
   (JS only) and swaps it to .is-visible on first scroll-entry, then unobserves
   — so each group animates exactly ONCE, never on scroll-back. It ALSO numbers
   every .r-rise inside the group in flat document order into --ri (7.A.13), which
   the transition-delay below consumes so each piece staggers individually. There
   are no nested .reveal groups, so this generic selector never double-fires and
   the numbering is unambiguous.

   PER-ELEMENT, NOT CONTAINER-LEVEL (7.A.13): the risers are the INDIVIDUAL pieces
   — a card's title and body, each footer link — not just the card/row/footer
   block. A container may ALSO be a riser (e.g. a feature card rises, then its
   title, then its body); document order guarantees a container's --ri precedes
   its children's, so the block leads and its contents follow.

   NEVER JS-GATED: with no JS neither class is present, so every .r-rise falls
   through to its visible resting state (opacity:1 / no transform, the CSS
   initial values) — the hidden state is applied, never assumed. reveal.js also
   never applies .is-hidden under reduced motion, and the guard below is the
   belt-and-suspenders snap.

   STRONGER + CLEARLY VISIBLE (§4): fade + rise run over --dur-reveal (1400ms,
   gentle ease-out) across --reveal-rise (64px, opened up in 7.A.13) — a long,
   slow travel so every piece is plainly seen arriving, not merely appearing. */
.reveal.is-hidden .r-rise{
  /* No transition on the hidden state, so applying it on load snaps in rather
     than animating a fade-OUT before the reveal. */
  opacity:0;
  transform:translateY(var(--reveal-rise));
}

/* Every riser eases up on its OWN beat (7.A.13). reveal.js numbers each .r-rise
   inside a group in flat DOCUMENT order and writes that number to --ri; the delay
   below turns it into a per-element stagger, so a card's title trails the card
   and its body trails the title, each footer link trails the one before it, and
   NOTHING moves as a single block. --ri defaults to 0 (the first piece, and the
   value when no JS ran), and --stagger (90ms) is zeroed under reduced motion
   (base.css) so the whole cascade collapses to 0 there.

   This replaced an nth-child stagger that could only reach a group's DIRECT
   children — nested pieces (titles / bodies / links) all fell to delay 0 and
   moved together, which is exactly why the reveals read as container-level. */
.reveal.is-visible .r-rise{
  opacity:1;
  transform:none;
  transition:opacity var(--dur-reveal) var(--ease-gentle),
             transform var(--dur-reveal) var(--ease-gentle);
  transition-delay:calc(var(--ri, 0) * var(--stagger));
}

/* Mandatory reduced-motion: every piece fully visible, final position, no
   transform, no transition — regardless of class state. */
@media (prefers-reduced-motion: reduce){
  .reveal .r-rise{
    opacity:1;
    transform:none;
    transition:none;
  }
}

/* ---------- 3. How it works (7.A.4) ---------- */
/* Three alternating full-width rows (text column + abstract visual column),
   built as ROWS, not cards. Uses the shared .section rhythm + the shared
   .container gutter, so the section's left edge lines up with section 2, the
   footer, and every later section. Raw numbers below are all either structural
   layout dims (motif box sizes, rem caps — same class as the hero's 28rem copy
   cap and the problem's 56rem measure) or the page's --dur-reveal reveal duration
   / raw rise-travel distance carried over from section 2 so the whole page
   shares one slow, calm motion language. None is a token-governed value
   (colour, spacing, radius, type, shadow, motion) — those all come from tokens.
   Each is commented at its use site. */

/* Small label above the rows. Doubles as the section h2 (heading order stays
   h1 -> h2 -> h3) but is styled as a quiet eyebrow: small, tracked-out caps in
   the tertiary tone. Sits flush to the container's left edge. */
.hiw-eyebrow{
  font-size:var(--text-sm);
  font-weight:var(--weight-semibold);
  letter-spacing:var(--tracking-normal);
  text-transform:uppercase;
  color:var(--color-text-tertiary);
}

/* Eyebrow reveal (7.A.11 "stronger reveals" pass). Unlike the sections, the
   eyebrow has no .r-rise child — it IS the reveal element, so reveal.js toggles
   .is-hidden / .is-visible on the eyebrow itself and the fade+rise is styled
   here directly. Same tokens as every other reveal (--reveal-rise / --dur-reveal
   / --ease-gentle) so it matches the page's one motion language. The hidden
   state carries no transition, so applying it on load snaps rather than fading
   OUT; reveal.js never applies it under reduced motion (and the guard below is
   the belt-and-suspenders snap). */
.hiw-eyebrow.reveal.is-hidden{
  opacity:0;
  transform:translateY(var(--reveal-rise));
}
.hiw-eyebrow.reveal.is-visible{
  opacity:1;
  transform:none;
  transition:opacity var(--dur-reveal) var(--ease-gentle),
             transform var(--dur-reveal) var(--ease-gentle);
}
@media (prefers-reduced-motion: reduce){
  .hiw-eyebrow.reveal{
    opacity:1;
    transform:none;
    transition:none;
  }
}

/* Generous vertical rhythm BETWEEN rows (--space-9, the largest step) so the
   section reads tall and unhurried — that height is intended. The top margin
   lifts the first row off the eyebrow with the same step. */
.hiw-rows{
  display:flex;
  flex-direction:column;
  gap:var(--space-9);
  margin-top:var(--space-8);
}

/* Each row: mobile-first single column, visual first in source order so it
   stacks ABOVE the text on narrow widths. border-radius lets the hover lift
   read as a rounded surface; the row carries no padding, so the text/motif
   left edges stay aligned to the container gutter (the eyebrow's edge). The
   two-column desktop layout + the side alternation live in the media query
   at the end of this block. The transform transition powers the hover lift;
   it is --dur-slow/--ease-out (premium settle, both zeroed under reduced
   motion by base.css) and does NOT interfere with the reveal, which animates
   the .r-rise CHILDREN, not the row itself. */
.hiw-row{
  display:grid;
  grid-template-columns:1fr;
  gap:var(--space-7);
  align-items:center;
  border-radius:var(--radius-lg);
  transition:transform var(--dur-slow) var(--ease-out);
}

/* Subtle premium lift on hover — a gentle scale-up (compact -> normal feel),
   well within the container gutter so it never triggers horizontal scroll.
   Reduced-motion handling is in the guard at the end of this block. */
.hiw-row:hover{
  transform:scale(1.01);
}

/* Visual column — centres its motif and caps it small (26rem) so each motif
   stays a restrained accent, not an illustration. min-height reserves a
   consistent band so the three rows feel evenly weighted even though the
   motifs differ. 26rem / 8rem are structural motif dims, not tokens. */
.hiw-visual{
  display:flex;
  align-items:center;
  justify-content:center;
  min-height:8rem;
}

/* Text column. */
.hiw-num{
  font-size:var(--text-sm);
  font-weight:var(--weight-medium);
  color:var(--color-text-tertiary);
}

.hiw-heading{
  /* A step larger than a card heading but clearly subordinate to the hero
     (--text-6xl) and to section 2's heading (--text-5xl): bigger, calmer
     type from the same scale. */
  font-size:var(--text-3xl);
  margin-top:var(--space-2);
}

.hiw-body{
  /* Mirrors the hero subhead / problem body (size, secondary tone) so the
     sections read as one system. Capped for a comfortable measure. */
  font-size:var(--text-lg);
  color:var(--color-text-secondary);
  max-width:var(--measure-prose);
  margin-top:var(--space-4);
}

/* ----- Row 01 motif: audio waveform (quiet) ----- */
/* A dense row of thin vertical bars of irregular height, muted greyscale,
   suggesting audio being listened to. Each bar is a fixed --space-2 width — the
   same bar thickness the row 02 meters and row 03 note lines use — so the three
   motifs read as one visual family. The bar group is centred within the 26rem
   cap. */
.hiw-wave{
  display:flex;
  align-items:center;
  justify-content:center;   /* centre the fixed-width bar group in the capped box */
  gap:var(--space-1);
  width:100%;
  max-width:26rem;   /* structural motif cap, not a token */
  height:7rem;       /* structural motif height, not a token */
}

.hiw-wave span{
  /* Fixed WIDTH = --space-2 matches the row 02 / row 03 bar thickness. flex-grow
     0 holds that width; flex-shrink 1 lets the denser row compress gracefully on
     very narrow screens rather than overflow. */
  flex:0 1 var(--space-2);
  height:var(--h);           /* inline structural motif dim (see index.html) */
  min-height:var(--space-1); /* keeps the shortest bars visible */
  background:var(--color-text-tertiary);  /* muted — row 01 is quiet */
  border-radius:var(--radius-pill);
}

/* ----- Row 02 motif: three certainty meters (focal) ----- */
.hiw-meters{
  display:flex;
  flex-direction:column;
  gap:var(--space-4);
  width:100%;
  max-width:26rem;   /* structural motif cap, not a token */
}

/* Thin track; the fill sits inside it, clipped to the pill so the animating
   right edge stays rounded. */
.hiw-meter{
  height:var(--space-2);
  background:var(--color-surface-3);
  border-radius:var(--radius-pill);
  overflow:hidden;
}

/* Fill. Default (no-JS / reduced-motion) RESTING state is already at the held
   --level — visibility/fill is never JS-gated. The 0 start and the animation
   are applied only by the reveal classes below. Stronger --color-text tone
   (vs the muted rows 01/03) makes row 02 the focal visual. */
.hiw-meter-fill{
  display:block;
  height:100%;
  width:var(--level);        /* inline structural motif dim (see index.html) */
  background:var(--color-text);
  border-radius:var(--radius-pill);
}

/* ----- Row 03 motif: stacked note lines (quiet) ----- */
/* A few short rounded rules of decreasing width, suggesting written notes. */
.hiw-notes{
  display:flex;
  flex-direction:column;
  gap:var(--space-3);
  width:100%;
  max-width:26rem;   /* structural motif cap, not a token */
}

.hiw-note{
  display:block;
  height:var(--space-2);
  width:var(--w);            /* inline structural motif dim (see index.html) */
  background:var(--color-text-tertiary);  /* muted — row 03 is quiet */
  border-radius:var(--radius-pill);
}

/* ----- Scroll reveal -----
   The fade-and-rise of each .hiw-row's .r-rise children is handled by the
   GENERIC reveal ruleset (7.A.12, §2 above): each .hiw-row is a .reveal group,
   and its risers — the .hiw-visual motif plus, individually, .hiw-num /
   .hiw-heading / .hiw-body (each tagged .r-rise in the markup, §4) — fade up and
   stagger by source order. Only the row-02 meter FILL is section-specific: */

/* Row 02 focal motion: the three meters fill from 0 to their held --level on
   reveal, driven ENTIRELY by this CSS width transition keyed on .is-visible —
   no JS touches the fill. Each meter is staggered a beat after the last. */
.hiw-row.is-hidden .hiw-meter-fill{
  /* No transition here, so the 0 start snaps in on load (no reverse animation). */
  width:0;
}

.hiw-row.is-visible .hiw-meter-fill{
  width:var(--level);
  transition:width var(--dur-reveal) var(--ease-gentle);
}

.hiw-row.is-visible .hiw-meter:nth-child(2) .hiw-meter-fill{
  transition-delay:var(--stagger);
}

.hiw-row.is-visible .hiw-meter:nth-child(3) .hiw-meter-fill{
  transition-delay:calc(var(--stagger) * 2);
}

/* Two-column desktop layout + side alternation. At the same 60rem breakpoint
   the hero uses, each row becomes text | visual. Source order is visual-then
   -text, so the default row is visual-LEFT; .hiw-row--text-left flips the two
   with `order` (row 02), giving the clean alternation visual-left / text-left
   / visual-left down the page. `order` does not affect the nth-child stagger
   above (that reads source order). */
@media (min-width: 60rem){
  .hiw-row{
    grid-template-columns:1fr 1fr;
  }

  .hiw-row--text-left .hiw-visual{
    order:2;
  }

  .hiw-row--text-left .hiw-text{
    order:1;
  }
}

/* Mandatory reduced-motion guard (belt-and-suspenders — reveal.js already
   skips the hidden state under reduced motion): every row's content and every
   motif render final and still, and the hover lift is dropped (no transform).
   This is the "no hover transform" reduced-motion option; the meters show
   already filled at their held levels. */
@media (prefers-reduced-motion: reduce){
  /* .r-rise is handled by the generic reduced-motion guard (§2). Section-specific
     here: the meters show already filled, and the hover lift is dropped. */
  .hiw-meter-fill{
    width:var(--level);
    transition:none;
  }

  .hiw-row{
    transition:none;
  }

  .hiw-row:hover{
    transform:none;
  }
}

/* ---------- 4. Features (7.A.5) ---------- */
/* A horizontal expand-accordion: six touching cards in one continuous strip,
   grouped Before / During / After, with exactly ONE card expanded at a time
   (default: the first). It is deliberately a DIFFERENT shape from the
   alternating how-it-works rows above — one strip, not stacked rows.

   Two layouts share this markup:
   · DEFAULT (mobile, AND desktop with no JS): the strip is a plain flex
     column — all six cards stacked and FULLY expanded, grouped under their
     labels. This is the no-JS fallback AND the < 60rem mobile layout, one and
     the same. Nothing is ever hidden.
   · INTERACTIVE (>= 60rem AND features.js has added .js-ready): the strip
     becomes a grid — labels in row 1 spanning their pairs, six cards in row 2,
     one wide + five narrow, animating on hover. Gated on BOTH the breakpoint
     and .js-ready, so it only appears when it can actually work.

   Raw numbers below are all structural layout dims (the strip's open/shut fr
   ratio, the full-card min-width that stops text reflow mid-animation, motif-
   class rem caps) or the page's shared --dur-reveal reveal duration / --reveal-rise rise
   distance carried over from sections 2–3 so the whole page shares one motion
   language. None is a token-governed value (colour, spacing, radius, type,
   shadow, motion) — each is commented at its use site. */

/* Section heading. A step below the hero (--text-6xl) and the problem
   (--text-5xl); flush to the container's left edge like the how-it-works
   eyebrow, not centred like the problem — variety between sections.
   text-wrap:balance evens the two-line wrap (progressive enhancement). */
.features-title{
  font-size:var(--text-4xl);
  max-width:34rem;            /* keeps the heading a tidy 2-line block; structural rem, not a token */
  text-wrap:balance;
}

/* ----- Default / no-JS / mobile: stacked, all cards fully expanded ----- */
/* The strip is a plain flex column here. The interactive grid below is scoped
   to .js-ready inside the 60rem media query, so this stacked, all-expanded
   layout is exactly what a no-JS visitor and a mobile visitor both get. */
.features-accordion{
  margin-top:var(--space-8);
}

.features-strip{
  display:flex;
  flex-direction:column;
  gap:var(--space-3);
}

/* Group labels — quiet muted caps, matching the how-it-works eyebrow tone.
   In the stacked layout they are group headers in normal flow (source order is
   label -> card -> card), lifted off the previous group by a larger top margin;
   the first label needs none. --tracking-normal (0) matches the hiw eyebrow —
   there is no positive-tracking token, so caps get the same treatment. */
.features-group{
  font-size:var(--text-sm);
  font-weight:var(--weight-semibold);
  letter-spacing:var(--tracking-normal);
  text-transform:uppercase;
  color:var(--color-text-tertiary);
}

.features-group:not(.features-group--before){
  margin-top:var(--space-5);
}

/* Card shell. Fully self-contained bordered block in the stacked layout;
   the horizontal layout below reshapes the borders into one continuous strip.
   position:relative anchors the absolute vertical rail; overflow:hidden clips
   the full content to the rounded corners while the card animates width. */
.feature-card{
  position:relative;
  overflow:hidden;
  background:var(--color-surface-raised);
  border:1px solid var(--color-border);
  border-radius:var(--radius-md);
}

/* Keyboard focus affordance on the focusable card (tabindex="0" in markup) —
   inset so it is not clipped by the card's overflow:hidden. */
.feature-card:focus-visible{
  outline:2px solid var(--color-focus-ring);
  outline-offset:-2px;
}

/* Expanded content — heading (+ optional badge) + body. Shown for every card
   in the stacked layout; faded per-state in the horizontal layout. Its own
   padding (the card itself has none) so the vertical rail can sit edge-to-edge. */
.feature-card-full{
  padding:var(--space-6);
}

.feature-card-head{
  display:flex;
  align-items:center;
  flex-wrap:wrap;
  gap:var(--space-3);
}

.feature-card-title{
  /* Card heading: a clear step below the how-it-works row headings
     (--text-3xl) — these are six smaller cards, not three full rows. */
  font-size:var(--text-xl);
}

.feature-card-body{
  font-size:var(--text-md);
  color:var(--color-text-secondary);
  max-width:var(--measure-prose);
  margin-top:var(--space-3);
}

/* Compact vertical rail label — the collapsed card's identity. Hidden in the
   stacked layout (the full heading is already visible); shown, absolutely
   filling the card, only in the horizontal layout below. aria-hidden in markup:
   it duplicates the real heading, which stays in the DOM at all times. */
.feature-card-rail{
  display:none;
}

/* Tier treatment — README §3 + app design-system SKILL §4 (Plan gradients).
   Only Levels of Certainty (Pro) and Live Feedback (Max) are tier-gated; the
   other four features are core to every plan and carry no pill.

   REVERTED: the gradient-TITLE treatment (.grad-pro / .grad-max on the heading)
   was tried and removed — it did not read well. The feature titles now keep the
   plain .feature-card-title colour (normal --color-text, inherited), same as the
   four untiered cards. No .grad-* rule touches any title.

   FINAL treatment (7.A.5 — matches Daniel's approved reference): the tier is a
   small "PRO"/"MAX" pill beside the heading, built as TWO elements:
     - the PILL (.feature-tier) is a fully TRANSPARENT rounded outline — a 1px
       --color-border keyline and NOTHING else. No fill, no background-image, no
       background-clip. --color-border flips per theme so the outline reads in both.
     - the metallic gradient lives ONLY on the inner TEXT element
       (.feature-tier__text), clipped to the glyphs via background-clip:text, so
       the LETTERS themselves are brushed silver (Pro) / champagne-gold (Max).
   These are badge-scoped gradients built from the raw brand-metal primitives
   (--silver* / --gold*, defined once at :root, theme-invariant) — deliberately
   NOT --grad-pro / --grad-max (those are black→metal gradients tuned for large
   glyphs; they go muddy on 12px letters). --grad-pro/--grad-max stay UNTOUCHED. */
.feature-tier{
  display:inline-flex;
  align-items:center;
  line-height:1;
  padding:var(--space-1) var(--space-2);
  border-radius:var(--radius-pill);
  /* Transparent pill: just a thin theme-adaptive outline, no fill of any kind.
     The tier is conveyed entirely by the gradient text inside. */
  background:transparent;
  border:1px solid var(--color-border);
}

/* Inner text element — carries only the badge TYPOGRAPHY (small uppercase, tuned
   to read at 12px). The metallic gradient fill + background-clip mechanics are
   NOT here anymore — they live in the shared .metal-text* rule below, which this
   span also carries in the markup, so the exact same brushed-metal asset paints
   both these badges and the pricing-preview tier names. */
.feature-tier__text{
  font-size:var(--text-xs);
  font-weight:var(--weight-semibold);
  letter-spacing:var(--tracking-normal);
  text-transform:uppercase;
}

/* ----- Shared brushed-metal gradient text (the ONE definition) --------------
   ONE Pro (brushed silver) gradient and ONE Max (champagne gold) gradient, held
   as custom properties so there is a single source of truth for each. Consumed
   in TWO places via the .metal-text classes: the features-section Pro/Max tier
   badges (.feature-tier__text, above) and the pricing-preview tier names
   (.pricing-plan-name, below). Change the metal here and both move together.

   Structure: deep→bright→deep at 60deg (brand angle) so the mid highlight reads
   as metallic sheen while both ends stay dark enough to remain legible on a
   light card. Does NOT start at pure black (would go muddy). Built from the raw
   brand-metal primitives (--silver* / --gold*, theme-invariant) — deliberately
   NOT --grad-pro / --grad-max (those are black→metal, tuned for large title
   glyphs and would go muddy here); --grad-pro / --grad-max stay UNTOUCHED.

   Theme: on a dark surface the anchor stops lift off the deep end toward the mid
   metals so the sheen stays luminous and the letters stay legible; the primitives
   are theme-invariant, so we re-aim the stops rather than flip a token.

   The stops are percentages of the element's own text box, so the sweep is
   scale-invariant — it reads identically on the ~12px badges and on the much
   larger tier-name headings, PROVIDED the box hugs the glyphs (the pricing name
   forces this with align-self below; the inline badge span hugs by default). */
:root{
  --grad-metal-pro:linear-gradient(60deg,
    var(--silver-deep) 0%, var(--silver-bright) 50%, var(--silver-deep) 100%);
  --grad-metal-max:linear-gradient(60deg,
    var(--gold-deep) 0%, var(--gold-bright) 50%, var(--gold-deep) 100%);
}
[data-theme="dark"]{
  --grad-metal-pro:linear-gradient(60deg,
    var(--silver) 0%, var(--silver-bright) 50%, var(--silver) 100%);
  --grad-metal-max:linear-gradient(60deg,
    var(--gold) 0%, var(--gold-bright) 50%, var(--gold) 100%);
}

/* Clip the gradient to the glyphs and clear the solid fill so the metal shows. */
.metal-text{
  -webkit-background-clip:text;
  background-clip:text;
  -webkit-text-fill-color:transparent;
  color:transparent;
}
.metal-text--pro{ background-image:var(--grad-metal-pro); }
.metal-text--max{ background-image:var(--grad-metal-max); }

/* Fallback — if background-clip:text is unsupported, never render invisible text:
   paint the glyphs a solid metal-deep instead (legible in both themes). */
@supports not ((-webkit-background-clip:text) or (background-clip:text)){
  .metal-text--pro{ color:var(--silver-deep); -webkit-text-fill-color:currentColor; }
  .metal-text--max{ color:var(--gold-deep);   -webkit-text-fill-color:currentColor; }
}

/* ----- Feature motifs (7.A.5, motif pass) -----
   One small, quiet, abstract GEOMETRIC motif per card, pure inline SVG. The
   whole set is a family: identical box size, identical 2-unit stroke weight,
   identical greyscale palette — only the SHAPE differs per card. All monochrome
   (--color-text / --color-text-secondary via currentColor / --color-border);
   NO silver/gold gradient on any motif, on purpose. They are deliberately a
   different KIND of form from the how-it-works waveform/meter/note-line bar
   motifs above — rings, nodes, orbs, pings, a filled page, a curve.

   These are the STATIC formed (resting) states — what shows on mobile, with no
   JS, on collapsed cards, and under prefers-reduced-motion. The gentle looping
   is added ONLY for the expanded desktop card, in the gated block far below
   (@media min-width:60rem AND prefers-reduced-motion:no-preference), so reduced
   motion and mobile simply never get the loops and rest here, fully formed. */
.feature-motif{
  width:3.5rem;                  /* uniform motif box — structural dim, not a token */
  height:3.5rem;
  margin-top:var(--space-5);
  color:var(--color-text-secondary);   /* base currentColor for strokes/fills */
}

.feature-motif svg{
  width:100%;
  height:100%;
  display:block;
  overflow:visible;              /* radar pings + gauge arcs breathe past the box */
}

/* Focal parts pick up the stronger primary tone; structural parts stay in the
   muted secondary (currentColor) or drop their opacity — all still greyscale. */
.fm-node,
.fm-orb,
.fm-dot,
.fm-travel,
.fm-read,
.fm-block{
  fill:var(--color-text);
  stroke:none;
}
.fm-arc-3,
.fm-check{
  stroke:var(--color-text);
}

/* Warm-Up gauge arcs — graded so the resting dial reads as concentric depth. */
.fm-arc-1{ opacity:.35; }
.fm-arc-2{ opacity:.6; }
.fm-arc-3{ opacity:1; }

/* Flow graph — faint edges, solid nodes; the travelling dot is hidden at rest
   (it exists only during the loop). */
.fm-edges{ opacity:.4; }
.fm-travel{ opacity:0; }

/* Certainty — three stems (muted secondary) on a faint baseline, each capped by
   a solid dot (primary, via the focal-fill rule). Three different stem lengths =
   three distinct axes. The dots drift on the loop; stems + baseline stay put. */
.fm-axis-base{ opacity:.4; }
.fm-stem{ opacity:.8; }

/* Radar — faint base ring + solid centre dot are the resting state; the ping
   rings are invisible at rest and only expand during the loop. transform-box so
   scale() pivots on each ring's own centre. */
.fm-base-ring{ opacity:.4; }
.fm-ping{
  opacity:0;
  transform-box:fill-box;
  transform-origin:center;
}

/* Notes — faint page outline, solid blocks + check all present at rest (the
   resting state is a fully filled form). transform-box so the check pops from
   its own centre. */
.fm-page{ opacity:.5; }
.fm-check{
  transform-box:fill-box;
  transform-origin:center;
}

/* KPI — faint curve; the reading dot is hidden at rest. */
.fm-curve{ opacity:.55; }
.fm-read{ opacity:0; }

/* ----- Motif keyframes (transform + opacity ONLY — GPU-cheap, no layout) -----
   Every loop is short-motion-then-long-hold, and loops whose 0%/100% differ are
   masked to opacity:0 across the loop boundary so the reset never flashes. */

/* Container intro: one gentle fade+scale-in when a card becomes the open one. */
@keyframes fm-intro{
  from{ opacity:0; transform:scale(.94); }
  to{   opacity:1; transform:scale(1); }
}

/* Gauge arc: dim -> fill bright -> long hold -> dim. 0%/100% match (.25) so the
   loop boundary is seamless; staggered per arc to read as filling outward. */
@keyframes fm-gauge{
  0%,100%{ opacity:.25; }
  18%{ opacity:1; }
  82%{ opacity:1; }
}

/* Flow: signal dot travels lead->call->script->goal, then fades for the pause.
   Hidden (opacity:0) at both ends so the jump back to the first node never shows. */
@keyframes fm-flow{
  0%{   transform:translate(7px,27px);  opacity:0; }
  6%{   opacity:1; }
  12%{  transform:translate(7px,27px); }
  30%{  transform:translate(16px,14px); }
  48%{  transform:translate(25px,25px); }
  66%{  transform:translate(34px,12px); }
  74%{  opacity:1; }
  82%{  transform:translate(34px,12px); opacity:0; }
  100%{ transform:translate(34px,12px); opacity:0; }
}

/* Certainty dots: a slow, gentle SYMMETRIC rise-and-fall along their stems (the
   one continuous loop, per the brief — calm, not a spin). Rest (static / reduced
   motion) is the dot at its stem top; this eases it a touch below then above. */
@keyframes fm-drift{
  0%,100%{ transform:translateY(2px); }
  50%{     transform:translateY(-2.5px); }
}

/* Radar ping: a ring scales outward from the centre and fades, then a long
   invisible rest (~2s of the cycle) before the next ping. */
@keyframes fm-ping{
  0%{      transform:scale(.5); opacity:.85; }
  35%{     opacity:0; }
  60%,100%{ transform:scale(2.6); opacity:0; }
}

/* Notes block: slide + fade in, long hold, fade out. 0%/100% both opacity:0 so
   the loop boundary is seamless; staggered so blocks fill in order. */
@keyframes fm-block{
  0%{   opacity:0; transform:translateX(-2px); }
  16%{  opacity:1; transform:translateX(0); }
  88%{  opacity:1; }
  100%{ opacity:0; }
}

/* Notes check: pops in after the blocks, holds, fades out with them. */
@keyframes fm-check{
  0%,12%{ opacity:0; transform:scale(.4); }
  26%{    opacity:1; transform:scale(1.15); }
  36%{    transform:scale(1); }
  88%{    opacity:1; }
  100%{   opacity:0; }
}

/* KPI reading dot: traces the trend line left->right, then fades for the pause.
   Hidden at both ends so the jump back to the start never shows. */
@keyframes fm-read{
  0%{   transform:translate(4px,27px);  opacity:0; }
  6%{   opacity:1; }
  12%{  transform:translate(4px,27px); }
  28%{  transform:translate(16px,16px); }
  44%{  transform:translate(26px,25px); }
  60%{  transform:translate(36px,13px); }
  68%{  opacity:1; }
  76%{  transform:translate(36px,13px); opacity:0; }
  100%{ transform:translate(36px,13px); opacity:0; }
}

/* ----- Reveal -----
   Handled by the GENERIC reveal ruleset (7.A.12/7.A.13, §2). The .features section
   is the .reveal group; its risers are the heading, the three group labels, and —
   for EACH of the six cards — the card itself, then its title, then its body (each
   tagged .r-rise in the markup, 7.A.13), all staggering in by flat document order.
   So a card rises and its title and body follow on their own beats, not the card
   as one block. The per-piece reveal is a translateY/opacity on that element only —
   it never touches .features-strip's grid-template-columns width animation or the
   cards' own hover/motif motion, which run on different properties/elements. The
   aria-hidden motif is NOT a riser: it rides with its card (decorative). On the
   desktop strip a collapsed card's title/body reveal is masked by the accordion's
   own opacity:0 on .feature-card-full — harmless, since that content is hidden
   there anyway; the visible card column still rises. */

/* ----- Interactive horizontal strip (>= 60rem AND JS ready) ----- */
/* Everything that turns the stacked fallback into the one-open accordion is
   scoped to BOTH the 60rem breakpoint (same as the hero + how-it-works) AND
   .js-ready. Below the breakpoint, or with no JS, none of this applies and the
   stacked all-expanded layout above stands. */
@media (min-width: 60rem){

  .features-strip.js-ready{
    /* Open track vs the five shut tracks. 6:1.4 puts the open card at ~46% of the
       strip and each shut card at ~11% (~120px at container width) — wide enough
       to be legible open, and enough room for the shut card's HORIZONTAL wrapped
       title (see the rail rule). Structural fr ratio, not a token. */
    --fc-open:6fr;
    --fc-shut:1.4fr;

    display:grid;
    /* Row 1 = group labels, row 2 = cards. 1fr on row 2 makes every card
       stretch to the tallest (the open one), so the shut cards are full-height
       columns with room for the vertical rail. */
    grid-template-rows:auto 1fr;
    /* Default column state = first card open. The :has() rules below move the
       open track to whichever card actually carries .is-open; this default also
       covers the first paint before features.js sets .is-open on card 1, so
       there is no flash. */
    grid-template-columns:var(--fc-open) var(--fc-shut) var(--fc-shut) var(--fc-shut) var(--fc-shut) var(--fc-shut);
    column-gap:0;                 /* cards TOUCH — one continuous strip, no gaps */
    row-gap:var(--space-3);
    /* The smooth width animation. grid-template-columns interpolates fr<->fr on
       --dur-slower/--ease-out — a calm settle, no snap. Under prefers-reduced-
       motion --dur-slower is 0ms (base.css), so it snaps but still works. */
    transition:grid-template-columns var(--dur-slower) var(--ease-out);
  }

  /* Which card is open drives the open track's position. One rule per card;
     each just moves --fc-open to that column. Default (above) = card 1. */
  .features-strip.js-ready:has(.feature-card:nth-of-type(1).is-open){
    grid-template-columns:var(--fc-open) var(--fc-shut) var(--fc-shut) var(--fc-shut) var(--fc-shut) var(--fc-shut);
  }
  .features-strip.js-ready:has(.feature-card:nth-of-type(2).is-open){
    grid-template-columns:var(--fc-shut) var(--fc-open) var(--fc-shut) var(--fc-shut) var(--fc-shut) var(--fc-shut);
  }
  .features-strip.js-ready:has(.feature-card:nth-of-type(3).is-open){
    grid-template-columns:var(--fc-shut) var(--fc-shut) var(--fc-open) var(--fc-shut) var(--fc-shut) var(--fc-shut);
  }
  .features-strip.js-ready:has(.feature-card:nth-of-type(4).is-open){
    grid-template-columns:var(--fc-shut) var(--fc-shut) var(--fc-shut) var(--fc-open) var(--fc-shut) var(--fc-shut);
  }
  .features-strip.js-ready:has(.feature-card:nth-of-type(5).is-open){
    grid-template-columns:var(--fc-shut) var(--fc-shut) var(--fc-shut) var(--fc-shut) var(--fc-open) var(--fc-shut);
  }
  .features-strip.js-ready:has(.feature-card:nth-of-type(6).is-open){
    grid-template-columns:var(--fc-shut) var(--fc-shut) var(--fc-shut) var(--fc-shut) var(--fc-shut) var(--fc-open);
  }

  /* Group labels: row 1, each spanning its pair's two columns, so it caps the
     pair and stays put as a header while the cards resize under it. Bottom-
     aligned and inset to line up with the card content's left padding.
     :nth-of-type on the labels is by <p> type; the cards are <article>. */
  .features-strip.js-ready .features-group{
    margin:0;                                 /* drop the stacked-layout top margin */
    align-self:end;
    padding-inline-start:var(--space-6);      /* align with .feature-card-full padding */
    padding-bottom:var(--space-1);
  }
  .features-strip.js-ready .features-group--before{ grid-area:1 / 1 / 2 / 3; }
  .features-strip.js-ready .features-group--during{ grid-area:1 / 3 / 2 / 5; }
  .features-strip.js-ready .features-group--after{  grid-area:1 / 5 / 2 / 7; }

  /* Cards: row 2, one per column, in order. Explicit placement (not auto-flow)
     because the labels also occupy the grid. */
  .features-strip.js-ready .feature-card:nth-of-type(1){ grid-area:2 / 1; }
  .features-strip.js-ready .feature-card:nth-of-type(2){ grid-area:2 / 2; }
  .features-strip.js-ready .feature-card:nth-of-type(3){ grid-area:2 / 3; }
  .features-strip.js-ready .feature-card:nth-of-type(4){ grid-area:2 / 4; }
  .features-strip.js-ready .feature-card:nth-of-type(5){ grid-area:2 / 5; }
  .features-strip.js-ready .feature-card:nth-of-type(6){ grid-area:2 / 6; }

  /* Reshape the per-card borders into ONE continuous strip: every card keeps
     top/bottom + a left divider, the last card closes the right edge, and only
     the two ends are rounded. overflow:hidden (base) clips content to those
     rounded ends. */
  .features-strip.js-ready .feature-card{
    border-radius:0;
    border-inline-end:0;                      /* the next card's left border is the single divider */
    /* Height floor for the strip. The open card's content is wide and short,
       so without a floor the row could be too short for a shut card's vertical
       rail heading to fit (it would clip under overflow:hidden). 14rem clears
       the longest rail label with margin. overflow:hidden also zeroes the grid
       item's auto min-width, so a shut card can be ~90px wide while its
       min-width:22rem full content sits clipped inside. Structural rem, not a token. */
    min-height:14rem;
  }
  .features-strip.js-ready .feature-card:nth-of-type(6){
    border-inline-end:1px solid var(--color-border);
  }
  .features-strip.js-ready .feature-card:nth-of-type(1){
    border-start-start-radius:var(--radius-lg);
    border-end-start-radius:var(--radius-lg);
  }
  .features-strip.js-ready .feature-card:nth-of-type(6){
    border-start-end-radius:var(--radius-lg);
    border-end-end-radius:var(--radius-lg);
  }

  /* Full content: faded per-state. min-width freezes the text at its open width
     so it does NOT reflow line-by-line while the card animates — it simply gets
     clipped by overflow:hidden as the card narrows, and fades out. Structural
     rem, not a token; the open card (~58% of the strip) is far wider than this,
     so it never clips when open. */
  .features-strip.js-ready .feature-card-full{
    min-width:22rem;
    opacity:0;
    transition:opacity var(--dur-slow) var(--ease-out);
  }
  .features-strip.js-ready .feature-card.is-open .feature-card-full{
    opacity:1;
  }

  /* Collapsed-card rail label: HORIZONTAL (not rotated). It absolutely fills the
     card and cross-fades against the full content. The full title is kept intact
     (never truncated); it fits the ~120px shut column because the type drops to
     --text-xs and the title WRAPS onto a few centred lines — the card's 14rem
     min-height leaves ample vertical room. Visible when shut, faded when open. */
  .features-strip.js-ready .feature-card-rail{
    display:flex;
    position:absolute;
    inset:0;
    align-items:center;
    justify-content:center;
    padding-inline:var(--space-2);
    padding-block:var(--space-3);
    font-size:var(--text-xs);
    line-height:var(--leading-tight);
    font-weight:var(--weight-medium);
    color:var(--color-text-secondary);
    text-align:center;
    text-wrap:balance;
    opacity:1;
    transition:opacity var(--dur-slow) var(--ease-out);
  }
  .features-strip.js-ready .feature-card.is-open .feature-card-rail{
    opacity:0;
  }
}

/* ----- Motif loops: ONLY the expanded desktop card, ONLY when motion is OK -----
   Gated on BOTH the 60rem breakpoint (so mobile — where all six cards are
   expanded at once — shows every motif STATIC, never six simultaneous loops)
   AND prefers-reduced-motion:no-preference (so reduced motion gets the static
   formed motifs from the base rules and no loops here at all). Because only
   .is-open carries these, collapsed cards never animate — no idle CPU/battery.
   Every animation touches transform/opacity only. Durations are raw ms (motion
   category, allowed + commented): each is short-motion + long calm hold. */
@media (min-width: 60rem) and (prefers-reduced-motion: no-preference){

  /* Gentle one-shot intro on the motif each time its card becomes the open one
     (the selector begins matching, so the animation restarts). `both` holds the
     end (fully formed) state after it finishes; the per-part loops run on top. */
  .features-strip.js-ready .feature-card.is-open .feature-motif{
    animation:fm-intro var(--dur-slower) var(--ease-out) both;
  }

  /* Card 1 — Warm-Up gauge: arcs fill inner -> outer, then a long hold.
     5200ms cycle (raw ms, motion category). */
  .features-strip.js-ready .feature-card.is-open .feature-motif--warmup .fm-arc-3{
    animation:fm-gauge 5200ms var(--ease-out) infinite;
  }
  .features-strip.js-ready .feature-card.is-open .feature-motif--warmup .fm-arc-2{
    animation:fm-gauge 5200ms var(--ease-out) infinite;
    animation-delay:160ms;
  }
  .features-strip.js-ready .feature-card.is-open .feature-motif--warmup .fm-arc-1{
    animation:fm-gauge 5200ms var(--ease-out) infinite;
    animation-delay:320ms;
  }

  /* Card 2 — Every call set up: signal dot travels the graph, then pauses.
     6000ms cycle. */
  .features-strip.js-ready .feature-card.is-open .feature-motif--flow .fm-travel{
    animation:fm-flow 6000ms var(--ease-standard) infinite;
  }

  /* Card 3 — Certainty orbs: slow drift, out of phase (negative delays desync
     them so they never move in lockstep). */
  .features-strip.js-ready .feature-card.is-open .feature-motif--certainty .fm-orb-1{
    animation:fm-drift 5200ms var(--ease-standard) infinite;
  }
  .features-strip.js-ready .feature-card.is-open .feature-motif--certainty .fm-orb-2{
    animation:fm-drift 6400ms var(--ease-standard) infinite;
    animation-delay:-1200ms;
  }
  .features-strip.js-ready .feature-card.is-open .feature-motif--certainty .fm-orb-3{
    animation:fm-drift 5800ms var(--ease-standard) infinite;
    animation-delay:-600ms;
  }

  /* Card 4 — Live Feedback radar: two staggered pings, ~2s rest between.
     3400ms cycle. */
  .features-strip.js-ready .feature-card.is-open .feature-motif--live .fm-ping-1{
    animation:fm-ping 3400ms var(--ease-out) infinite;
  }
  .features-strip.js-ready .feature-card.is-open .feature-motif--live .fm-ping-2{
    animation:fm-ping 3400ms var(--ease-out) infinite;
    animation-delay:500ms;
  }

  /* Card 5 — SmartNotes: blocks fill in sequence, then the check pops.
     5600ms cycle. */
  .features-strip.js-ready .feature-card.is-open .feature-motif--notes .fm-block-1{
    animation:fm-block 5600ms var(--ease-out) infinite;
  }
  .features-strip.js-ready .feature-card.is-open .feature-motif--notes .fm-block-2{
    animation:fm-block 5600ms var(--ease-out) infinite;
    animation-delay:180ms;
  }
  .features-strip.js-ready .feature-card.is-open .feature-motif--notes .fm-block-3{
    animation:fm-block 5600ms var(--ease-out) infinite;
    animation-delay:360ms;
  }
  .features-strip.js-ready .feature-card.is-open .feature-motif--notes .fm-check{
    animation:fm-check 5600ms var(--ease-out) infinite;
  }

  /* Card 6 — KPIs: reading dot traces the trend line, then pauses. 6000ms. */
  .features-strip.js-ready .feature-card.is-open .feature-motif--kpi .fm-read{
    animation:fm-read 6000ms var(--ease-standard) infinite;
  }
}

/* Reduced motion: .r-rise is handled by the generic guard (§2). The accordion
   stays fully interactive under reduced motion — the expand works, it just
   snaps (the motion tokens zero out); the default card is still shown. */

/* ---------- 5. Privacy (7.A.6) ---------- */
/* The Option B carve-out section (Decisions.md §Website "Privacy claim", LOCKED
   2026-07-19): the local-first architecture stated strongly, then the single
   opt-in exception disclosed at FULL reading weight — a highlighted card, never
   a footnote or an asterisk. Uses the shared .section rhythm + the shared
   .container gutter, so its left edge lines up with sections 2–4, the footer,
   and every later section. Raw numbers below are structural layout dims (rem
   caps, the three-card grid, the shield-icon box) or the page's shared --dur-reveal
   reveal duration / --reveal-rise rise distance carried over from sections 2–4 so the
   whole page shares one slow, calm motion language. None is a token-governed
   value (colour, spacing, radius, type, shadow, motion) — each is commented at
   its use site. */

/* Section fill — the lifted band, starting a little below the previous section:
   the margin lets a strip of the page background show above it before the fill
   begins, so the band starts lower rather than butting up against section 4. The
   section takes the lifted --color-surface; the cards drop to the page default
   --color-bg (below) so they read as panels inset into the band. Both tokens flip
   per data-theme, so the step holds in light and in dark. The fill spans full-
   width as a band; the nested .container caps the content. --space-9 is the
   band's top offset — an existing spacing token, no raw px. */
.privacy{
  background:var(--color-surface);
  margin-block-start:var(--space-9);
}

/* Kicker — quiet muted caps, matching the how-it-works eyebrow / features-group
   tone. --tracking-normal (0) matches those labels; there is no positive-
   tracking token, so all caps labels share the one treatment.
   SHARED WITH /tester SECTION 1 (7.A.18): that page's "TESTING PHASE" kicker is
   the same eyebrow, so .ts-kicker joins the selector rather than tester.css
   restating these five declarations — one definition, two consumers, exactly like
   the hero rules above. Declarations unchanged; the privacy section is untouched.
   The rule lives here (inside the landing privacy block) because that is where it
   was first authored; if a third consumer appears it should move up to
   components.css as a real site-wide eyebrow primitive. */
.privacy-kicker,
.ts-kicker{
  font-size:var(--text-sm);
  font-weight:var(--weight-semibold);
  letter-spacing:var(--tracking-normal);
  text-transform:uppercase;
  color:var(--color-text-tertiary);
}

/* Two-line display heading. A step below the hero (--text-6xl); shares section
   2's weight (--text-5xl). Capped narrow so the two sentences wrap onto two
   lines; text-wrap:balance evens them (progressive enhancement, ignored where
   unsupported). 24rem is a structural rem cap — same category as the hero's
   28rem copy cap and the features-title's 34rem — not a design token. */
.privacy-title{
  font-size:var(--text-5xl);
  max-width:24rem;
  margin-top:var(--space-3);
  text-wrap:balance;
}

/* Lead paragraph — mirrors the hero subhead / problem body / hiw body (size,
   secondary tone) so the sections read as one system. Capped to the shared
   prose measure. */
.privacy-body{
  font-size:var(--text-lg);
  color:var(--color-text-secondary);
  max-width:var(--measure-prose);
  margin-top:var(--space-5);
}

/* ----- Three-column card row ----- */
/* Mobile-first single column; the three columns come in at the shared 60rem
   breakpoint (bottom of this block). */
.privacy-cards{
  display:grid;
  grid-template-columns:1fr;
  gap:var(--space-4);
  margin-top:var(--space-8);
}

/* Card shell — takes the page default --color-bg so the cards read as panels
   inset into the lifted section band; border + radius family unchanged. */
.privacy-card{
  background:var(--color-bg);
  border:1px solid var(--color-border);
  border-radius:var(--radius-md);
  padding:var(--space-6);
}

.privacy-card-title{
  font-size:var(--text-md);
}

.privacy-card-body{
  font-size:var(--text-base);
  color:var(--color-text-secondary);
  margin-top:var(--space-2);
}

/* ----- Single wide highlighted card: the disclosed opt-in exception ----- */
/* Highlighted a step above the three cards so it reads as the section's focal
   block — the full-reading-weight disclosure the privacy claim requires, not a
   footnote: it carries the same page-default --color-bg fill as the three cards,
   but the larger lg radius and the md shadow lift it a step above them. Shield
   icon sits at the left, flex row so the copy flows to its right and reflows
   below it on narrow widths. */
.privacy-exception{
  display:flex;
  gap:var(--space-4);
  align-items:flex-start;
  margin-top:var(--space-4);
  background:var(--color-bg);
  border:1px solid var(--color-border);
  border-radius:var(--radius-lg);
  box-shadow:var(--shadow-md);
  padding:var(--space-6);
}

/* Shield glyph box. currentColor is set here (inherits to the SVG stroke) to the
   secondary tone so the icon is a quiet accent, not a loud badge. flex:0 0 auto
   keeps it from squashing as the text wraps. 1.5rem is a structural icon dim,
   not a token. */
.privacy-exception-icon{
  flex:0 0 auto;
  width:1.5rem;
  height:1.5rem;
  color:var(--color-text-secondary);
}

.privacy-exception-icon svg{
  width:100%;
  height:100%;
  display:block;
}

.privacy-exception-title{
  font-size:var(--text-lg);
}

/* Body copy — secondary tone, relaxed leading for the longer disclosure, capped
   to the shared prose measure. */
.privacy-exception-body{
  font-size:var(--text-base);
  color:var(--color-text-secondary);
  max-width:var(--measure-prose);
  margin-top:var(--space-3);
  line-height:var(--leading-relaxed);
}

/* Bold the "off by default" clause (the <strong> in markup): lift it to the
   primary text tone so it stands out from the secondary body around it. */
.privacy-exception-body strong{
  color:var(--color-text);
  font-weight:var(--weight-semibold);
}

/* Three columns at the shared 60rem breakpoint (same one the hero + how-it-works
   + features use). */
@media (min-width: 60rem){
  .privacy-cards{
    grid-template-columns:repeat(3, 1fr);
  }
}

/* ----- Scroll reveal -----
   Handled by the GENERIC reveal ruleset (7.A.12/7.A.13, §2). The .privacy section
   is the .reveal group; its risers are the kicker, the title, the body, then — for
   each of the three on-device cards — the card, its title, and its body, and for
   the wide exception card its icon, title, and paragraph (7.A.13), all staggering
   in by flat document order. So each card's title and body reveal on their own
   beats after the card, and the exception's icon/title/paragraph are three
   separate pieces. Reduced motion falls to the generic guard. */

/* ---------- 6. Pricing preview (7.A.7) ---------- */
/* Three plans, one trial — NO prices and NO feature matrix (the app is
   testers-only for now; the real prices + the full matrix live on the future
   /pricing page, README §3 the source of truth). Deliberately on the DEFAULT
   page background and DEFAULT raised card surface — NOT the privacy section's
   lifted charcoal band — so it sits on the same footing as how-it-works and
   features. Uses the shared .section rhythm + the shared .container gutter, so
   its left edge lines up with every other section. Raw numbers below are
   structural layout dims (rem caps, the three-card grid) or the page's shared
   --dur-reveal reveal duration / --reveal-rise rise distance carried over from sections 2–5
   so the whole page shares one slow, calm motion language. None is a
   token-governed value (colour, spacing, radius, type, shadow, motion) — each is
   commented at its use site. */

/* Extra breathing room between the end of the privacy section's grey band and
   this section. The privacy band already closes with its own --space-section-y
   bottom padding; this adds a further --space-9 gap on top of it so the grey
   band's lower edge reads as clearly separated from the pricing content, not
   butting up against it. --space-9 is an existing spacing token, no raw px —
   same approach the privacy band itself uses for its top offset. */
.pricing-preview{
  margin-block-start:var(--space-9);
}

/* Centred header block (heading + subhead), like the problem section's quiet
   beat. 44rem is a structural rem cap — same category as the problem measure
   and the privacy title cap — not a design token. */
.pricing-preview-head{
  max-width:44rem;
  margin-inline:auto;
  text-align:center;
}

/* Display heading. Shares the section-heading weight used by the problem and
   privacy titles (--text-5xl) — a clear step below the hero (--text-6xl).
   text-wrap:balance evens the short line (progressive enhancement, ignored
   where unsupported). */
.pricing-preview-title{
  font-size:var(--text-5xl);
  text-wrap:balance;
}

/* Subhead — mirrors the hero subhead / problem body (size, secondary tone) so
   the sections read as one system; centred within the capped head block. */
.pricing-preview-subhead{
  font-size:var(--text-lg);
  color:var(--color-text-secondary);
  margin-top:var(--space-5);
}

/* ----- Three-column card row ----- */
/* Mobile-first single column; the three columns come in at the shared 60rem
   breakpoint (bottom of this block). */
.pricing-preview-cards{
  display:grid;
  grid-template-columns:1fr;
  gap:var(--space-5);
  margin-top:var(--space-8);
}

/* Card shell — the DEFAULT raised card surface (matches the shared .card and
   the feature cards), on the default page background. flex column so the
   "10-day trial" label can pin to the bottom (margin-top:auto below) and the
   three cards read as an even row regardless of positioning-line length. */
.pricing-plan{
  display:flex;
  flex-direction:column;
  background:var(--color-surface-raised);
  border:1px solid var(--color-border);
  border-radius:var(--radius-lg);
  box-shadow:var(--shadow-md);
  padding:var(--space-6);
}

/* Tier name — the card's headline, a step below the section heading. Basic keeps
   the normal --color-text (inherited); Pro and Max take the shared brushed-metal
   gradient text via the .metal-text classes in the markup (see the modifier
   below for the box-hugging that makes the full sweep read at heading size). */
.pricing-plan-name{
  font-size:var(--text-2xl);
}

/* Positioning line — one line per tier, secondary tone, capped to a comfortable
   measure (harmless inside the narrow card; matches the feature-card body). */
.pricing-plan-line{
  font-size:var(--text-md);
  color:var(--color-text-secondary);
  max-width:var(--measure-prose);
  margin-top:var(--space-3);
}

/* Small "10-day trial" label, pinned to the bottom of the card: margin-top:auto
   pushes it down (so all three labels line up when the grid stretches the cards
   to equal height), and padding-top guarantees a gap above it even when the
   positioning line is short. Quiet tertiary tone. */
.pricing-plan-trial{
  margin-top:auto;
  padding-top:var(--space-5);
  font-size:var(--text-sm);
  color:var(--color-text-tertiary);
}

/* ----- Tier-name gradient text (Pro = silver, Max = gold) -----
   README §3 tiers; app design-system SKILL §4 (Plan gradients). Pro and Max reuse
   the EXACT same brushed-metal gradient asset as the features-section badges —
   the fill, the background-clip:text mechanics, the theme re-aiming, and the
   solid-metal fallback all live once in .metal-text* (features section above);
   the markup gives these headings `metal-text metal-text--pro|--max`. Basic has
   no modifier and keeps the normal heading colour (--color-text).

   The ONLY thing added here is box-hugging: an <h3> is a block-level flex item and
   would stretch to the full card width, so background-clip:text would clip only
   the left slice of the gradient onto the short word — the sweep would look washed
   (one metal, no sheen). align-self:flex-start shrinks the box to the glyphs so the
   whole deep→bright→deep sweep lands across the word, exactly as it does on the
   inline badges. Same colours, same angle, same stops — no stop-position change
   was needed once the box hugs the text. */
.pricing-plan-name--pro,
.pricing-plan-name--max{
  align-self:flex-start;
}

/* ----- Compare-plans link (centred, out to the future /pricing page) ----- */
.pricing-preview-foot{
  margin-top:var(--space-7);
  text-align:center;
}

/* Small secondary text link, same treatment as the hero's secondary link
   (medium weight, secondary tone, hovers to the primary text colour). */
.pricing-preview-compare{
  font-size:var(--text-base);
  font-weight:var(--weight-medium);
  color:var(--color-text-secondary);
  transition:color var(--dur-fast) var(--ease-standard);
}

.pricing-preview-compare:hover{
  color:var(--color-text);
}

/* Three columns at the shared 60rem breakpoint (same one every other section
   uses). */
@media (min-width: 60rem){
  .pricing-preview-cards{
    grid-template-columns:repeat(3, 1fr);
  }
}

/* ----- Scroll reveal -----
   Handled by the GENERIC reveal ruleset (7.A.12/7.A.13, §2). The .pricing-preview
   section is the .reveal group; its risers are the title, the subhead, then — for
   each of the three tier cards — the card, its tier name, its positioning line,
   and its "10-day trial" label (7.A.13), and finally the compare-plans link, all
   staggering in by flat document order. So each tier card's name, line, and trial
   label are three separate staggered pieces, not the card as one block. Reduced
   motion falls to the generic guard. */

/* ---------- 7. FAQ (7.A.8) ---------- */
/* Seven Q&A rows as a VERTICAL one-open-at-a-time accordion — the SAME
   interaction contract as the section-4 features strip (exactly one open,
   default first, hover/tap/focus opens, the last-opened stays open on leave),
   but height-based instead of the features' width-based strip. The shared
   one-open STATE engine lives in accordion.js; features.js and faq.js both
   consume it, so the JS is factored, not duplicated.

   Progressive enhancement, identical guarantees to the features strip:
   accordion.js adds .js-ready to the list, and that class is the ONLY thing
   that switches this section from its no-JS fallback (every answer fully
   shown, plain stacked list) to the collapsed one-open accordion. With no JS
   every answer is readable; under prefers-reduced-motion the expand still
   works but the height SNAPS (--dur-slow zeroes in base.css). Visibility is
   never JS-gated.

   Deliberately a plain text list — a thin separator between rows, no card
   fill and no radius — NOT a card grid. Sits on the DEFAULT page background
   and DEFAULT surface (not the privacy band). Uses the shared .section rhythm
   + .container gutter, so its left edge lines up with every other section.
   Raw numbers below are structural layout dims (rem caps, the icon box) or
   the page's shared --dur-reveal reveal duration / --reveal-rise rise distance carried
   over from sections 2–6 so the whole page shares one slow, calm motion
   language. None is a token-governed value (colour, spacing, radius, type,
   shadow, motion) — each is commented at its use site. */

/* Centred section heading, matching the problem / pricing-preview beats
   (--text-5xl, one step below the hero --text-6xl). text-wrap:balance evens
   the short line (progressive enhancement, ignored where unsupported). */
.faq-title{
  font-size:var(--text-5xl);
  text-align:center;
  text-wrap:balance;
}

/* The list — a plain vertical stack at full content width, capped to a
   comfortable reading measure and centred. The list's top keyline plus each
   row's bottom keyline (below) bracket every row like a plain list. 46rem is
   a structural rem cap — same category as the problem measure and the pricing
   head cap — not a design token. */
.faq-list{
  max-width:46rem;
  margin-inline:auto;
  margin-top:var(--space-8);
  border-top:1px solid var(--color-border);
}

/* Each row — separated only by a thin bottom keyline. No fill, no radius. */
.faq-item{
  border-bottom:1px solid var(--color-border);
}

/* The <h3> is a semantic heading wrapper only (keeps the heading order
   h1 -> h2 -> h3, like the how-it-works rows and the feature cards); the
   BUTTON inside carries the visible type. font:inherit drops the base h3
   weight/tracking/leading so the button starts from body defaults and sets
   its own. */
.faq-q-head{
  margin:0;
  font:inherit;
}

/* The question is a REAL button filling the row width — the whole collapsed
   row IS this button, so it is also the hover target. Question text left,
   the chevron pinned right. Keyboard focus uses the global :focus-visible
   ring (base.css) — the button is not clipped, so no custom inset ring is
   needed (unlike the overflow-clipped feature cards). */
.faq-q{
  display:flex;
  width:100%;
  align-items:center;
  justify-content:space-between;
  gap:var(--space-4);
  padding-block:var(--space-5);
  background:transparent;
  border:0;
  cursor:pointer;
  text-align:left;
  color:var(--color-text);
  font-size:var(--text-lg);
  font-weight:var(--weight-medium);
  line-height:var(--leading-normal);
}

/* Chevron — quiet secondary tone, points down at rest and rotates to point up
   when the row is open. The rotate is scoped to .js-ready: in the no-JS
   fallback every answer is already shown, so a static down-chevron there does
   not claim a collapsed state it can't deliver; only the interactive accordion
   (which actually collapses rows) rotates it. aria-hidden in markup — the
   button's aria-expanded carries the real state. 1rem is a structural icon
   dim, not a token. */
.faq-icon{
  flex:0 0 auto;
  width:1rem;
  height:1rem;
  color:var(--color-text-secondary);
}

.faq-icon svg{
  display:block;
  width:100%;
  height:100%;
}

.faq-list.js-ready .faq-icon{
  transition:transform var(--dur-slow) var(--ease-out);
}

.faq-list.js-ready .faq-item.is-open .faq-icon{
  transform:rotate(180deg);
}

/* Answer wrapper.
   NO-JS FALLBACK (no .js-ready): a plain block — every answer fully shown.
   INTERACTIVE (.js-ready): a one-row grid whose track animates 1fr <-> 0fr,
   the modern height-auto transition (smooth ease-out open/close). Making
   display:grid conditional on .js-ready is deliberate: grid-template-rows is
   introduced only WITH .js-ready, so a collapsed row starts at 0fr with no
   prior value to animate from — no collapse flash on load (the same reason the
   features strip's flex->grid switch never flashes). --dur-slow zeroes under
   reduced motion (base.css), so it snaps but stays fully functional. */
.faq-list.js-ready .faq-a-wrap{
  display:grid;
  grid-template-rows:1fr;
  transition:grid-template-rows var(--dur-slow) var(--ease-out);
}

.faq-list.js-ready .faq-item:not(.is-open) .faq-a-wrap{
  grid-template-rows:0fr;
}

/* The clipped grid row: min-height:0 lets it collapse below content height,
   overflow:hidden hides the answer as the track closes. Harmless in the no-JS
   block state (height is auto there). */
.faq-a{
  min-height:0;
  overflow:hidden;
}

/* Answer body — secondary tone + relaxed leading, matching the privacy
   exception copy. padding-bottom gives the open answer air above the next
   row's keyline; it lives inside .faq-a so it is clipped away as the row
   closes (no residual gap on a collapsed row). */
.faq-a-body{
  font-size:var(--text-base);
  color:var(--color-text-secondary);
  line-height:var(--leading-relaxed);
  padding-bottom:var(--space-5);
}

/* The /tester link inside answer 7 — same treatment as the base .prose link
   (primary text tone, underline, 2px offset), with a quiet hover to secondary.
   The page ships later; the href is correct now and 404s honestly until then. */
.faq-a-body a{
  color:var(--color-text);
  text-decoration:underline;
  text-underline-offset:2px;   /* structural, keeps the rule off the descenders */
  font-weight:var(--weight-medium);
  transition:color var(--dur-fast) var(--ease-standard);
}

.faq-a-body a:hover{
  color:var(--color-text-secondary);
}

/* ----- Scroll reveal -----
   Handled by the GENERIC reveal ruleset (7.A.12, §2). The .faq section is the
   .reveal group; its risers (§4) are the heading and each of the seven Q&A rows
   individually (each .faq-item is tagged .r-rise), staggering in by source
   order. The per-row reveal is a translateY/opacity on the .faq-item — a
   different element from the accordion's grid-rows transition on the descendant
   .faq-a-wrap, so the two never fight over `transition`. The accordion stays
   fully interactive under reduced motion (it just snaps). Reduced motion for the
   reveal falls to the generic guard. */


/* ---------- 8. Final CTA (7.A.9) ---------- */
/* The page's closing waitlist ask: a large centred display heading, an inline
   email + submit form (side by side on desktop, stacked on mobile), and a small
   test-request affordance under it. Sits on the DEFAULT page background + card
   surface (no background rule at all — it inherits the body) — NOT the privacy
   band's charcoal and NOT the mockup's inverted treatment. Uses the shared
   .section rhythm + .container gutter so its left edge lines up with every other
   section.

   The submit button reuses the shared .btn / .btn-primary and its new shared
   :disabled state (components.css); it ships `disabled` until 7.C. Raw numbers
   below are structural layout dims (the form's rem width caps + its own small
   breakpoint) or the page's shared --dur-reveal reveal duration / --reveal-rise rise carried
   over from sections 2–7 so the whole page shares one calm motion language. None
   is a token-governed value (colour, spacing, radius, type, shadow, motion) —
   each is commented at its use site. */
.final-cta{
  text-align:center;
}

/* Large centred display heading — the section-heading weight used by the
   problem / privacy / pricing / faq titles (--text-5xl, one step below the hero
   --text-6xl). The two lines are forced by a <br> in the markup; text-wrap:balance
   still evens each forced line where a viewport is too narrow to hold it on one
   row (progressive enhancement, ignored where unsupported). Centres via the
   section's text-align:center. */
.final-cta-title{
  font-size:var(--text-5xl);
  text-wrap:balance;
}

/* ----- Inline waitlist form -----
   Mobile-first: a stacked column, input then button, both full width (the flex
   column stretches its items). The centred max-width keeps the stacked fields
   from spanning the whole page on a phone held wide. */
.final-cta-form{
  display:flex;
  flex-direction:column;
  gap:var(--space-3);
  max-width:30rem;        /* structural cap for the stacked form — not a token */
  margin-inline:auto;
  margin-top:var(--space-8);
}

/* Email input. No input primitive exists in the site yet, so it is styled from
   tokens here: the DEFAULT raised surface, a strong hairline border, and the
   pill radius so it pairs cleanly with the pill submit button beside it. Keyboard
   focus uses the global :focus-visible ring (base.css) — no custom focus rule,
   matching every other control on the site. Vertical padding matches .btn
   (--space-2) so input and button share a height when they sit side by side. */
.final-cta-input{
  padding-block:var(--space-2);
  padding-inline:var(--space-5);
  background:var(--color-surface-raised);
  border:1px solid var(--color-border-strong);
  border-radius:var(--radius-pill);
  color:var(--color-text);
  font-size:var(--text-base);
  transition:border-color var(--dur-fast) var(--ease-standard);
}

.final-cta-input::placeholder{
  color:var(--color-text-tertiary);
}

.final-cta-input:hover{
  border-color:var(--color-text-tertiary);
}

/* Side by side once there is room. 30rem is a structural layout dim — a
   two-field input+button row reads comfortably from ~480px up — NOT the shared
   60rem section breakpoint, which governs the wide 3-column card grids; a short
   inline form goes horizontal far earlier than those do. The input grows to
   fill; min-width:0 lets it shrink below its content width so the button always
   keeps its natural size on the same line. */
@media (min-width: 30rem){
  .final-cta-form{
    flex-direction:row;
    max-width:34rem;
  }

  .final-cta-input{
    flex:1 1 auto;
    min-width:0;
  }

  .final-cta-submit{
    flex:0 0 auto;
  }
}

/* ----- Early-access offer line ----- */
/* The offer statement under the form. Quiet secondary text at base size — the
   same tone as the tester lead-in beneath it, so the two lines read as a
   consistent pair under the form rather than one shouting over the other; the
   section's restraint (its only microcopy) is preserved. Centred via the
   section's text-align:center; --space-6 top gap matches the tester's, giving
   an even rhythm form → offer → tester. */
.final-cta-offer{
  margin-top:var(--space-6);
  font-size:var(--text-base);
  color:var(--color-text-secondary);
}

/* ----- Test-request affordance ----- */
/* Small line under the form. The lead-in is quiet secondary text; the link
   portion (ending in the arrow, no underline — the arrow is the affordance)
   takes the primary text colour at medium weight and hovers to secondary, the
   same treatment as the pricing-preview "Compare plans →" link. */
.final-cta-tester{
  margin-top:var(--space-6);
  font-size:var(--text-base);
  color:var(--color-text-secondary);
}

/* The quiet arrow-link treatment itself. SHARED WITH /tester SECTION 2 (7.A.19):
   that page's "How your data stays yours →" link is the same affordance, so
   .ts-privacy-link joins the selector rather than tester.css restating these three
   declarations — one definition, two consumers, exactly like .privacy-kicker /
   .ts-kicker and the hero layout rules above. Declarations unchanged; the landing
   final-CTA line is untouched. (/waitlist section 4 carries a third copy of this
   treatment in waitlist.css §8, authored before there was a second consumer to
   share with; if this is ever touched again, that one should be folded in here
   too — or all three moved up to components.css as a real site-wide quiet-link
   primitive.) */
.final-cta-tester a,
.ts-privacy-link a{
  color:var(--color-text);
  font-weight:var(--weight-medium);
  transition:color var(--dur-fast) var(--ease-standard);
}

.final-cta-tester a:hover,
.ts-privacy-link a:hover{
  color:var(--color-text-secondary);
}

/* ----- Scroll reveal -----
   Handled by the GENERIC reveal ruleset (7.A.12, §2). The .final-cta section is
   the .reveal group; its risers (§4) are the heading, the waitlist form, the
   7.C privacy notice, the offer line and the tester line, staggering in by
   source order. The form's two controls (email input + submit) reveal together
   as the single form unit — a deliberate grouping, since they are an inline
   side-by-side pair and staggering them against each other would read as a
   glitch, not a sequence. The form's HONEYPOT wrapper is not a riser and could
   not be one: it is off-screen and aria-hidden, so a beat spent on it would be
   a beat spent on nothing. The failure line is not a riser either — it is
   `hidden` until a submission fails, by which point its section has long since
   revealed. Reduced motion falls to the generic guard. */

/* ---------- 9. Footer reveal ---------- */
/* Handled by the GENERIC reveal ruleset (7.A.12/7.A.13, §2). The .footer is the
   .reveal group; in 7.A.13 the risers moved OFF the .footer-brand / .footer-links
   containers and ONTO each individual piece — the logo link, the tagline, each of
   the six links in turn, and the copyright line — so they cascade one at a time in
   flat document order (logo -> tagline -> Privacy -> Terms -> email -> Instagram
   -> X -> Reddit -> copyright), not brand-block then links-block. The footer
   styling itself lives in components.css; only its scroll-reveal is a landing
   concern. Reduced motion falls to the generic guard.
   NOTE: making each footer <a> a riser means the generic .r-rise transition wins
   over .footer a's colour transition (higher specificity), so a footer link's
   hover colour change becomes instant rather than fading — a deliberate, minor
   trade for the per-link reveal; every other link on the page is unaffected. */
