/* ==========================================================================
   Heala — heala.fit homepage (2026 visual-first rebuild)

   THE APP IS THE CONTENT.
   Six pixel-transcribed replicas of real Heala screens carry this page. The
   site chrome exists only to hold them up: warm paper, two ink bands, one
   violet accent, and as little type as a page can legally carry.

   Architecture notes that are easy to get wrong later:
     • <main class="page"> is a named-line grid. Everything sits in the
       `content` column; anything that needs edge-to-edge paint gets .bleed
       plus an .inner wrapper that restores the same measure.
     • The replicas ship their OWN <style> blocks (namespaced .rep-*), inlined
       in index.html's <head>. Nothing in this file may reach into them —
       which is why there is no bare `svg` rule anywhere below. A global
       `svg{height:auto}` silently rewrites the ~19 attribute-sized SVGs
       inside the scan and cardio replicas.
     • Reveals are IntersectionObserver-driven (app.js adds .in). The
       opacity:0 start state is gated behind .js-reveal on <html>, which
       app.js's dead-man timer removes — so a script that never arrives can
       never leave the page blank.
     • The LAST block of this file is SHARED with privacy.html, terms.html,
       p.html, s.html and twin.html — those pages link this stylesheet and are
       byte-frozen. Do not delete it.
   ========================================================================== */

/* ── 0. THE DISPLAY FACE ──────────────────────────────────────────────────
   The same two files index.html loads, declared again here because these
   pages do not link app-v2.css. Both files, both @font-face blocks: a browser
   only downloads the ranges a page actually uses, so declaring latin-ext
   costs nothing on an English page and is the difference between a Turkish
   heading rendering and falling back mid-word.

   ⚠️ THE PATH IS RELATIVE TO THIS FILE, which sits beside index.html at the
   site root — so "assets/…" resolves the same from /privacy as from /. */
@font-face {
  font-family: "Heala Display";
  src: url("assets/nunito-latin.woff2") format("woff2");
  font-weight: 700 900; font-style: normal; font-display: swap;
  unicode-range: U+0000-00FF, U+0131, U+0152-0153, U+02BB-02BC, U+02C6, U+02DA,
                 U+02DC, U+0304, U+0308, U+0329, U+2000-206F, U+20AC, U+2122,
                 U+2191, U+2193, U+2212, U+2215, U+FEFF, U+FFFD;
}
@font-face {
  font-family: "Heala Display";
  src: url("assets/nunito-latin-ext.woff2") format("woff2");
  font-weight: 700 900; font-style: normal; font-display: swap;
  unicode-range: U+0100-02BA, U+02BD-02C5, U+02C7-02CC, U+02CE-02D7, U+02DD-02FF,
                 U+0304, U+0308, U+0329, U+1D00-1DBF, U+1E00-1E9F, U+1EF2-1EFF,
                 U+2020, U+20A0-20AB, U+20AD-20C0, U+2113, U+2C60-2C7F, U+A720-A7FF;
}

/* ── 1. TOKENS ─────────────────────────────────────────────────────────── */

:root{
  color-scheme: light dark;

  --font-display:"Heala Display", ui-rounded, "SF Pro Rounded", system-ui,
                 -apple-system, "Segoe UI", Roboto, sans-serif;

  /* surfaces — exactly two layers, ever */
  --bg:#F8F6FD;               /* page — the same paper as app-v2.css */
  --raised:#FFFFFF;           /* every card plate */
  --surface:rgba(0,0,0,.04);
  --surface-2:rgba(0,0,0,.08);
  --track:rgba(0,0,0,.10);
  --track-strong:rgba(0,0,0,.15);
  --line:rgba(48,20,96,.11);  /* THE primary divider */
  /* .43, not .22: --line-strong is the ONLY thing that identifies the Free
     plan's outline button as a control, and .22 measured 1.69:1 on white
     against the 3:1 that SC 1.4.11 requires. */
  --line-strong:rgba(0,0,0,.43);

  /* ink */
  --ink:#150F22;              /* not pure black */
  --subtle:rgba(21,15,34,.70);
  --muted:rgba(21,15,34,.52);
  --faint:rgba(21,15,34,.28);

  /* accent — violet, ≤5% of pixels */
  --accent:#6D28D9;           /* 7.12:1 on #FFF, 6.28:1 on --bg */
  --accent-ink:#FFFFFF;
  --accent-wash:rgba(109,40,217,.10);
  --accent-glow:rgba(109,40,217,.14);

  /* data hues — kept for the frozen pages */
  --protein:#F4795B; --carbs:#46B4D6; --fat:#F2B33D;
  --score-red:#C0291C; --score-amber:#C26A00; --score-green:#1C8F46;
  --coral:#FF3B30;

  /* THE app's own page colour. Every device screen paints this so a replica
     shorter than its frame blends into the bezel instead of ending in a seam.
     It is minimalistLightTheme.bg from lib/theme.ts, not a site colour — it
     must not drift with the site palette or with dark mode. */
  --app-paper:#F3F1ED;

  /* elevation */
  --shadow:0 3px 8px rgba(0,0,0,.08);
  --shadow-pop:0 8px 18px rgba(0,0,0,.12);
  --device-shadow:0 44px 90px -38px rgba(11,14,20,.42), 0 10px 26px -14px rgba(11,14,20,.18);
  --highlight:rgba(255,255,255,.65);

  --focus:#5B3FD9;

  /* type */
  --font: system-ui, -apple-system, "Segoe UI", Roboto, "Helvetica Neue", Arial, sans-serif;
  --mono: ui-monospace, SFMono-Regular, "SF Mono", Menlo, Consolas, monospace;

  /* geometry */
  --r-xs:8px; --r-sm:10px; --r-md:12px; --r-lg:16px; --r-xl:22px; --r-2xl:28px; --r-pill:999px;
  --gutter:20px; --header-h:56px; --dock-h:68px; --maxw:1120px;
  --dev-w:320px;              /* device frame width; sections override it */

  /* motion */
  --dur-micro:160ms; --dur-std:240ms; --dur-hero:320ms;
  --ease-out:cubic-bezier(.33,1,.68,1);
  --ease-inout:cubic-bezier(.37,0,.63,1);
  --ease-pop:cubic-bezier(.34,1.4,.64,1);
}

@media (min-width:900px){ :root{ --gutter:32px; --header-h:64px; } }
/* Above ~1360 the 1120px measure left 320px of bare paper down each side of a
   page whose whole argument is "look at the screens". The measure widens; the
   text does not (h1/h2 are capped in ch, .footer-legal p in 52ch), so the
   extra track goes to the device frames and nowhere else. */
@media (min-width:1360px){ :root{ --maxw:1240px; } }

/* Dark. Repeated verbatim under [data-theme="dark"] so an explicit toggle
   always beats the media query. --app-paper is deliberately absent: the
   replicas are light-theme transcriptions and stay light in both schemes. */
@media (prefers-color-scheme:dark){ :root:not([data-theme="light"]){
  --bg:#17142B; --raised:#262146;
  --surface:rgba(255,255,255,.07); --surface-2:rgba(255,255,255,.13);
  --track:rgba(255,255,255,.12); --track-strong:rgba(255,255,255,.18);
  --line:rgba(178,160,240,.17); --line-strong:rgba(178,160,240,.42);
  --ink:#EFEAFF; --subtle:rgba(226,218,252,.72);
  --muted:rgba(190,178,226,.58); --faint:rgba(190,178,226,.32);
  --accent:#B49CFF;            /* 8.1:1 on #17142B */
  --accent-ink:#100A1F;
  --accent-wash:rgba(180,156,255,.13); --accent-glow:rgba(180,156,255,.20);
  --score-red:#E5484D; --score-amber:#F5A523; --score-green:#3EC56B;
  --coral:#FF5F52;
  --shadow:0 3px 8px rgba(0,0,0,.22); --shadow-pop:0 8px 18px rgba(0,0,0,.35);
  --device-shadow:0 44px 90px -38px #000, 0 10px 26px -14px rgba(0,0,0,.6);
  --highlight:rgba(255,255,255,.08);
  --focus:#B49CFF;
}}
:root[data-theme="dark"]{
  /* The violet night the rest of the site moved to on 2026-08-28. This was
     the last neutral-grey page left, and arriving here from the footer read
     as leaving for a different product. */
  --bg:#17142B; --raised:#262146;
  --surface:rgba(255,255,255,.07); --surface-2:rgba(255,255,255,.13);
  --track:rgba(255,255,255,.12); --track-strong:rgba(255,255,255,.18);
  --line:rgba(178,160,240,.17); --line-strong:rgba(178,160,240,.42);
  --ink:#EFEAFF; --subtle:rgba(226,218,252,.72);
  --muted:rgba(190,178,226,.58); --faint:rgba(190,178,226,.32);
  --accent:#B49CFF; --accent-ink:#100A1F;
  --accent-wash:rgba(180,156,255,.13); --accent-glow:rgba(180,156,255,.20);
  --score-red:#E5484D; --score-amber:#F5A523; --score-green:#3EC56B;
  --coral:#FF5F52;
  --shadow:0 3px 8px rgba(0,0,0,.22); --shadow-pop:0 8px 18px rgba(0,0,0,.35);
  --device-shadow:0 44px 90px -38px #000, 0 10px 26px -14px rgba(0,0,0,.6);
  --highlight:rgba(255,255,255,.08);
  --focus:#B49CFF;
}

/* The ink scope — dark in BOTH colour schemes. Used by the two showcase
   bands and the closer. */
.ink{
  --bg:#0B0D12; --raised:#151821;
  --ink:#FFFFFF; --subtle:rgba(255,255,255,.72);
  --muted:rgba(255,255,255,.56); --faint:rgba(255,255,255,.30);
  --line:rgba(255,255,255,.13); --line-strong:rgba(255,255,255,.40);
  --surface:rgba(255,255,255,.07); --track:rgba(255,255,255,.12);
  --track-strong:rgba(255,255,255,.18);
  --accent:#C4B5FD; --accent-ink:#1A0E2E;
  --accent-wash:rgba(180,156,255,.14); --accent-glow:rgba(180,156,255,.22);
  --highlight:rgba(255,255,255,.08);
  --shadow:0 3px 8px rgba(0,0,0,.40);
  --focus:#C4B5FD;
  background:var(--bg); color:var(--ink);
}

/* ── 2. RESET + BASE TYPE ──────────────────────────────────────────────── */

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

/* clip, NOT hidden — `hidden` on an ancestor silently kills position:sticky */
html,body{ overflow-x:clip; }
html{ scroll-behavior:smooth; -webkit-text-size-adjust:100%; }
body{
  margin:0; background:var(--bg); color:var(--ink);
  font-family:var(--font); font-size:16px; line-height:1.6;
  -webkit-font-smoothing:antialiased;
}
/* NO global `svg` rule — see the header note. Site icons are sized one by one.
   `img` is safe: the only images inside a replica are the four integration
   marks in the cardio card, and `.rep-cardio .rc-mark img` outranks this. */
img{ display:block; max-width:100%; height:auto; }
video{ max-width:100%; }
.sprite{ position:absolute; width:0; height:0; overflow:hidden; }
.ic{ flex:none; }
a{ color:inherit; text-decoration:none; }
button{ font:inherit; color:inherit; border:0; background:none; cursor:pointer; }
h1,h2,h3,h4,p,figure{ margin:0; }
ul,ol{ margin:0; padding:0; list-style:none; }
[hidden]{ display:none !important; }
::selection{ background:var(--accent-wash); }

/* ⚠️ ROUNDED FACES WANT LESS NEGATIVE TRACKING, NOT MORE. -.038em was tuned
   for SF Pro; carried onto Nunito unchanged it closes the counters and a
   large heading turns into a dark bar. */
h1,h2,h3{ font-family:var(--font-display); }
h1{ font-size:clamp(2.125rem,8.4vw,4.25rem); font-weight:900;
    letter-spacing:-.026em; line-height:1.0; text-wrap:balance; }
h2{ font-size:clamp(1.75rem,6vw,3rem); font-weight:900;
    letter-spacing:-.024em; line-height:1.06; text-wrap:balance; }
h3{ font-size:clamp(1.1875rem,2vw,1.375rem); font-weight:800;
    letter-spacing:-.016em; line-height:1.28; }
p{ font-size:clamp(.9375rem,1.1vw,1rem); font-weight:500; letter-spacing:-.004em;
   line-height:1.6; text-wrap:pretty; max-width:56ch; }
h1,h2,h3,p,li{ overflow-wrap:anywhere; }
h1 em,h2 em{ font-style:normal; color:var(--accent); }

.eyebrow{
  font-size:10.5px; font-weight:800; letter-spacing:.15em; line-height:1.4;
  text-transform:uppercase; color:var(--accent);
}
.eyebrow--mute{ color:var(--muted); }
.lede{ font-size:clamp(1rem,1.5vw,1.1875rem); font-weight:500; letter-spacing:-.008em;
       line-height:1.5; color:var(--subtle); }
.mono{ font-family:var(--mono); font-size:11.5px; font-weight:500; letter-spacing:.01em; }
.num{ font-variant-numeric:tabular-nums; }

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

/* ── 3. PAGE GRID + BANDS ──────────────────────────────────────────────── */

.page{
  display:grid;
  grid-template-columns:
    [full-start] minmax(var(--gutter),1fr)
    /* maxw minus the two gutters, so a content-column section lines up
       exactly with an .inner inside a .bleed section */
    [content-start] minmax(0, calc(var(--maxw) - var(--gutter) * 2))
    [content-end] minmax(var(--gutter),1fr) [full-end];
}
.page > *{ grid-column:content; }
.bleed{ grid-column:full; }
.inner{ width:100%; max-width:var(--maxw); margin-inline:auto; padding-inline:var(--gutter); }

/* Every anchor target clears the sticky header (WCAG 2.2 SC 2.4.11). */
[id]{ scroll-margin-top:calc(var(--header-h) + 16px); }
/* scroll-margin on sections does nothing for SEQUENTIAL FOCUS navigation —
   shift-tabbing upward parks the focused link under the sticky header, and
   tabbing downward parks it under the fixed dock (SC 2.4.11 again). */
a,button,summary,[tabindex]{
  scroll-margin-top:calc(var(--header-h) + 16px);
  scroll-margin-bottom:calc(var(--dock-h) + 16px);
}

/* ── 4. BUTTONS, LINKS, FOCUS ──────────────────────────────────────────── */
/* Shape discipline: primary is ALWAYS a filled ink pill, secondary is ALWAYS
   a text link with a trailing arrow. They never read as equal siblings —
   not in the hero, not in the dock, not in the pricing cards, not in the
   closer. */

.btn{
  position:relative; display:inline-flex; align-items:center; justify-content:center;
  gap:8px; border-radius:var(--r-pill); font-weight:800; letter-spacing:-.012em;
  font-family:var(--font-display);
  white-space:nowrap; text-align:center;
  transition:transform var(--dur-micro) var(--ease-pop), opacity var(--dur-micro);
}
.btn:active{ transform:scale(.97); }
.btn-ink{ background:var(--ink); color:var(--bg); }
.btn-ink:hover{ opacity:.85; }
.btn-sm{ height:36px; padding:0 16px; font-size:13px; }
.btn-sm::after{ content:""; position:absolute; inset:-6px -4px; }
.btn-lg{ min-height:54px; padding:0 30px; font-size:15.5px; width:100%; }
.btn-lg:active{ transform:scale(.98); }

.tlink{
  position:relative; display:inline-flex; align-items:center; gap:6px;
  font-size:15px; font-weight:700; color:var(--subtle);
}
.tlink::after{ content:""; position:absolute; inset:-14px -8px; }
.tlink:hover{ color:var(--ink); text-decoration:underline; text-underline-offset:3px; }
.tlink svg{ transition:transform var(--dur-micro) var(--ease-out); }
.tlink:hover svg{ transform:translateX(2px); }

/* NO `border-radius` here. It would apply to the ELEMENT, not the ring, and at
   (0,1,0) it ties with `.btn` and wins on source order — so every pill on the
   page snapped to a 4px rectangle the instant it took keyboard focus. */
:focus-visible{ outline:2px solid var(--focus); outline-offset:3px; }

.skip-link{
  position:absolute; left:-9999px; top:0; z-index:999;
  padding:10px 16px; border-radius:10px; background:var(--ink); color:var(--bg);
  font-size:14px; font-weight:700;
}
/* :focus too — programmatic focus does not always satisfy :focus-visible, and
   a skip link that stays at left:-9999px is a skip link that does not exist */
.skip-link:focus,.skip-link:focus-visible{ left:8px; top:8px; }

/* ── 5. REVEAL ─────────────────────────────────────────────────────────── */
/* IntersectionObserver only. The start state is scoped to .js-reveal on
   <html>, which the head script sets and app.js's 2.5s dead-man timer
   removes if the observer never armed. */

:root.js-reveal .reveal{
  opacity:0; transform:translateY(16px);
  transition:opacity 620ms var(--ease-out), transform 620ms var(--ease-out);
  transition-delay:var(--d,0ms);
  will-change:opacity, transform;
}
:root.js-reveal .reveal.in{ opacity:1; transform:none; will-change:auto; }
.reveal--1{ --d:70ms; } .reveal--2{ --d:140ms; } .reveal--3{ --d:210ms; }

/* ── 6. HEADER ─────────────────────────────────────────────────────────── */

.hdr{
  position:sticky; top:0; z-index:600;
  background:var(--bg);
  border-bottom:1px solid transparent;
  transition:border-color var(--dur-std) var(--ease-out);
}
/* double-guarded: the translucent fill only exists where the blur does, so a
   non-supporting engine never gets see-through chrome */
@supports (backdrop-filter:blur(1px)) or (-webkit-backdrop-filter:blur(1px)){
  .hdr{
    background:color-mix(in srgb, var(--bg) 80%, transparent);
    -webkit-backdrop-filter:saturate(170%) blur(14px);
    backdrop-filter:saturate(170%) blur(14px);
  }
}
.hdr.is-solid{ border-bottom-color:var(--line); }
.hdr-in{
  max-width:var(--maxw); margin-inline:auto;
  min-height:var(--header-h); padding-inline:var(--gutter);
  display:flex; align-items:center; gap:12px;
}
.brand{
  position:relative; display:inline-flex; align-items:center; gap:8px;
  font-size:16px; font-weight:800; letter-spacing:-.03em; margin-right:auto;
}
.brand::after{ content:""; position:absolute; inset:-11px -8px; }
.brand img{ width:26px; height:26px; border-radius:7px; }
.hdr-nav{ display:none; gap:22px; margin-right:6px; }
.hdr-nav a{ position:relative; font-size:14px; font-weight:600; color:var(--subtle); }
.hdr-nav a::after{ content:""; position:absolute; inset:-14px -8px; }
.hdr-nav a:hover{ color:var(--ink); }
@media (min-width:660px){ .hdr-nav{ display:flex; } }

/* ── 7. THE DEVICE PRIMITIVES ──────────────────────────────────────────── */
/* Three ways a real Heala screen lands on this page:
     .dev    — a phone. Bezel + 9:19.5 screen, content clipped like a scroll.
     .crop   — a bezel-less screen card whose height the replica decides.
     .poster — the shareable weekly card, which is not a screen at all.
   All three paint --app-paper, so a replica shorter than its box ends in the
   app's own paper rather than in a seam. */

.dev{
  position:relative; width:100%; max-width:var(--dev-w); margin-inline:auto;
  padding:7px; border-radius:46px;
  background:linear-gradient(168deg,#33363F 0%,#1B1E25 42%,#0E1015 100%);
  box-shadow:var(--device-shadow), inset 0 0 0 1px rgba(255,255,255,.13);
}
/* the entire skeuomorphic budget: one 1px lit top edge */
.dev::before{
  content:""; position:absolute; inset:0; border-radius:inherit;
  box-shadow:inset 0 1px 0 rgba(255,255,255,.22);
  pointer-events:none;
}
.dev-scr{
  position:relative; overflow:hidden; isolation:isolate;
  border-radius:39px; aspect-ratio:9 / 19.5;
  background:var(--app-paper); color:#000;
}
/* A replica shorter than the frame stretches into it rather than stopping
   mid-screen; a taller one clips at the bottom, exactly like a real scroll. */
.dev-scr > *{ min-height:100%; }
/* the frames sit on ink bands too, where a dark bezel needs its own rim */
.ink .dev{ box-shadow:0 44px 90px -34px #000, inset 0 0 0 1px rgba(255,255,255,.19); }

.crop{
  position:relative; width:100%; max-width:var(--dev-w); margin-inline:auto;
  overflow:hidden; isolation:isolate;
  border-radius:26px; border:1px solid var(--line);
  background:var(--app-paper); color:#000;
  box-shadow:var(--device-shadow);
}

/* The recap card is a 360x450 EXPORT, not a screen — it is resolution-free,
   so on desktop it gets to be bigger than a phone rather than the smallest
   thing on a page whose entire argument is "look at the app". */
.poster{ width:100%; max-width:360px; margin-inline:auto; }
@media (min-width:900px){ .poster{ max-width:452px; } }
@media (min-width:1360px){ .poster{ max-width:500px; } }

/* Two device frames side by side (the camera section). Below 560px they
   stack — two 150px-wide phones are not a demo, they are a thumbnail. */
.duo{ display:grid; gap:30px; justify-items:center; }
@media (min-width:560px){
  .duo{ grid-template-columns:1fr 1fr; gap:18px; align-items:start; }
  .duo > *:last-child{ margin-top:46px; }
}

/* ── 8. HERO ───────────────────────────────────────────────────────────── */

/* --dev-w lives on .hero, not on .hero-dev, because the desktop grid track
   is sized FROM it. An `auto` track here collapsed the phone to a dot: the
   only thing inside is a width:100% bezel wrapping an aspect-ratio box, so
   its max-content contribution is zero and `auto` obliged. */
.hero{ --dev-w:300px; padding-block:30px 10px; }
.hero h1{ max-width:13ch; }
.hero .lede{ margin-top:18px; max-width:33ch; }
.hero-ctas{ margin-top:26px; display:flex; flex-direction:column; align-items:flex-start; gap:16px; }
.hero-ctas .btn-lg{ width:100%; max-width:340px; }
.hero-dev{ margin-top:40px; }

@media (min-width:520px){
  .hero{ --dev-w:322px; }
  .hero-ctas{ flex-direction:row; align-items:center; gap:24px; }
  .hero-ctas .btn-lg{ width:auto; }
}
@media (min-width:900px){
  .hero{
    --dev-w:378px;
    padding-block:48px 24px;
    display:grid; grid-template-columns:minmax(0,1fr) var(--dev-w); gap:56px;
    /* start, not center: centring 367px of copy against a 780px phone left
       255px of dead paper exactly where the eye lands first. The phone runs
       past the fold on purpose — that is the scroll cue. */
    align-items:start;
  }
  .hero-copy{ padding-top:40px; }
  .hero-dev{ margin-top:0; }
}
@media (min-width:1080px){ .hero{ --dev-w:410px; gap:64px; } }
@media (min-width:1360px){ .hero{ --dev-w:452px; } }

/* ── 9. SHOWCASE SECTIONS ──────────────────────────────────────────────── */
/* One section per replica. On mobile the screen leads and the caption
   follows — done with flex `order`, not DOM order, because the replicas are
   aria-hidden decoration with nothing focusable inside them, so visual order
   and focus order cannot disagree. */

.show{ padding-block:62px; display:flex; flex-direction:column; gap:30px; }
.show-vis{ order:-1; min-width:0; }
.show-copy{ min-width:0; }
.show-copy > * + *{ margin-top:14px; }
.show-copy h2{ max-width:13ch; }
.show-copy p{ max-width:36ch; color:var(--subtle); }
.band-in{ padding-block:62px; }
.band-in .show{ padding-block:0; }

@media (min-width:900px){
  /* The split is deliberately lopsided — .72 / 1.28, not the old .92 / 1.08.
     A showcase section carries 6-17 words of caption against a whole phone
     screen; an even split spent 452px of a 1056px column on three lines of
     type and 700px of nothing, and the page measured 16.5% app UI against
     78% empty paper. Every point of track handed back here becomes screen. */
  .show{
    padding-block:112px;
    display:grid; grid-template-columns:minmax(0,.72fr) minmax(0,1.28fr);
    align-items:center; gap:56px;
  }
  .band-in{ padding-block:112px; }
  .show-vis{ order:0; --dev-w:404px; }
  /* The alternator — every other section puts the screen on the left.
     `order` swaps which TRACK each child lands in, but the track SIZES stay
     where they were authored, so a flipped section handed its wide track to
     the caption and squeezed two phones into 384px. Every flip therefore
     mirrors the template as well. */
  .show--flip{ grid-template-columns:minmax(0,1.28fr) minmax(0,.72fr); }
  .show--flip .show-copy{ order:2; }
  .show--wide{ grid-template-columns:minmax(0,.62fr) minmax(0,1.38fr); }
  .show--wide.show--flip{ grid-template-columns:minmax(0,1.38fr) minmax(0,.62fr); }
  /* two frames share the track, so each one takes less of it */
  .duo{ --dev-w:308px; }
  .pair{ --dev-w:344px; }
  /* The caption is 6-17 words against a 700-900px screen. Centred, it floats
     in the middle of a tall column with nothing to hold it; pinned, it rides
     the frame the whole way down. align-self:start is required — a centred
     grid item is shrink-wrapped and sticky has no room to travel. */
  .show-copy{ position:sticky; top:calc(var(--header-h) + 40px); align-self:start; }
  /* The recap section is a centred FLEX column, so `align-self:start` above
     does not mean "top" there — it means "left", and it knocked the centred
     caption off the poster's axis. Both properties have to be undone. */
  .show--centre .show-copy{ position:static; align-self:auto; }
}
/* Wide desktops — the frames grow with the measure. */
@media (min-width:1360px){
  .show-vis{ --dev-w:444px; }
  .duo{ --dev-w:334px; }
  .pair{ --dev-w:378px; }
}

/* Cardio pairs a tall session card with a short sources card, bottom-aligned
   so the mismatch reads as deliberate rather than as a layout bug. */
.pair{ display:grid; gap:24px; justify-items:center; }
@media (min-width:600px){
  .pair{ grid-template-columns:minmax(0,1fr) minmax(0,.86fr); align-items:end; gap:20px; }
}

/* The weekly card is a poster, not a screen — so it gets the one centred
   layout on the page. */
.show--centre{ text-align:center; }
.show--centre .show-copy h2,
.show--centre .show-copy p{ margin-inline:auto; }
@media (min-width:900px){
  .show--centre{ display:flex; flex-direction:column; align-items:center; gap:46px; }
  /* width:100% is load-bearing: `align-items:center` shrink-wraps a flex
     item, and .poster is width:100% of that — which resolved to zero and
     erased the whole card. The poster's own max-width still centres it. */
  .show--centre .show-vis{ order:0; width:100%; }
}

/* ── 10. PRICING ───────────────────────────────────────────────────────── */

#pricing{ padding-block:62px; }
#pricing h2{ margin-top:10px; max-width:13ch; }
/* No `#pricing .lede` rule — the section's lede was deleted, and a selector
   that matches nothing is the thing that rots first. */

.seg{
  margin-top:26px; display:inline-flex; padding:4px; gap:2px;
  border-radius:var(--r-pill); background:var(--surface);
  border:1px solid var(--line);
}
.seg button{
  position:relative; height:38px; padding:0 16px; border-radius:var(--r-pill);
  font-size:13.5px; font-weight:800; letter-spacing:-.01em; color:var(--muted);
  transition:background var(--dur-micro), color var(--dur-micro);
}
/* 38px visual, 44px hit. The two billing radios were the only interactive
   controls on the page without hit expansion — every other sub-44px control
   already carries this pad. Vertical only (`inset:-3px 0`), so the two
   buttons cannot overlap each other and .seg stays exactly 48px tall. */
.seg button::after{ content:""; position:absolute; inset:-3px 0; }
.seg button[aria-checked="true"]{ background:var(--raised); color:var(--ink); box-shadow:var(--shadow); }
.save-chip{ font-weight:800; color:var(--accent); }

.plans{ margin-top:26px; display:grid; gap:18px; }
@media (min-width:820px){ .plans{ grid-template-columns:1fr 1fr; gap:22px; align-items:start; } }

.plan{
  position:relative; overflow:hidden;
  background:var(--raised); border:1px solid var(--line);
  border-radius:var(--r-2xl); box-shadow:var(--shadow); padding:22px;
}
.plan::before{
  content:""; position:absolute; inset:0 0 auto 0; height:1px;
  background:var(--highlight); pointer-events:none;
}
.plan--pro{ border-color:color-mix(in srgb, var(--accent) 40%, transparent); box-shadow:var(--shadow-pop); }
.plan-head{ display:flex; align-items:center; justify-content:space-between; gap:12px; }
/* 10.5px, not 9.5: uppercase + 900 + wide tracking survives small sizes, but
   9.5px was the smallest type on the page and the only shell rule under 10. */
.badge{
  padding:4px 9px; border-radius:var(--r-pill); background:var(--accent-wash); color:var(--accent);
  font-size:10.5px; font-weight:900; letter-spacing:.12em; text-transform:uppercase;
}
.plan h3{ font-size:11.5px; font-weight:900; letter-spacing:.15em; text-transform:uppercase; color:var(--accent); }
.plan--free h3{ color:var(--muted); }
.plan-price{ margin-top:14px; display:flex; align-items:baseline; gap:7px; flex-wrap:wrap; }
.pp-num{ font-size:clamp(2.25rem,7vw,2.875rem); font-weight:800; letter-spacing:-.04em; line-height:1; font-variant-numeric:tabular-nums; }
.pp-per{ font-size:14px; font-weight:700; color:var(--muted); }
.plan-alt{ margin-top:8px; font-size:13.5px; font-weight:600; color:var(--muted); }
.plan-trial{ margin-top:6px; font-size:13.5px; font-weight:700; color:var(--accent); }
.swap{ animation:swap 260ms var(--ease-out); }
@keyframes swap{ from{ opacity:0; transform:translateY(-4px); } to{ opacity:1; transform:none; } }

.divider{ height:1px; background:var(--line); border:0; margin:18px 0; }
.plan-list{ display:grid; gap:11px; }
.plan-list li{ display:flex; gap:10px; font-size:14px; font-weight:600; line-height:1.45; color:var(--subtle); }
.plan-list svg{ margin-top:3px; color:var(--accent); flex:none; }
.plan-list .off{ color:var(--muted); }
.plan-list .off svg{ color:var(--faint); }
.plan-list .off s{ text-decoration-color:var(--faint); }
.plan-list .off em{ display:block; font-style:normal; font-weight:700; color:var(--ink); }
.plan .btn{ margin-top:20px; min-height:52px; width:100%; font-size:15px; }
.plan--free .btn{ border:1px solid var(--line-strong); }
.plan--pro .btn{ background:var(--ink); color:var(--bg); }
.plan-foot{ margin-top:12px; font-size:12px; font-weight:600; color:var(--muted); }

/* app.js's renderLimits() writes `<span class="lock">Locked</span>` into any
   [data-limit] cell whose cap is 0. The comparison table that owned those
   cells is gone from this design, so the branch is currently unreachable —
   but the renderer is deliberately kept (app.js:274-277) and an unstyled
   "Locked" is exactly the rot that would ship if the table came back. */
.lock{
  display:inline-flex; align-items:center; padding:2px 8px; border-radius:var(--r-pill);
  background:var(--surface); color:var(--muted);
  font-size:11.5px; font-weight:800; letter-spacing:.04em;
}

@media (min-width:900px){ #pricing{ padding-block:112px; } }

/* ── 11. CLOSER ────────────────────────────────────────────────────────── */

#close{ padding-block:76px 68px; text-align:center; }
#close .inner{ max-width:600px; }
/* a neutral well, not an accent glow: the app icon is BLUE, and ringing it in
   violet put two unrelated hues 8px apart */
.close-mark{
  width:88px; height:88px; border-radius:50%; margin:0 auto;
  display:grid; place-items:center;
  background:rgba(255,255,255,.06); box-shadow:inset 0 0 0 1px var(--line);
}
.close-mark img{ width:60px; height:60px; border-radius:15px; }
#close h2{ margin-top:26px; max-width:15ch; margin-inline:auto; }
.close-ctas{ margin-top:28px; display:flex; flex-direction:column; align-items:center; gap:14px; }
.close-ctas .btn-lg{ background:#FFFFFF; color:#0B0D12; }
.close-ctas .tlink{ color:rgba(255,255,255,.72); }
.close-ctas .tlink:hover{ color:#FFF; }
.close-note{ margin:16px auto 0; font-size:13px; font-weight:600; color:rgba(255,255,255,.55); }
.store-badges{ display:flex; gap:12px; justify-content:center; margin-top:18px; flex-wrap:wrap; }
@media (min-width:620px){
  .close-ctas{ flex-direction:row; justify-content:center; gap:22px; }
  .close-ctas .btn-lg{ width:auto; }
}
@media (min-width:900px){ #close{ padding-block:132px; } }

/* ── 12. FOOTER ────────────────────────────────────────────────────────── */

.footer{
  border-top:1px solid var(--line); background:var(--bg);
  padding:44px var(--gutter);
  /* the dock must never cover the legal links */
  padding-bottom:calc(44px + var(--dock-h) + env(safe-area-inset-bottom,0px));
}
.footer-inner{ max-width:var(--maxw); margin-inline:auto; }
/* the only link on the page without hit expansion — it measured 71×24 */
.footer-brand{ position:relative; display:inline-flex; align-items:center; gap:8px; font-size:15px; font-weight:800; letter-spacing:-.02em; }
.footer-brand::after{ content:""; position:absolute; inset:-11px -8px; }
.footer-brand img{ width:22px; height:22px; border-radius:6px; }
.footer-links{ display:flex; flex-wrap:wrap; gap:16px; margin-top:16px; }
.footer-links a{ position:relative; font-size:14px; font-weight:600; color:var(--subtle); }
.footer-links a::after{ content:""; position:absolute; inset:-14px -8px; }
.footer-links a:hover{ color:var(--ink); }
.footer-legal{ margin-top:22px; display:grid; gap:10px; }
.footer-legal p{ font-size:12.5px; font-weight:500; line-height:1.55; color:var(--muted); max-width:52ch; }

@media (min-width:900px){
  .footer-top{ display:flex; align-items:center; justify-content:space-between; gap:24px; }
  .footer-top .footer-links{ margin-top:0; }
}

/* ── 13. THE DOCK ──────────────────────────────────────────────────────── */
/* `visibility` is load-bearing, not decoration. With transform alone the dock
   stayed in the tab order and the accessibility tree while parked off-screen,
   so every page ended with focus landing on an invisible "Start →" link. The
   0s delay on the way out lets the slide finish first; app.js mirrors this
   with `inert`. */
.dock{
  position:fixed; inset-inline:12px; bottom:0; z-index:700;
  background:var(--raised); border:1px solid var(--line); border-radius:20px;
  box-shadow:0 5px 14px rgba(0,0,0,.10);
  padding:10px 12px; margin-bottom:max(12px, env(safe-area-inset-bottom,0px));
  display:flex; align-items:center; gap:10px;
  transform:translateY(130%); visibility:hidden;
  transition:transform 280ms var(--ease-out), visibility 0s linear 280ms;
}
.dock.is-up{ transform:none; visibility:visible; transition-delay:0s; }
.dock > img{ width:24px; height:24px; border-radius:7px; flex:none; }
.dock-copy{ min-width:0; flex:1; }
.dock-copy strong{ display:block; font-size:14px; font-weight:800; letter-spacing:-.01em; }
.dock-copy span{ display:block; font-size:11px; font-weight:700; color:var(--muted); }
.dock .btn-sm{ min-width:88px; }
.dock-get{
  display:none; width:36px; height:36px; border-radius:999px; flex:none;
  border:1px solid var(--line-strong); color:var(--ink);
  align-items:center; justify-content:center;
}
:root[data-store="live"] .dock-get{ display:inline-flex; }
/* 32px visual, 44px hit area. It is a persistent control 6px from an 88px CTA
   on a bar that appears under the thumb — mis-taps dismissed the dock. */
.dock-close{
  position:relative;
  width:32px; height:32px; border-radius:999px; flex:none; color:var(--muted);
  display:grid; place-items:center; font-size:18px; line-height:1;
}
.dock-close::after{ content:""; position:absolute; inset:-6px; }
.dock-close:hover{ color:var(--ink); background:var(--surface); }

/* Small phones. At 320px, after the icon, the 88px CTA and the close button,
   .dock-copy had ~96px for a ~120px string. Drop chrome, don't wrap. */
@media (max-width:399px){ .dock-get{ display:none !important; } }
@media (max-width:359px){
  .dock{ gap:8px; padding:9px 10px; }
  .dock > img{ display:none; }
  .dock-copy span{ display:none; }
  .dock-copy strong{ font-size:13px; }
  .dock .btn-sm{ min-width:0; padding:0 12px; }
}
@media (min-width:900px){ .dock{ inset-inline:auto 20px; width:400px; } }

/* ── 14. STORE-READY LABEL SWAP ────────────────────────────────────────── */
/* Both labels ship in the DOM; CSS picks one, so the swap survives JS being
   off. <html data-store="soon"> is the authored default — the inline head
   script only ever upgrades it to "live" once config.js has store URLs. */
[data-get-label]{ display:none; }
:root[data-store="soon"] [data-get-label="soon"]{ display:inline; }
:root[data-store="live"] [data-get-label="live"]{ display:inline; }
:root:not([data-store="live"]) .store-badges{ display:none; }

/* ── 15. REDUCED MOTION ────────────────────────────────────────────────── */

@media (prefers-reduced-motion:reduce){
  *,*::before,*::after{
    animation-duration:.01ms !important; animation-iteration-count:1 !important;
    transition-duration:.01ms !important; scroll-behavior:auto !important;
  }
  html{ scroll-behavior:auto; }
  /* the full fallback: no start state at all, so nothing ever moves in */
  :root.js-reveal .reveal{ opacity:1 !important; transform:none !important; transition:none !important; }
}

/* ==========================================================================
   16. SHARED — DO NOT DELETE
   privacy.html, terms.html, p.html, s.html and twin.html all <link> this
   stylesheet and are byte-frozen. They reference the token names and the
   handful of classes below. Several of those tokens were never defined in
   the tokens block (so those pages shipped with invalid var() lookups); the
   aliases fix that without touching a single frozen file.
   ========================================================================== */

:root{
  --paper:var(--bg);
  --card:var(--raised);
  --text:var(--ink);
  --sub:var(--subtle);
  --mut:var(--muted);
  --hairline:var(--line);
  --app-border:var(--line);
  --accent-text:var(--accent-ink);
  --accent-wash-13:var(--accent-wash);
  --shadow-soft:var(--shadow);
  --ease:var(--ease-out);
  --vio:var(--accent);
}

.container{ max-width:var(--maxw); margin-inline:auto; padding-inline:var(--gutter); }

.legal-nav{ border-bottom:1px solid var(--line); background:var(--bg); }
.nav-inner{
  max-width:var(--maxw); margin-inline:auto; padding:14px var(--gutter);
  display:flex; align-items:center; justify-content:space-between; gap:16px;
}
.back-link{ display:inline-flex; align-items:center; gap:6px; font-size:15px; font-weight:800; letter-spacing:-.02em; }
.back-link svg{ width:18px; height:18px; stroke:currentColor; fill:none; }
.other-link{ font-size:13.5px; font-weight:700; color:var(--subtle); }
.other-link:hover{ color:var(--ink); }

/* ══ THE READING LAYOUT ═══════════════════════════════════════════════════
   A privacy policy is forty screens of numbered sections, and the only two
   things a reader ever wants are "where is the bit about X" and "how much is
   left". So: a contents list that follows you down the page on a wide screen,
   a jump menu on a narrow one, and a bar at the top that fills as you read.

   ⚠️ THE SECTIONS ARE GENERATED. `npm run legal` rewrites everything between
   the two marker comments from lib/i18n/locales/en.ts, so nothing here may
   depend on a hand-written id or a fixed section count — legal.js reads the
   headings that are actually on the page and builds the list from those. */
.legal-progress{
  position:fixed; left:0; top:0; height:2px; width:0; z-index:60;
  background:var(--accent); transition:width .1s linear;
}
.legal-shell{
  max-width:1120px; margin-inline:auto; padding:40px var(--gutter) 72px;
  display:grid; gap:clamp(28px,4vw,56px);
  grid-template-columns:minmax(0,1fr);
}
@media (min-width:1000px){
  .legal-shell{ grid-template-columns:250px minmax(0,1fr); align-items:start; }
}
.legal-toc{ display:none; }
@media (min-width:1000px){
  .legal-toc{
    display:block; position:sticky; top:24px;
    max-height:calc(100vh - 48px); overflow:auto; padding-right:8px;
  }
  .legal-toc::-webkit-scrollbar{ width:0; }
}
.legal-toc .eyebrow{
  display:block; font-size:11px; font-weight:900; letter-spacing:.12em;
  text-transform:uppercase; color:var(--muted); margin-bottom:10px;
}
.legal-toc ol{ list-style:none; display:flex; flex-direction:column; gap:1px; }
.legal-toc a{
  display:block; padding:7px 10px; border-radius:9px; border-left:2px solid transparent;
  font-size:13px; line-height:1.35; font-weight:600; color:var(--muted);
}
.legal-toc a:hover{ color:var(--ink); background:var(--surface); }
.legal-toc a[aria-current="true"]{
  color:var(--accent); border-left-color:var(--accent);
  background:var(--accent-wash); font-weight:750;
}
/* The narrow-screen equivalent: a real <details>, closed, costing one line. */
.legal-jump{ margin-top:22px; border:1px solid var(--line); border-radius:14px; background:var(--raised); }
@media (min-width:1000px){ .legal-jump{ display:none; } }
.legal-jump summary{
  cursor:pointer; list-style:none; padding:13px 16px; min-height:48px;
  display:flex; align-items:center; gap:8px;
  font-size:14px; font-weight:800; color:var(--ink);
}
.legal-jump summary::-webkit-details-marker{ display:none; }
.legal-jump summary::after{ content:"⌄"; margin-left:auto; color:var(--muted); font-size:16px; }
.legal-jump[open] summary::after{ content:"⌃"; }
.legal-jump ol{ list-style:none; padding:0 8px 10px; }
.legal-jump a{
  display:block; padding:11px 10px; min-height:44px; border-radius:9px;
  font-size:14px; color:var(--subtle); border-top:1px solid var(--line);
}
.legal-jump a:hover{ background:var(--surface); color:var(--ink); }

.legal-wrap{ max-width:760px; min-width:0; }
/* Anchored headings land under the progress bar, not behind it. */
.legal-section{ scroll-margin-top:24px; }
/* A quiet, permanent way back to the top — a forty-screen document without
   one is a forty-screen scroll. */
.legal-top{
  position:fixed; right:18px; bottom:18px; z-index:60;
  width:44px; height:44px; border-radius:50%;
  display:grid; place-items:center;
  background:var(--raised); border:1px solid var(--line-strong); color:var(--ink);
  box-shadow:var(--shadow-pop); opacity:0; pointer-events:none;
  transition:opacity .2s ease;
}
.legal-top.is-on{ opacity:1; pointer-events:auto; }
@media (prefers-reduced-motion:reduce){ .legal-progress,.legal-top{ transition:none; } }

.legal-wrap h1{ font-size:clamp(1.75rem,5vw,2.5rem); }
.legal-stamp{ margin-top:10px; font-size:13px; font-weight:700; color:var(--muted); }
.legal-note{ margin-top:6px; font-size:13.5px; color:var(--subtle); }
/* Language switcher on the legal pages. Quiet by design — it is a way out for
   the reader who needs it, not a decision the page is asking everyone to make. */
.legal-langs{ margin-top:10px; font-size:12.5px; font-weight:600; color:var(--muted); }
.legal-langs a{ color:var(--muted); text-decoration:underline; text-underline-offset:2px; }
.legal-langs a:hover{ color:var(--text); }
.legal-langs .lang-current{ color:var(--text); font-weight:800; }
.legal-section{ margin-top:36px; }
.legal-section h2{ font-size:clamp(1.125rem,2.6vw,1.375rem); letter-spacing:-.02em; }
.legal-section h3{ margin-top:20px; font-size:1rem; }
.legal-section p,
.legal-section ul,
.legal-section ol{ margin-top:12px; }
.legal-section li{ margin-top:8px; margin-left:20px; list-style:disc; font-size:15px; line-height:1.6; color:var(--subtle); }
.legal-section a,
.legal-wrap a{ color:var(--accent); border-bottom:1px solid var(--accent-wash); }
.legal-website-note{ font-size:13px; color:var(--muted); }
.scope-tag{
  display:inline-block; margin-right:8px; padding:3px 8px; border-radius:999px;
  font-size:10px; font-weight:900; letter-spacing:.1em; text-transform:uppercase;
  background:var(--accent-wash); color:var(--accent);
}
.footer-bottom{ max-width:var(--maxw); margin-inline:auto; display:flex; flex-wrap:wrap; gap:12px 20px; align-items:center; }
.footer-credit{ font-size:12.5px; color:var(--muted); }
/* legal pages have no dock, so they don't need the dock clearance */
.legal-nav ~ .footer{ padding-bottom:44px; }

/* twin.html button + badge variants */
/* ⚠️ NEAR-BLACK, MATCHING THE HOMEPAGE. When the pressable control was the
   accent it competed with every violet number on the page for the same
   glance, and the two designs no longer looked like one site. */
.btn-primary{ background:var(--ink); color:var(--bg); padding:13px 22px; font-size:15px; }
.btn-primary:hover{ opacity:.88; }
.btn-ghost{ background:var(--surface); color:var(--ink); border:1px solid var(--line); padding:13px 22px; font-size:15px; }

.store-badge{
  display:inline-flex; align-items:center; gap:9px; min-height:44px;
  padding:9px 16px; border-radius:12px; background:var(--ink); color:var(--raised);
}
.store-badge svg{ width:22px; height:22px; flex:none; }
.sb-text{ display:flex; flex-direction:column; line-height:1.1; text-align:left; }
.sb-top{ font-size:9px; font-weight:700; letter-spacing:.04em; opacity:.8; }
.sb-bottom{ font-size:14px; font-weight:800; letter-spacing:-.01em; }

.ssc-head{ display:flex; align-items:center; justify-content:space-between; gap:12px; margin-bottom:12px; }
.ssc-eyebrow{ font-size:10.5px; font-weight:900; letter-spacing:.13em; text-transform:uppercase; color:var(--muted); }
.ssc-live{ display:inline-flex; align-items:center; gap:6px; font-size:10.5px; font-weight:900; letter-spacing:.08em; color:var(--accent); }
.live-dot{ width:7px; height:7px; border-radius:50%; background:var(--accent); flex:none; }
.scan-caption{ margin-top:12px; font-size:12.5px; color:var(--muted); text-align:center; }
