/* =========================================================================
   Health Path — website
   -------------------------------------------------------------------------
   Design system is inherited from the Flutter app, not re-invented here:

     Font        Inter (app/lib/core/theme/app_typography.dart — every style
                 in the app is GoogleFonts.inter). The project brief asks for
                 Nunito; that is wrong against the shipped app, so Inter wins.
                 Zen Maru Gothic appears exactly once, on the mission quote —
                 mirroring the app, where it is used for the Activities quote
                 card and nowhere else.

     Colour      app/lib/core/theme/app_colors.dart. Note the brief's primary
                 (#5B7FDE) is also wrong; the real one is #7095DF.

   Only --page is a website-only value: the app is pure white, but the
   reference layout needs panels to float on a slightly warmer ground.
   ========================================================================= */

/* =========================================================================
   Fonts — SELF-HOSTED since 2026-08-15. Do not put the Google <link> back.
   -------------------------------------------------------------------------
   Every page used to hot-link fonts.googleapis.com, which sends the visitor's
   IP address to Google before they have read a word or agreed to anything —
   including on the privacy and terms pages, where a policy promising no
   third-party data flow was itself making one. LG München I, 20 Jan 2022
   (3 O 17493/20) is the only court decision on this exact fact pattern, and
   the compliance memo's row H6 asks for exactly this fix.

   Self-hosting is a COMPLETE fix, not a mitigation: no request ever leaves
   for a Google-controlled host, so there is no IP transmission to disclose,
   consent to, or justify. The files are downloaded from Google Fonts (SIL
   Open Font License 1.1, both families — see CREDITS.md) and served from
   assets/fonts/.

   Inter is a VARIABLE font: one file per subset covers every weight, which is
   why the 400/500/600 URLs Google serves are byte-identical and why these
   rules say `font-weight: 100 900` instead of shipping three copies of the
   same 48KB. Subsets kept: latin, latin-ext, and cyrillic — the last is not
   optional, privacy-bg.html and terms-bg.html are entirely Cyrillic (and the
   № they both use is U+2116, inside that range). Greek, Vietnamese and
   cyrillic-ext are dropped; nothing on the site uses them.

   Zen Maru Gothic ships LATIN ONLY. Google serves it in ~120 Japanese chunks
   and the site uses it for exactly one English quote; the Japanese would be
   ~2MB of font for glyphs no page contains.

   unicode-range is copied verbatim from Google's own CSS — it is what makes
   the browser fetch only the subset a page actually needs.
   ========================================================================= */

@font-face {
  font-family: "Inter";
  font-style: normal;
  font-weight: 100 900;
  font-display: swap;
  src: url(assets/fonts/Inter-cyrillic.woff2) format("woff2");
  unicode-range: U+0301, U+0400-045F, U+0490-0491, U+04B0-04B1, U+2116;
}
@font-face {
  font-family: "Inter";
  font-style: normal;
  font-weight: 100 900;
  font-display: swap;
  src: url(assets/fonts/Inter-latin-ext.woff2) format("woff2");
  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;
}
@font-face {
  font-family: "Inter";
  font-style: normal;
  font-weight: 100 900;
  font-display: swap;
  src: url(assets/fonts/Inter-latin.woff2) format("woff2");
  unicode-range: U+0000-00FF, U+0131, U+0152-0153, U+02BB-02BC, U+02C6, U+02DA, U+02DC, U+0304, U+0308, U+0329, U+2000-206F, U+20AC, U+2122, U+2191, U+2193, U+2212, U+2215, U+FEFF, U+FFFD;
}
@font-face {
  font-family: "Zen Maru Gothic";
  font-style: normal;
  font-weight: 400;
  font-display: swap;
  src: url(assets/fonts/ZenMaruGothic-latin-ext.woff2) format("woff2");
  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;
}
@font-face {
  font-family: "Zen Maru Gothic";
  font-style: normal;
  font-weight: 400;
  font-display: swap;
  src: url(assets/fonts/ZenMaruGothic-latin.woff2) format("woff2");
  unicode-range: U+0000-00FF, U+0131, U+0152-0153, U+02BB-02BC, U+02C6, U+02DA, U+02DC, U+0304, U+0308, U+0329, U+2000-206F, U+20AC, U+2122, U+2191, U+2193, U+2212, U+2215, U+FEFF, U+FFFD;
}

:root {
  /* --- Brand ----------------------------------------------------------- */
  --primary: #7095df;          /* AppColors.primary */
  --primary-deep: #4a70ca;     /* AppColors.primaryAccessible — AA on white */
  --sky: #60b0e2;              /* AppColors.accentSky */
  --sky-soft: #97c8ed;         /* AppColors.cardSelected / journalPill */
  --periwinkle: #98b5e5;       /* AppColors.carouselCardA */
  --lilac: #ada6ed;            /* AppColors.foodProtein */
  --lilac-deep: #b9a5e5;       /* mascot / activities illustration accent */
  --mint: #71d3ce;             /* AppColors.foodHealthyFats */
  --leaf: #a9ddb8;             /* AppColors.foodFruitsVegetables */

  /* --- Surfaces -------------------------------------------------------- */
  --page: #fafaf8;             /* website-only: warm ground for the panels */
  --surface: #ffffff;
  --surface-muted: #f5f5f5;    /* AppColors.subScreenCardGrey */
  --divider: #efefef;          /* AppColors.divider */
  --hairline: rgba(0, 0, 0, 0.08);

  /* --- Ink ------------------------------------------------------------- */
  --ink: #000000;              /* AppColors.textPrimary */
  --ink-soft: #3a3a3a;
  --ink-muted: #6b6b6b;        /* AppColors.textSecondary — 5.3:1 on white */

  /* --- Elevation (app uses 0 4px 6px rgba(0,0,0,.25) on cards) ---------- */
  --lift-sm: 0 2px 6px rgba(16, 24, 40, 0.04), 0 1px 2px rgba(16, 24, 40, 0.04);
  --lift-md: 0 12px 28px rgba(16, 24, 40, 0.07), 0 2px 6px rgba(16, 24, 40, 0.04);
  --lift-lg: 0 32px 64px rgba(70, 90, 160, 0.10), 0 4px 12px rgba(16, 24, 40, 0.04);

  /* --- Radii: oval-leaning, per the "natural forms" direction ----------- */
  --r-sm: 14px;
  --r-md: 22px;
  --r-lg: 28px;
  --r-xl: 40px;
  --r-pill: 999px;

  /* --- Rhythm (4/8 scale) ---------------------------------------------- */
  --gutter: clamp(20px, 5vw, 48px);
  --section-y: clamp(72px, 8vw, 116px);
  --measure: 62ch;

  /* --- Motion ---------------------------------------------------------- */
  --ease-out: cubic-bezier(0.22, 0.61, 0.36, 1);
  --ease-in-out: cubic-bezier(0.65, 0, 0.35, 1);
  --dur: 240ms;
}

/* =========================================================================
   Reset / base
   ========================================================================= */

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

html { -webkit-text-size-adjust: 100%; scroll-behavior: smooth; }

body {
  margin: 0;
  /* Sticky-footer column: now that the footer is full-bleed white rather than
     a card floating on --page, a short page (Terms) on a tall viewport would
     otherwise leave a strip of --page showing beneath it. */
  min-height: 100dvh;
  display: flex;
  flex-direction: column;
  background: var(--page);
  color: var(--ink);
  font-family: "Inter", system-ui, -apple-system, "Segoe UI", sans-serif;
  font-size: 1rem;
  line-height: 1.7;
  font-feature-settings: "cv11", "ss01";
  -webkit-font-smoothing: antialiased;
  text-rendering: optimizeLegibility;
}

img, svg { display: block; max-width: 100%; }
a { color: inherit; }

h1, h2, h3 {
  margin: 0;
  font-weight: 600;
  line-height: 1.12;
  letter-spacing: -0.028em;
  text-wrap: balance;
}

p { margin: 0; }

/* Visible focus everywhere — never removed (Quick Reference §1). */
:focus-visible {
  outline: 3px solid var(--primary-deep);
  outline-offset: 3px;
  border-radius: 6px;
}

.skip-link {
  position: absolute;
  left: 50%;
  top: 12px;
  translate: -50% -160%;
  z-index: 200;
  padding: 12px 22px;
  border-radius: var(--r-pill);
  background: var(--surface);
  box-shadow: var(--lift-md);
  font-size: 0.9375rem;
  font-weight: 500;
  text-decoration: none;
  transition: translate var(--dur) var(--ease-out);
}
.skip-link:focus-visible { translate: -50% 0; }

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

.shell {
  width: 100%;
  max-width: 1160px;
  margin-inline: auto;
  padding-inline: var(--gutter);
}

/* =========================================================================
   Scroll reveal — blocks arrive as they come into view

   Owner, 2026-08-08: "make animation on the whole page so the following
   information appear slowly and not instant as you scroll down."

   Three things hold this together and none of them work alone:
     · `data-reveal` in the markup says WHICH blocks arrive. It is on section
       heads, the six capsules, the four widgets, the five article cards, the
       download panel, each FAQ row and the three footer columns — never on a
       whole section, so a section's heading can land before its cards.
     · `.reveal` on <html>, set by an inline script in <head>, says the page is
       allowed to hide them at all. Without it — no JS, an older browser,
       reduced motion — every rule below is inert and the page is simply whole.
       It has to be inline and in the head, before first paint.
     · app.js adds `.is-revealed` per block as it enters the viewport.

   `translate`, not `transform`: several of these blocks already own their
   transform (`.widget:hover` lifts 2px, `.article__link:hover` the same), and
   a reveal rule with a class in it outranks those selectors — the cards would
   have arrived and then never lifted again. The separate property composes
   instead of fighting.

   The transition lives on the REVEALED state only, so a block eases in and
   nothing eases out; blocks are revealed once and then left alone.
   ========================================================================= */

.reveal [data-reveal] {
  opacity: 0;
  translate: 0 22px;
}

.reveal [data-reveal].is-revealed {
  opacity: 1;
  translate: none;
  /* 700ms, well past the 150-300ms of a micro-interaction, because this is
     not feedback for anything the reader did — it is the page arriving, and
     the owner asked for slowly. --ease-out is the site's own curve. */
  transition:
    opacity 700ms var(--ease-out) var(--reveal-delay, 0ms),
    translate 700ms var(--ease-out) var(--reveal-delay, 0ms);
}

/* Belt and braces. The inline script already declines to add `.reveal` under
   reduced motion, but if the preference is switched on with the page open,
   this is what makes everything currently hidden appear at once rather than
   waiting for a scroll that may never come. */
@media (prefers-reduced-motion: reduce) {
  .reveal [data-reveal] { opacity: 1; translate: none; transition: none; }
}

/* =========================================================================
   Type helpers
   ========================================================================= */

.eyebrow {
  font-size: 0.8125rem;
  font-weight: 500;
  letter-spacing: 0.14em;
  text-transform: uppercase;
  color: var(--primary-deep);
}

.display {
  font-size: clamp(2.3rem, 5.2vw, 3.7rem);
  line-height: 1.08;
  letter-spacing: -0.036em;
}

.h2 { font-size: clamp(1.9rem, 3.8vw, 2.85rem); }
.h3 { font-size: clamp(1.15rem, 1.6vw, 1.3rem); letter-spacing: -0.018em; }

.lead {
  font-size: clamp(1.0625rem, 1.35vw, 1.1875rem);
  line-height: 1.65;
  color: var(--ink-muted);
  max-width: var(--measure);
}

.muted { color: var(--ink-muted); }

/* =========================================================================
   Buttons
   ========================================================================= */

.btn {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  gap: 8px;
  min-height: 48px;              /* ≥44px touch target */
  padding: 13px 26px;
  border: 1px solid transparent;
  border-radius: var(--r-pill);
  font: inherit;
  font-size: 0.9688rem;
  font-weight: 500;
  letter-spacing: -0.006em;
  text-decoration: none;
  cursor: pointer;
  white-space: nowrap;
  transition: transform var(--dur) var(--ease-out),
              box-shadow var(--dur) var(--ease-out),
              background-color var(--dur) var(--ease-out),
              color var(--dur) var(--ease-out),
              border-color var(--dur) var(--ease-out);
}

.btn--primary {
  background: var(--primary-deep);
  color: #fff;
  box-shadow: 0 6px 16px rgba(74, 112, 202, 0.28);
}
.btn--primary:hover { background: #3f62b8; box-shadow: 0 10px 22px rgba(74, 112, 202, 0.32); }

.btn--ghost {
  background: var(--surface);
  color: var(--ink);
  border-color: var(--hairline);
  box-shadow: var(--lift-sm);
}
.btn--ghost:hover { border-color: rgba(0, 0, 0, 0.16); box-shadow: var(--lift-md); }

.btn:hover { transform: translateY(-1px); }
.btn:active { transform: translateY(0); }

.btn[aria-disabled="true"] {
  cursor: default;
  opacity: 0.55;
  box-shadow: none;
}
.btn[aria-disabled="true"]:hover { transform: none; }

.btn-row {
  display: flex;
  flex-wrap: wrap;
  gap: 12px;
  align-items: center;
}
.btn-row--center { justify-content: center; }

/* Quiet text link with a sliding chevron — the "see more" affordance. */
.link-more {
  display: inline-flex;
  align-items: center;
  gap: 7px;
  min-height: 44px;
  font-size: 0.9375rem;
  font-weight: 500;
  color: var(--primary-deep);
  text-decoration: none;
  background: none;
  border: 0;
  padding: 0;
  cursor: pointer;
  font-family: inherit;
}
.link-more svg { transition: transform var(--dur) var(--ease-out); }
.link-more:hover svg { transform: translateX(3px); }
.link-more[aria-expanded="true"] svg { transform: rotate(90deg); }

/* =========================================================================
   Navigation — floating glass pill
   ========================================================================= */

.nav {
  position: fixed;
  inset: 14px var(--gutter) auto;
  z-index: 100;
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 20px;
  max-width: 1160px;
  margin-inline: auto;
  padding: 10px 12px 10px 22px;
  border: 1px solid rgba(255, 255, 255, 0.6);
  border-radius: var(--r-pill);
  background: rgba(255, 255, 255, 0.72);
  backdrop-filter: saturate(180%) blur(18px);
  -webkit-backdrop-filter: saturate(180%) blur(18px);
  box-shadow: var(--lift-sm);
}

.nav__logo {
  position: relative;
  display: flex;
  align-items: center;
  min-height: 44px;
}
.nav__logo img { width: 118px; }

/* Two wordmarks stacked in the same box, cross-faded rather than swapped, so
   the transition over the hero has no flicker. The white one is absolutely
   positioned so it does not add to the layout height. */
.nav__logo-light {
  position: absolute;
  inset: 0 auto 0 0;
  margin: auto;
  opacity: 0;
  transition: opacity var(--dur) var(--ease-out);
}
.nav__logo-dark { transition: opacity var(--dur) var(--ease-out); }

/* ---- Transparent state, while the hero photo is behind the bar ---------- */

.nav--over-hero {
  border-color: transparent;
  background: transparent;
  backdrop-filter: none;
  -webkit-backdrop-filter: none;
  box-shadow: none;
}
.nav--over-hero .nav__logo-light { opacity: 1; }
.nav--over-hero .nav__logo-dark { opacity: 0; }

.nav--over-hero .nav__links a { color: rgba(255, 255, 255, 0.88); }
.nav--over-hero .nav__links a:hover {
  color: #fff;
  background: rgba(255, 255, 255, 0.16);
}

/* Outlined rather than filled here — a solid blue pill on a photograph reads
   as pasted on, and Calm's reference does the same thing. */
.nav--over-hero .btn--primary {
  background: rgba(255, 255, 255, 0.12);
  border-color: rgba(255, 255, 255, 0.7);
  color: #fff;
  backdrop-filter: blur(10px);
  -webkit-backdrop-filter: blur(10px);
  box-shadow: none;
}
.nav--over-hero .btn--primary:hover {
  background: rgba(255, 255, 255, 0.24);
  border-color: #fff;
}

/* The bar animates between the two states, so scrolling past the hero settles
   into the glass pill instead of snapping. */
.nav {
  transition: background-color var(--dur) var(--ease-out),
              border-color var(--dur) var(--ease-out),
              box-shadow var(--dur) var(--ease-out);
}

.nav__links {
  display: flex;
  gap: 4px;
}
.nav__links a {
  display: inline-flex;
  align-items: center;
  min-height: 44px;
  padding: 0 14px;
  border-radius: var(--r-pill);
  font-size: 0.9375rem;
  color: var(--ink-muted);
  text-decoration: none;
  transition: color var(--dur) var(--ease-out), background-color var(--dur) var(--ease-out);
}
.nav__links a:hover { color: var(--ink); background: rgba(0, 0, 0, 0.04); }

.nav .btn { min-height: 44px; padding: 11px 20px; font-size: 0.9375rem; }

/* Was 860px, raised to 1000px when the bar went from four links to five
   (2026-08-01). The old value was tuned to four short labels; with "What is
   Health Path" and "What we help with" in the row, 860-1000px is a band where
   the labels WRAP TO TWO LINES rather than overflow — the pill grows taller and
   the whole bar reads as broken. Verified at 900px before the change.
   Whatever the labels become, this number is a consequence of their total
   width: re-measure it if the bar is ever re-cut, and do not assume the count
   of links is what matters. */
@media (max-width: 1000px) {
  .nav__links { display: none; }
}
/* Belt and braces: if a future label is long enough to wrap ABOVE 1000px, this
   makes it overflow visibly instead of silently doubling the bar's height —
   an overflow is noticed, a quiet extra line is not. */
.nav__links a { white-space: nowrap; }
@media (max-width: 420px) {
  .nav { padding-left: 16px; }
  .nav__logo img { width: 100px; }
}

/* =========================================================================
   Hero — one full-bleed illustration, copy set left over it

   Rebuilt 2026-08-09 (owner, who supplied the picture). What stood here
   before was the app's splash gradient with the animated person centred on
   it; the illustration has its own figure, so the person is parked at the
   foot of this block and the gradient with it. The COPY did not change.

   Still no scrims and no text-shadow, unlike the lake photograph this
   replaced two versions back: the picture is pale everywhere. Sampled off the
   encoded file across the band the copy occupies, its darkest tone is
   #B3CAF4 — black lands at 9.9:1 there and --ink-soft at 6.9:1, both clear of
   AA's 4.5. Re-measure if the picture is ever swapped; that is the whole
   reason this hero needs no treatment over it.
   ========================================================================= */

.hero {
  position: relative;
  display: flex;
  align-items: center;
  /* dvh, not vh: on mobile Safari/Chrome the URL bar makes 100vh taller than
     the visible area, which would push the button under the fold.

     100, not the 82 it was: the owner asked on 2026-08-10 for the picture to
     be BIGGER, and the picture is the background of the section, so the only
     way it gets bigger is for the section to. That ask outlived the picture
     it arrived with — hero-meadow.jpg came back out the same day and this
     stayed. The copy still clears the fold because it is centred in the box. */
  min-height: 100dvh;
  padding-block: 140px clamp(56px, 8vw, 96px);
  overflow: hidden;
  isolation: isolate;

  /* assets/hero-sunrise.jpg — the owner's illustration, 1672x941 (16:9),
     re-encoded from a 1.8MB PNG to a 221KB JPEG at q92. q92 and not lower
     because most of this frame is flat sky, which is exactly what banding
     shows up in: measured against the source, the flat left sky deviates by
     at most 6/255 and 1.26 on average, i.e. no visible step.

     BACK IN PLACE 2026-08-10 after a few hours as hero-meadow.jpg, which the
     owner asked to swap out again. That file stays in assets/ unreferenced,
     the same way this one did while it was out.

     `cover`, NOT the `100% 100%` the splash gradient wanted. That stretch was
     right for a smooth low-frequency ramp and is wrong for a drawing — a
     horizon and a figure skew the moment the aspect changes.

     center 58% — but read the note below before trusting it. At the 82dvh
     this section used to be, every desktop box was wider than 16:9, cover
     scaled to width, and 58% chose which strip of sky to give up. At 100dvh
     that is no longer true below about 1670px wide: the box goes TALLER than
     16:9, cover scales to height instead, and the crop turns horizontal, so
     the 58% stops doing anything and 160px comes off the sides at 1440.
     Re-measured after that change and the copy still clears AA — the numbers
     are on the no-scrim note below.

     The flat colour under it is the sky's own tone, covering the load gap. */
  background: #b7c6ef url("assets/hero-sunrise.jpg") center 58% / cover no-repeat;
}

/* ---- still no scrim and no text-shadow ----------------------------------
   The rule this hero has carried since it was built: re-measure on any swap,
   and the treatment is whatever the measurement asks for. It has now been
   measured three times.

   hero-meadow.jpg, briefly here on 2026-08-10, FAILED it — dark stems at
   #0B0A34 put 5% of the copy rect under 2.4:1 — and wore a white veil for as
   long as it was up. It is gone, and the veil went with it; do not restore
   one without a picture that needs it.

   This picture passes, and it still passes at the taller frame even though
   the taller frame re-cropped it. Sampled off the render with the copy hidden
   so no glyph contaminates the numbers — worst pixel in the band, not the
   average:

     1440x900   headline vs black  12.3:1    subline vs --ink-soft  6.8:1
     1280x800   headline vs black  12.3:1    subline vs --ink-soft  6.6:1
     390x844    headline vs black  10.6:1    subline vs --ink-soft  6.6:1

   All six clear AA's 4.5 with room to spare, and a shadow under dark type on
   a light ground only muddies it. */

/* ---- the copy column ----------------------------------------------------
   Both of these are still live and still load-bearing: --hero-x sets where
   the copy starts and --hero-copy-w how wide it runs. --hero-copy-w was ALSO
   what the parked stage padded by, which is why it reads like a shared
   measurement; with the person gone it now belongs to the copy alone. */
.hero {
  --hero-copy-w: 27rem;

  /* Where the hero's copy starts (owner, 2026-08-08: "move the text from the
     hero a little just a little to right", after it had been moved out to the
     page gutter earlier the same day).

     This is not a number picked to look right — it is the expression the nav
     bar's own left edge resolves to: a 1160px box with auto margins, floored
     at the gutter. So the headline and the wordmark above it sit on one
     vertical line at every width, and neither can drift from the other. At
     1440 that is x=140, against 48 at the gutter and 188 inside the shell.

     One step further right, if this still reads as too far left, is the
     wordmark's own edge rather than the pill's: add the bar's 22px padding. */
  --hero-x: max(var(--gutter), (100% - 1160px) / 2);
}

/* ---- PARKED 2026-08-09: the splash gradient and the person ---------------
   Not deleted. The markup is parked in index.html at the foot of the hero
   section and says how to restore all three pieces; this is the third.

   The background these went on, verbatim, for the same restore:
     background: #a7a6e5 url("assets/splash_gradient.png") center / 100% 100% no-repeat;
   STRETCHED, not `cover`: the source is 412x894 portrait and the box is
   landscape, so `cover` crops away the pink top and the blue bottom, which is
   the whole gradient. The app stretches it for the same reason
   (android:gravity="fill" in launch_background.xml) — it is a smooth
   low-frequency ramp, so it takes any aspect without showing it.

   The stage centred the person in the space LEFT OVER BY THE COPY, not in the
   section (owner, 2026-08-08: "move the face a little to the right so it's
   centred between the text and the end of the screen"). Its padding is the
   copy's own column, from the same --hero-copy-w the copy uses, so the two
   could not drift apart. At 1440 that put his middle at 960 against the
   section's own 720. Behind the copy in z-order and non-interactive, so
   nothing there could catch a click meant for the button.

.hero__stage {
  position: absolute;
  inset: 0;
  display: grid;
  place-items: center;
  padding-left: calc(var(--hero-x) + var(--hero-copy-w));
  z-index: 0;
  pointer-events: none;
}

.hero__person {
  vmin, not vw: the box is square, so it must not outgrow the viewport's
  SHORT side or a landscape phone crops the person's head and feet. The
  ceiling is 460px because the copy column ends around x=556 at 1440 and
  the ball's left edge should meet it, not swallow it.
  width: clamp(210px, 44vmin, 500px);
  aspect-ratio: 1;
  The lift the app gives the person on its two gradient screens
  (HealthPathMascot's `glow`), rebuilt as drop-shadows so they follow the
  circle instead of sitting behind a square. TWO of them, and both are
  needed: the ball's lower half is the same blue as the gradient behind it
  at that height, so with the white halo alone the bottom edge disappeared
  and the ball read as a rim rather than a body. The blur is deliberately
  much wider than the halo's own radius — a tight one draws a white outline.
  filter:
    drop-shadow(0 6px 64px rgba(255, 255, 255, 0.55))
    drop-shadow(0 22px 44px rgba(44, 62, 116, 0.16));
}
.hero__person canvas { display: block; width: 100%; height: 100%; }

No-JS fallback: the same ball at the same size, at the resting pose.
.hero__person-still { position: relative; display: block; width: 100%; height: 100%; }
.hero__person-ball {
  width: 78.01%;            the Figma component's circle inside its box
  height: 78.01%;
  margin: 10.64%;
  The PNG's corners are opaque WHITE, not alpha — Figma flattened the mask
  into the fill. Without this the orb sits on a visible white square.
  border-radius: 50%;
}
.hero__person-face {
  position: absolute;
  left: 49.65%;
  top: 35.46%;
  width: 28.36%;
  height: auto;
}

   The inner comments above had their delimiters stripped: CSS comments do not
   nest, so the first inner close would have ended this park early and let the
   rest through as live rules. Put them back when restoring. */

/* ---- the copy ------------------------------------------------------------ */

.hero__inner { position: relative; z-index: 1; width: 100%; }

/* The hero copy is the one block on the site that does not use the shell's
   own inset. It went out to the page gutter first (owner: "move the text more
   to left") and then part-way back (owner: "a little just a little to right"),
   and it now lands on --hero-x, which is the nav bar's left edge — see the
   note on that variable. The shell is still what centres it; only its padding
   is overridden, so the 1160px cap and the auto margins still apply. */
.hero__inner.shell { max-width: none; padding-inline: var(--hero-x); }

/* Narrower than the 34rem the photo hero used. It was tuned when the parked
   person was centred in whatever this left over; it is kept at 27rem because
   the illustration behind it is empty on the left for about the first 40% of
   the frame, and a wider column would run the subline into the mist. */
.hero__copy { max-width: var(--hero-copy-w); }

.hero h1 {
  color: var(--ink);
  font-size: clamp(2.4rem, 5.6vw, 4.1rem);
  font-weight: 600;
  line-height: 1.04;
  /* Uppercase, at the owner's ask, set in sentence case in the markup.
     Uppercase needs POSITIVE tracking where the display face wanted -0.042em:
     caps are drawn to sit apart, and the tight setting that flatters
     "Your Pace." jams "YOUR HOME." into one block. */
  text-transform: uppercase;
  letter-spacing: 0.005em;
  /* No text-shadow. The gradient is pale everywhere — its darkest tone is the
     bottom-right blue at #5BA7E2 — so dark ink clears AA against every part
     of it, and a shadow under dark type on a light ground only muddies it. */
}

/* Restored 2026-08-08 with the line itself. Dark ink and no text-shadow,
   where the photo hero's version was white with a shadow under it — the
   gradient is pale everywhere, so a shadow would only muddy the type.

   --ink-soft, NOT the --ink-muted this size of copy uses elsewhere. Measured
   against the gradient behind the line: #6b6b6b lands at 2.16:1, less than
   half of AA's 4.5, because the periwinkle under the subline is far darker
   than the white --ink-muted was chosen against. #3a3a3a is 4.60:1 and still
   reads as secondary to the black headline. Same call, for the same reason,
   as the #helps capsules. */
.hero__sub {
  margin-top: 22px;
  max-width: 30rem;
  color: var(--ink-soft);
  font-size: clamp(1.0625rem, 1.4vw, 1.1875rem);
  line-height: 1.55;
}

.hero .btn-row { margin-top: 34px; }

@media (max-width: 900px) {
  /* The column-reverse stack that lived here is gone with the person it was
     stacking — there is only the copy now, so the hero is a plain centred
     flex box at every width and the DOM order is the reading order.

     min-height stays below the desktop figure, at 82dvh rather than the 100
     the wide frame takes: a phone screen is portrait and a 16:9 drawing has
     to give up most of its width to fill one, so height bought here buys less
     picture than it does on desktop. It was 70 before the owner asked for
     bigger; 82 still clears the fold with the copy on a 667px-tall iPhone
     SE, which is the floor this number is really set by. */
  .hero {
    min-height: 82dvh;
    padding-block: 128px 64px;
    /* Copy to the TOP of the frame, not its middle. The source is 16:9 and a
       phone is portrait, so cover trims left and right and shows the picture's
       full height — which means the figure and the meadow are always in the
       lower half and the upper half is always open sky. Centred copy would
       land on her hair, the one dark thing in the picture; at the top it sits
       on the palest part of it. */
    align-items: flex-start;
    /* 74%, not 58%: below 16:9 the crop is horizontal, and centred it keeps
       the empty mist while throwing away the figure and the flowers — the two
       things the picture is for. She sits at 78% across; 74% holds her inside
       the frame down to 360px wide. */
    background-position: 74% center;
  }
  .hero__copy { max-width: none; }
  .hero__inner { text-align: center; }
  /* The button drops away from the subline and lands on the meadow (owner,
     2026-08-15: "move the join waitlist button a few spaces down so it sits on
     the grass"). PHONE ONLY — on desktop the crop is vertical, the flowers are
     off the bottom of the frame, and the same gap would just strand the button
     in mist.

     Proportional, not a fixed 130px, because this gap is the one thing that
     could push the button under the fold on a short phone: the copy is pinned
     to the top of an 82dvh box, so every pixel added here spends the slack
     between the subline and the bottom padding. 18dvh is ~142px on a 788px
     viewport, which clears the mountains and puts the whole pill in the
     flowers, and ~104px on an iPhone SE, where the picture is shorter and the
     button has to stay visible. Measured on the device, not guessed: at 16dvh
     the top third of the pill still sat on the mist. */
  .hero .btn-row { justify-content: center; margin-top: clamp(34px, 18dvh, 165px); }
}

/* =========================================================================
   Sections
   ========================================================================= */

.section { padding-block: var(--section-y); }

.section__head { max-width: 46ch; }
/* Body copy set under a .section__head inside a .split. Written 2026-08-14 for
   #you and in use on what-is-Health-Path.html, which is where the owner asked
   that copy to live; #you's own copy of the block is parked in index.html and
   would need this rule again if it ever comes back.

   THE TWO MEASURES MUST MATCH: .prose caps its paragraphs at --measure (62ch)
   and .split__copy is wider than 46ch, so without this the body runs past the
   right edge of the lead above it and the column has two ragged edges — which
   is exactly how what-is-Health-Path.html read until this went on it. Change
   one of these numbers and change the other. */
.split__body { max-width: 46ch; }
.section__head .eyebrow { margin-bottom: 16px; }
.section__head .lead { margin-top: 20px; }

.section__head--center {
  margin-inline: auto;
  text-align: center;
}
.section__head--center .lead { margin-inline: auto; }

/* For a centred head whose h2 is a full sentence. At the default 46ch the
   triad heading balanced onto three short lines with "your" stranded at two
   of the breaks; the extra width lets text-wrap: balance find two. */
.section__head--wide { max-width: 58ch; }

/* =========================================================================
   Split — copy left, picture right

   The shape the argument sections take since 2026-08-01 (owner's call). Text
   is set LEFT, never centred: a centred paragraph sitting beside a picture
   reads as its caption. Column ratio is the same as .story__grid on purpose,
   so the two sections line up down the page.

   Under 900px the picture goes ABOVE the copy rather than below it — it is
   the thing that makes you stop, and on a phone the copy alone opens as a
   wall of text.
   ========================================================================= */

.split {
  display: grid;
  grid-template-columns: minmax(0, 1fr) minmax(0, 0.82fr);
  gap: clamp(32px, 6vw, 88px);
  align-items: center;
}

/* Matches .disclosure's offset, so a "See more" link sits at the same
   distance from the copy whether it opens a panel or leaves the page. */
.split__actions { margin-top: 26px; }

@media (max-width: 900px) {
  .split { grid-template-columns: 1fr; gap: clamp(28px, 6vw, 44px); }
  .split__media { order: -1; }
}

/* --- Picture placeholder ------------------------------------------------
   Holds the exact space the real photograph will take, in the app's own wash
   rather than a dashed box, so an unfinished page still reads as deliberate.

   To fill one: drop the <img> in as the first child and delete the
   .media-panel__mark; the panel crops it to fill. Per-instance ratio via
   --ratio.

   The standing rule for what goes in is the hero's — no bodies, no people, no
   food portions — and it still governs any picture chosen without the owner
   in the room. #you's panel departs from it by their explicit instruction on
   2026-08-09; the reasoning, theirs and the rule's, is on that instance in
   index.html rather than repeated here.                                     */

.media-panel {
  position: relative;
  aspect-ratio: var(--ratio, 1 / 1.06);
  border-radius: var(--r-xl);
  overflow: hidden;
  border: 1px solid var(--hairline);
  /* Same two-radial wash as .story__aside — one family of empty panels. */
  background:
    radial-gradient(90% 80% at 20% 10%, #e6edfb 0%, rgba(230, 237, 251, 0) 60%),
    radial-gradient(85% 80% at 85% 90%, #efe8fb 0%, rgba(239, 232, 251, 0) 62%),
    var(--surface);
  display: grid;
  place-items: center;
  /* The same circularity the mockup stage below hit, one more time: an auto
     track sizes to its content, the content is the image, and the image's
     `height: 100%` is a percentage OF that track — so the browser gives up,
     lays the picture out at its natural size, and object-fit has nothing to
     crop against. The overflow is then clipped off the BOTTOM instead of
     cropped from both edges. The panel is still exactly the right shape, which
     is what hid it: it only shows when the picture's ratio and the panel's
     differ, and every panel on the site was empty until the article
     illustrations landed on 2026-08-08.

     Measured with a calibration SVG — bands at y = 0/60/163/266/326 of a
     330-tall source, pixel-scanned out of the render — a 16/7 panel showed
     0..210 before this line and the centred 60..270 after. Note `1fr` alone
     does NOT fix it: an fr track still has an automatic minimum of min-content,
     so the row grows to the picture. It has to be minmax(0, 1fr). */
  grid-template: minmax(0, 1fr) / minmax(0, 1fr);
}
/* --focus is the panel's crop anchor, set inline beside --ratio where a
   picture needs one (2026-08-14, with the article photographs). The five
   article illustrations are SQUARE and each panel shows a horizontal band of
   one: 69% of the height at 16:11 on the card, 44% at 16:7 on the article's
   own banner. Centred, that band cuts the top of a head on three of them, so
   each panel names the row to keep instead. The old SVGs did not need this —
   they were drawn for both ratios with the safe rows written into the file. */
.media-panel img {
  width: 100%;
  height: 100%;
  object-fit: cover;
  object-position: var(--focus, 50% 50%);
}

.media-panel__mark {
  display: inline-flex;
  align-items: center;
  gap: 9px;
  padding: 9px 16px 9px 13px;
  border-radius: var(--r-pill);
  border: 1px solid var(--hairline);
  background: rgba(255, 255, 255, 0.72);
  font-size: 0.875rem;
  color: var(--ink-muted);
}
.media-panel__mark svg { width: 18px; height: 18px; color: var(--primary); }

@media (max-width: 900px) {
  .media-panel { aspect-ratio: 16 / 10; }
}

/* =========================================================================
   #helps — three circles, the mission composition

   Owner, 2026-08-10: replace this section with the mission one from the
   page-structure reference, "don't add the illustrations and text for now
   only the circles". So it is the reference's three-column mission row with
   only its top row present — no picture inside, no title, no sentence.

   The HEADLINE above it is still the old one ("The struggles nobody talks
   about, finally have a place."), which the owner chose when asked. The
   section is therefore titled as the struggles and laid out as the mission on
   purpose; do not "fix" one to match the other without asking.

   Each item is circle, then title, then one sentence — the owner's copy
   arrived a step after the circles did. Text is CENTRED, which the page does
   in exactly two places: here and in the capsules this replaced. Same reason
   both times — there is no picture beside the copy to set an edge for it, the
   measure is short, and a centred illustration over ragged-left text under it
   looks like a mistake.

   Bigger circles than #inside's (128 against 96 at the ceiling) because there
   are three across a full row rather than six down two rails, and they head a
   column rather than label one. Same two-radial fill as every other circle on
   the site.

   Three columns hold down to about 820px. Below that the titles — "Progress
   Without Perfection" is the long one — break onto three lines and the row
   stops reading as three peers, so it goes to a single column. Not two: three
   items in two columns strands one.
   ========================================================================= */

/* Wider than .section__head--wide, and the reason is a trap worth naming: that
   token is 58ch, and `ch` resolves against the CONTAINER's 16px rather than
   the 45px headline inside it — so it lands at about 557px whatever the h2 is
   set in. That width balanced the old 55-character headline onto two lines and
   broke this 57-character one onto three, which is a lot of 45px type above
   three small columns. 720px puts it back to two. Re-check on a headline
   swap; this is tuned to a specific string. */
#helps .section__head { max-width: 720px; }

/* SCALED UP 2026-08-14 — owner: "in the second section with 3 circle photos
   make everything bigger", then "i mean only the 3 photos and text
   underneat". So the head above this row is deliberately untouched: the
   circles, the titles and the sentences grew, the h2 and lead did not.

   What moved, and it moved together on purpose — a circle that grows while
   the type under it does not reads as a picture that got out of hand rather
   than a block that got bigger:

     .mission-dot     88-128px -> 124-180px
     title            1-1.125rem -> 1.125-1.375rem
     sentence         0.9375rem -> 1.0625rem
     row              960px -> 1040px, gap up with it

   The pictures were REBUILT at 512px for this, from the owner's 1254px
   originals archived in ..\design\mission-circles\. At 256 they were 2x the
   old 128px ceiling and would have been 1.42x the new one - visibly soft on
   any modern screen. 512 is 2.84x and still 16-18KB each.                    */
.mission-row {
  display: grid;
  grid-template-columns: repeat(3, 1fr);
  align-items: start;
  gap: clamp(32px, 5vw, 72px);
  max-width: 1040px;
  margin: clamp(40px, 6vw, 68px) auto 0;
  padding: 0;
  list-style: none;
}

.mission-item {
  display: grid;
  justify-items: center;
  text-align: center;
}

.mission-item__title {
  margin: clamp(20px, 2.2vw, 30px) 0 0;
  font-size: clamp(1.125rem, 1.9vw, 1.375rem);
  font-weight: 600;
  letter-spacing: -0.016em;
}

/* Held to ~30ch rather than the page's 62ch --measure: three of these sit side
   by side, and a column that fills its full width leaves no air between the
   three blocks of text even when the grid gap is generous. */
.mission-item p {
  margin-top: 12px;
  max-width: 29ch;
  font-size: 1.0625rem;
  line-height: 1.64;
  color: var(--ink-muted);
}

/* 820 -> 900 with the type. The breakpoint was never about 820px as such; it
   is where "Progress Without Perfection" — the long title — breaks onto three
   lines and the row stops reading as three peers. Bigger titles hit that
   sooner, and 900 is where it now happens. Still not two columns: three items
   in two strands one. */
@media (max-width: 900px) {
  .mission-row { grid-template-columns: 1fr; max-width: 470px; }
}

.mission-dot {
  width: clamp(124px, 13vw, 180px);
  aspect-ratio: 1;
  display: grid;
  place-items: center;
  overflow: hidden;
  border-radius: 50%;
  border: 1px solid var(--hairline);
  background:
    radial-gradient(90% 80% at 20% 10%, #e6edfb 0%, rgba(230, 237, 251, 0) 62%),
    radial-gradient(85% 80% at 85% 90%, #efe8fb 0%, rgba(239, 232, 251, 0) 64%),
    var(--surface);
}
.mission-dot img,
.mission-dot svg { width: 100%; height: 100%; object-fit: cover; }

/* =========================================================================
   PARKED — Bubbles: the six struggles (#helps)

   Taken off the page 2026-08-10 when the mission row above replaced them; the
   markup is parked as a comment in index.html, so uncommenting both halves
   restores the section exactly. Delete both halves together, or neither.

   Everything below this line describes that parked row.

   THE SHAPE is a RECTANGLE WITH OVAL ENDS — flat top and bottom, the left and
   right edges turned into full half-circles. Owner's call, 2026-08-01, over
   the organic blobs this section shipped with earlier the same day.

   It is one declaration: border-radius: var(--r-pill). 999px is far larger
   than any bubble, so the browser clamps it to half the height — which is
   exactly the capsule, at every width, with no per-breakpoint numbers to keep
   in sync. Do NOT swap it for a measured px radius to "fix" a breakpoint; the
   clamp is what makes it correct everywhere.

     ╭──────────────────────────────╮
    (        flat top / bottom       )   <- ends are semicircles
     ╰──────────────────────────────╯

   Two things follow from the shape and must not be dropped:

   1. EVERY BUBBLE THE SAME HEIGHT (grid-auto-rows: 1fr). The end radius IS
      half the height, so two bubbles side by side at different heights get
      visibly different end curves — with a rectangle that reads as a mistake
      in a way six different blobs did not.

   2. THE HORIZONTAL PADDING CLEARS THE END ARCS. The widest point of the
      capsule is its vertical middle; the first and last lines of copy sit
      ~70px above and below that, where each end has already curved ~24px
      inwards. The padding below is what keeps those lines off the arc. Shrink
      it and the top line is the first to touch — check the two-column
      breakpoint, where the boxes are tallest and the arcs deepest.

   THE TEXT IS CENTRED here and nowhere else on the page. #you and #story set
   their copy left because a picture sits beside it; there is no picture here,
   the measure is short (~40ch), and ragged-left text between two symmetrical
   arcs looks like a mistake.

   Body copy is --ink-soft rather than the usual --ink-muted: on a tint,
   #6b6b6b lands at about 4.5:1 — right on the line — and the mint bubble fell
   under it. --ink-soft clears 9:1 on every tint below.
   ========================================================================= */

.bubbles {
  display: grid;
  grid-template-columns: repeat(2, minmax(0, 1fr));
  /* Every bubble the same height — load-bearing, see (1) in the note above. */
  grid-auto-rows: 1fr;
  gap: clamp(20px, 2.6vw, 32px);
  /* Narrower than the 1160px shell: two capsules at full width read as slabs. */
  max-width: 1000px;
  margin: clamp(44px, 5vw, 68px) auto 0;
  padding: 0;
  list-style: none;
}

.bubble {
  display: flex;
  flex-direction: column;
  justify-content: center;
  padding: clamp(42px, 5vw, 54px) clamp(48px, 6.5vw, 78px);
  border-radius: var(--r-pill);
  text-align: center;
  background: var(--tint);
  transition: transform var(--dur) var(--ease-out),
              box-shadow var(--dur) var(--ease-out);
}

/* Hover lifts the whole capsule a little. Shadow is tinted to the bubble
   rather than grey so the lift stays soft-edged and doesn't sit on the page
   like a card with a drop shadow. */
.bubble:hover {
  transform: translateY(-3px);
  box-shadow: 0 18px 34px rgba(70, 90, 160, 0.10);
}

.bubble__title {
  margin-bottom: 14px;
  font-size: clamp(1.1rem, 1.5vw, 1.25rem);
  letter-spacing: -0.018em;
}

.bubble p {
  /* ~40ch keeps the line length inside the readable band even though the
     column is wide enough to run longer. */
  max-width: 40ch;
  margin-inline: auto;
  color: var(--ink-soft);
  font-size: 0.9688rem;
  line-height: 1.68;
  text-wrap: pretty;
}

/* One shape, six tints — the shape is on .bubble now, so these carry colour
   only. Tints are the app's own palette washed back to ~94% lightness; the
   sixth drops the colour entirely (see the markup note). */
.bubble--a { --tint: #e8effb; }
.bubble--b { --tint: #efe9fb; }
.bubble--c { --tint: #e6f2ea; }
.bubble--d { --tint: #e1f2f0; }
.bubble--e { --tint: #dfeefa; }

.bubble--f {
  border: 1px solid var(--hairline);
  background:
    radial-gradient(90% 80% at 20% 10%, #e6edfb 0%, rgba(230, 237, 251, 0) 60%),
    radial-gradient(85% 80% at 85% 90%, #efe8fb 0%, rgba(239, 232, 251, 0) 62%),
    var(--surface);
}

/* One column below 820px. A phone bubble is a wider, shorter box, so its ends
   are shallower arcs and the side padding can come back in — the radius needs
   no help, it follows the height on its own. */
@media (max-width: 820px) {
  .bubbles { grid-template-columns: 1fr; max-width: 560px; }
  .bubble { padding: clamp(40px, 8vw, 50px) clamp(40px, 8.5vw, 64px); }
}

@media (prefers-reduced-motion: reduce) {
  .bubble { transition: none; }
  .bubble:hover { transform: none; }
}

/* =========================================================================
   Sub-page — an ordinary content page sitting under the fixed nav pill
   ========================================================================= */

/* 152px is the legal pages' clearance (.doc), kept identical so every page
   that is not the hero starts its copy at the same height. */
.subpage {
  padding-top: 152px;
  padding-bottom: var(--section-y);
}

.subpage .prose p:first-child { padding-top: 26px; }

/* =========================================================================
   PARKED — the old #you section: the counting outside, and the quiet inside

   Replaced 2026-08-01 by the .split layout above; the markup is kept as a
   comment in index.html and these rules are kept here so uncommenting it
   restores the block exactly. Delete both together, or neither.

   Its whole job was a contrast, and the contrast was made of space: a tight,
   grey, crowded band of everything that keeps count of you, then a
   deliberately large gap, then one calm statement with room around it. The
   emptiness between the two IS the argument — do not close it up.
   ========================================================================= */

.noise {
  display: flex;
  flex-wrap: wrap;
  justify-content: center;
  gap: 10px;
  /* Tuned, not rounded: 720px wrapped the six chips 3 / 2 / 1, which reads as
     a deliberate pyramid rather than a crowd. 780px is the narrowest width
     that still holds the second row of three, giving an even 3 / 3. */
  max-width: 780px;
  margin: clamp(36px, 4.5vw, 52px) auto 0;
  padding: 0;
  list-style: none;
}

/* Deliberately the page's only grey-on-grey chips: they are the noise, so they
   read as a crowd rather than as anything you would want to tap. Measured at
   4.89:1 against --surface-muted — muted, but never below AA. Do NOT dim them
   with opacity to make them recede; that was tried and it drops the smaller
   ones to roughly 2.3:1. */
.noise li {
  padding: 9px 18px;
  border: 1px solid var(--hairline);
  border-radius: var(--r-pill);
  background: var(--surface-muted);
  font-size: 0.9063rem;
  line-height: 1.5;
  color: var(--ink-muted);
}

.quiet {
  max-width: 48ch;
  /* The big gap. It is the turn from "out there" to "in here". */
  margin: clamp(56px, 8vw, 104px) auto 0;
  text-align: center;
}

.quiet__line {
  font-size: clamp(1.0625rem, 1.35vw, 1.1875rem);
  line-height: 1.68;
  color: var(--ink-muted);
}

/* The answer to the paragraph above it. Same size, but full ink and a step up
   in weight — that alone is enough to make it land, and it keeps the hinge
   below as the only large type in the block. */
.quiet__turn {
  margin-top: 20px;
  max-width: 34ch;
  margin-inline: auto;
  font-size: clamp(1.0625rem, 1.35vw, 1.1875rem);
  font-weight: 500;
  line-height: 1.6;
  color: var(--ink);
}

/* A statement, not a section heading — hence a <p> at heading scale. Sized
   below .h2 so it does not compete with the real headings on either side. */
.quiet__hinge {
  margin-top: 30px;
  max-width: 20ch;
  margin-inline: auto;
  font-size: clamp(1.5rem, 3vw, 2.1rem);
  font-weight: 600;
  line-height: 1.18;
  letter-spacing: -0.028em;
  text-wrap: balance;
  color: var(--ink);
}

/* --- PARKED: Story ------------------------------------------------------
   #story ("Why this exists") was removed from index.html 2026-08-01 on the
   owner's instruction and its markup is parked as a comment there. These rules
   are kept so uncommenting it restores the section exactly. Delete both halves
   together, or neither.

   Note .story__aside's two-radial wash: .media-panel copies it, and the two
   are meant to look like one family of empty panels. If this block is ever
   deleted for real, that comment on .media-panel needs correcting.          */

.story__grid {
  display: grid;
  grid-template-columns: minmax(0, 1fr) minmax(0, 0.82fr);
  gap: clamp(32px, 6vw, 88px);
  align-items: center;
}

.story__aside {
  position: relative;
  aspect-ratio: 1 / 0.92;
  border-radius: var(--r-xl);
  overflow: hidden;
  background:
    radial-gradient(90% 80% at 20% 10%, #e6edfb 0%, rgba(230, 237, 251, 0) 60%),
    radial-gradient(85% 80% at 85% 90%, #efe8fb 0%, rgba(239, 232, 251, 0) 62%),
    var(--surface);
  border: 1px solid var(--hairline);
  display: grid;
  place-items: center;
  padding: 40px;
}
.story__aside blockquote {
  margin: 0;
  max-width: 22ch;
  text-align: center;
  /* Zen Maru Gothic — the app's one deliberate non-Inter face, reserved for
     a quote. Carried over here for exactly the same role. */
  font-family: "Zen Maru Gothic", "Inter", sans-serif;
  font-size: clamp(1.25rem, 2.1vw, 1.6rem);
  font-weight: 400;
  line-height: 1.5;
  letter-spacing: 0;
  color: var(--ink-soft);
}

@media (max-width: 900px) {
  .story__grid { grid-template-columns: 1fr; }
  .story__aside { aspect-ratio: 16 / 10; order: -1; }
}

/* --- end PARKED: Story -------------------------------------------------- */

/* --- Disclosure (Read more / See more) --------------------------------- */

.disclosure { margin-top: 26px; }

.disclosure__panel {
  display: grid;
  grid-template-rows: 0fr;
  transition: grid-template-rows 320ms var(--ease-out),
              opacity 220ms var(--ease-out);
  opacity: 0;
}
.disclosure__panel > div { overflow: hidden; }
.disclosure[data-open="true"] .disclosure__panel {
  grid-template-rows: 1fr;
  opacity: 1;
}
/* An author `display: grid` outranks the UA sheet's [hidden] rule, so the
   collapsed state has to be restated here or app.js's panel.hidden is a no-op. */
.disclosure__panel[hidden] { display: none; }

.prose > p + p { margin-top: 1.1em; }
.prose p {
  max-width: var(--measure);
  color: var(--ink-muted);
  font-size: 1rem;
  line-height: 1.78;
}
.prose p:first-child { padding-top: 22px; }

/* =========================================================================
   PARKED — Triad: body / mind / soul, drawn as three circles that share a
   centre

   Taken off the page 2026-08-01 when #inside was rebuilt as four widgets. The
   markup is kept as a comment at the foot of that section, so uncommenting it
   brings the diagram straight back — here or wherever it ends up belonging.
   Delete both halves together, or neither.

   The section's claim was that the three are not separable, so the diagram has
   to overlap; three circles side by side would illustrate the opposite point.
   mix-blend-mode does the work — where two tints cross they darken on their
   own, so the intersections need no extra elements and stay correct whatever
   the circles are recoloured to.

   Colours are the same three the pillar cards are tinted with, one step more
   saturated so they survive being multiplied together.
   ========================================================================= */

.triad {
  position: relative;
  width: min(376px, 80vw);
  /* Slightly wider than tall: two circles across the top, one below. */
  aspect-ratio: 1 / 0.92;
  margin: clamp(40px, 5vw, 60px) auto 0;
}

.triad__c {
  position: absolute;
  display: grid;
  justify-items: center;
  width: 60%;
  aspect-ratio: 1;
  border-radius: 50%;
  mix-blend-mode: multiply;
  font-size: 0.9375rem;
  font-weight: 500;
  letter-spacing: -0.004em;
  color: var(--ink);
}

/* Labels are pushed to the outer edge of each circle, away from the shared
   middle — a word sitting in the intersection would be read as belonging to
   the overlap rather than to its own circle. */
.triad__c--body,
.triad__c--mind { align-items: start; padding-top: 13%; }
.triad__c--soul { align-items: end;   padding-bottom: 13%; }

.triad__c--body { top: 0;    left: 0;    background: #d9ede1; }
.triad__c--mind { top: 0;    right: 0;   background: #dbe6fa; }
.triad__c--soul { bottom: 0; left: 20%;  background: #e6ddf7; }

/* =========================================================================
   #inside — the phone, and six named circles around it

   Owner, 2026-08-10, in two messages: take the four widgets out and put their
   photo in the centre, then put six circles round it, empty for now, joined
   to the phone by lines, "like it's on the photo reference".

   The shape is a three-column grid — rail, phone, rail — and the two rails
   are three-row grids of their own, so the circles land at the sixths of the
   phone's height without a single measured offset. The reference has them at
   13% / 47% / 80% of the phone; centring in equal thirds gives 17 / 50 / 83,
   which is the same composition and survives the phone changing size.

   Each node is its own little grid: [name][circle][line] on the left,
   mirrored on the right. It has to be a grid and not a flex row, because the
   circles must line up in a column and the names are different lengths — with
   flex, "Hydration" would push its circle further in than "Food" does.

   DOM ORDER IS ALWAYS name, circle, line. The right rail only reverses them
   visually, by grid-column, so a screen reader and a keyboard still meet the
   name first on both sides.

   The circles are EMPTY BY INSTRUCTION, not unfinished. Their fill is the
   same two-radial recipe .widget__circle uses, so whatever goes in later
   matches what was here before.

   The phone's shadow is a filter: drop-shadow, never box-shadow — the PNG's
   corners are rounded and transparent, and box-shadow traces the image's
   rectangle instead of the device.
   ========================================================================= */

.inside-map {
  --inside-dot: clamp(64px, 6.6vw, 96px);
  --inside-line: clamp(18px, 3.2vw, 48px);
  --inside-name-gap: clamp(10px, 1.4vw, 18px);

  display: grid;
  grid-template-columns: 1fr auto 1fr;
  align-items: stretch;
  /* No gap, on purpose: the connectors have to TOUCH. A rail that is held off
     the phone leaves its lines ending in mid-air, which is what a gap here
     did. The rails are 1fr and the phone is auto, so each rail's inner edge
     IS the phone's edge, and a line that fills the last column meets it. Air
     around the names comes from --inside-name-gap instead, which only opens
     up on the side that needs it. */
  gap: 0;
  /* 1060, not the 1020 this was when the rails held bare names: each side now
     carries a sentence, and the text column is whatever is left after the
     circle, the connector and the gap. At 1020 with a 300px phone that left
     about 190px, where these sentences run five and six lines and the rails
     grow taller than the phone between them. 1060 against a 280px phone
     leaves ~230px and three lines. It is still inside .shell's 1064px content
     box at 1440, so the row does not break out the way #inside's old widget
     row did. */
  max-width: 1060px;
  margin: clamp(36px, 5vw, 60px) auto 0;
}

.inside-phone { width: min(280px, 42vw); }
.inside-phone img {
  width: 100%;
  height: auto;
  filter: drop-shadow(0 26px 46px rgba(70, 90, 160, 0.18));
}

/* Three equal rows across the phone's full height — that is what `stretch` on
   the parent is for. Take it off and the rails shrink to their content and
   the circles bunch in the middle. */
.inside-rail {
  display: grid;
  grid-template-rows: repeat(3, 1fr);
  margin: 0;
  padding: 0;
  list-style: none;
}

.inside-node {
  display: grid;
  align-items: center;
  column-gap: 0;
}

.inside-node__name {
  font-size: clamp(0.9375rem, 1.3vw, 1.0625rem);
  font-weight: 600;
  letter-spacing: -0.012em;
}

/* Sits under the name, in the same column, beside the circle. Smaller than the
   page's body size because there are six of these and they read as captions on
   the phone rather than as prose — and because the column is only ~230px wide
   at 1440, where a 1rem line would run about four words. */
.inside-node__desc {
  margin-top: 7px;
  font-size: 0.8438rem;
  line-height: 1.62;
  font-weight: 400;
  letter-spacing: 0;
  color: var(--ink-muted);
}

.inside-node__dot {
  width: var(--inside-dot);
  aspect-ratio: 1;
  display: grid;
  place-items: center;
  overflow: hidden;
  border-radius: 50%;
  border: 1px solid var(--hairline);
  background:
    radial-gradient(90% 80% at 20% 10%, #e6edfb 0%, rgba(230, 237, 251, 0) 62%),
    radial-gradient(85% 80% at 85% 90%, #efe8fb 0%, rgba(239, 232, 251, 0) 64%),
    var(--surface);
}
.inside-node__dot img,
.inside-node__dot svg { width: 100%; height: 100%; object-fit: cover; }

.inside-node__line { height: 1px; background: rgba(112, 149, 223, 0.5); }

.inside-rail--left .inside-node { grid-template-columns: 1fr auto var(--inside-line); }
.inside-rail--left .inside-node__text { text-align: right; padding-right: var(--inside-name-gap); }

/* Mirrored by column assignment, not by reordering the markup.

   grid-row: 1 on all three is LOAD-BEARING, not tidiness. Naming a column and
   leaving the row to auto-placement puts each item on its own row: the cursor
   has already passed column 2 by the time the circle asks for it, so the
   circle drops to row 2 and the line to row 3. It renders as a name floating
   above its own circle with the connector stranded below — which is exactly
   what the first render of this showed. */
.inside-rail--right .inside-node { grid-template-columns: var(--inside-line) auto 1fr; }
.inside-rail--right .inside-node > * { grid-row: 1; }
.inside-rail--right .inside-node__line { grid-column: 1; }
.inside-rail--right .inside-node__dot  { grid-column: 2; }
.inside-rail--right .inside-node__text { grid-column: 3; padding-left: var(--inside-name-gap); }

/* Under 900 the three columns cannot hold: the rails squeeze the names onto
   two lines long before the phone gets narrow enough to matter. So the map
   stacks — phone first, then the six as two rows of three. The lines go with
   the layout: a connector that connects nothing is just a stray rule.

   These selectors are the same weight as the desktop ones above and come
   after them, which is what lets them win without !important. */
@media (max-width: 900px) {
  .inside-map {
    grid-template-columns: 1fr;
    justify-items: center;
    gap: clamp(26px, 5vw, 40px);
  }
  .inside-phone { width: min(260px, 62vw); order: -1; }
  /* 660, not the 460 this was while the rails held bare names: three columns
     of 153px put these sentences on five lines each. */
  .inside-rail {
    grid-template-rows: none;
    grid-template-columns: repeat(3, 1fr);
    gap: clamp(14px, 4vw, 26px);
    width: 100%;
    max-width: 660px;
  }
  .inside-map .inside-node {
    grid-template-columns: 1fr;
    justify-items: center;
    row-gap: 12px;
  }
  /* grid-row: auto undoes the desktop row pinning, or all three stack on one
     row again — this time on top of each other. */
  .inside-map .inside-node > * { grid-row: auto; }
  .inside-map .inside-node__text {
    grid-column: 1;
    text-align: center;
    padding: 0;
    order: 2;
    max-width: 34ch;
  }
  .inside-map .inside-node__dot  { grid-column: 1; order: 1; }
  .inside-map .inside-node__line { display: none; }
}

/* Below 620 even three widened columns run out: at 375px they are ~110px and
   the sentence goes to seven lines. So the six become a LIST — circle at the
   left, name and sentence beside it, one per row. That is the shape a phone
   reads best, and it is shorter than six centred blocks, not taller.

   Placement is explicit here (both a column and a row on each part), so the
   `order` the 900 block uses to lift the circle above the name is simply
   ignored rather than fought with. */
@media (max-width: 620px) {
  .inside-rail {
    grid-template-columns: 1fr;
    gap: 22px;
    max-width: 420px;
  }
  .inside-map .inside-node {
    grid-template-columns: auto 1fr;
    justify-items: start;
    align-items: center;
    column-gap: 16px;
    row-gap: 0;
  }
  .inside-map .inside-node__dot  { grid-column: 1; grid-row: 1; }
  .inside-map .inside-node__text {
    grid-column: 2;
    grid-row: 1;
    text-align: left;
    max-width: none;
  }
}

/* =========================================================================
   PARKED — Widgets: the four things inside the app

   Taken off the page 2026-08-10 when the phone above replaced them; the
   markup is parked as a comment in index.html at the foot of #inside, so
   uncommenting both halves restores the row exactly as it shipped. Delete
   both halves together, or neither. app.js still carries its clamp and stage
   code and needs no change either way — every entry point guards on the
   elements being absent.

   Everything below this line describes that parked row.

   Replaced the three .pillar cards on 2026-08-01. Centred composition, and
   FOUR ACROSS on desktop by the owner's explicit choice, made against a
   recommended 2×2.

   THE ROW IS WIDER THAN THE PAGE (owner, same day: bigger cards, squarer
   shape). It breaks out of the 1160px .shell to 1280px, which makes it the
   widest element on the site — the section head above it, and every other
   section, still sit in the shell. That misalignment is deliberate and is the
   price of the shape: inside the shell, four across caps each card at ~250px
   against a ~377px height, which is a portrait card however the type is
   tuned. At 1280 the cards are ~305px against ~365px, close to square.

   The breakout is arithmetic, not a magic number: the row takes the smaller of
   1280px and the viewport minus its gutters, then pulls itself back out of the
   shell's content box by half the difference. It therefore stays centred and
   can never be wider than the window, so nothing here needs a breakpoint.
   Do NOT re-implement it by widening .shell — that would drag every other
   section out with it.

   The old .pillar / .parts / .part rules went with the markup; the copy they
   carried is in git history at commit 81c042b.
   ========================================================================= */

.widgets {
  --widgets-w: min(1280px, calc(100vw - 2 * var(--gutter)));

  display: grid;
  grid-template-columns: repeat(4, minmax(0, 1fr));
  gap: 22px;
  margin-top: clamp(40px, 5vw, 64px);

  width: var(--widgets-w);
  /* 100% here is the shell's content box, so this is negative on desktop and
     harmlessly ~0 once the viewport is narrower than the shell. */
  margin-inline: calc((100% - var(--widgets-w)) / 2);

  /* Each card is its own height (owner, 2026-08-08: "only the particular one
     clicked should expand, the others stay as they are").

     A grid stretches its items to the tallest in the row by default, so
     opening ONE "See more" grew the row and dragged the other three up with
     it — three cards suddenly a paragraph taller with nothing in the space.
     The JS was never the bug: only the clicked card's text was ever
     un-clamped, but all four BOXES grew, which is what you see.

     This costs nothing at rest. The cards are already the same height there
     by construction — same circle, a title that reserves two lines whether it
     needs them or not, and a paragraph clamped to exactly three. */
  align-items: start;

  /* The opened screen is positioned against this box. */
  position: relative;
  transition: min-height 320ms var(--ease-out);
}

/* =========================================================================
   The opened app screen

   Owner, 2026-08-08: no separate window. Clicking "See more" on a card blurs
   the other three and puts a mockup of that screen in the app over the space
   they occupy - they stay visible underneath, soft.

   The stage is ABSOLUTE, so it is not a fifth grid item. Its left edge is
   column two's, and that is arithmetic rather than a guess: with four equal
   columns and a 22px gap, a column is (100% - 3*22px)/4, so column two starts
   at that plus one gap, which simplifies to 25% + 5.5px. Change the column
   count or the gap and this line has to change with them.
   ========================================================================= */

.widgets__stage {
  position: absolute;
  top: 0;
  bottom: 0;
  left: calc(25% + 5.5px);
  right: 0;
  display: grid;
  /* Top-aligned, not centred. Centring made the capsule hang below the cards
     by however much taller than them the row happened to be; starting it at
     the same y as the card beside it is what makes the two read as one row. */
  /* stretch, so the panel takes the row height rather than sitting inside it */
  place-items: stretch;
  /* And the track has to be told its size, or the whole chain goes circular:
     an auto row sizes to its content, the content is the panel, the panel's
     height is a percentage OF that row - so the browser gives up, falls back
     to the image's natural 1196px, and the panel grows to 1238 inside a 494px
     stage. Measured exactly that before this line existed. A 1fr track against
     the stage's own definite height breaks the loop. */
  grid-template: minmax(0, 1fr) / minmax(0, 1fr);
  pointer-events: none;
  opacity: 0;
  scale: 0.96;
  transition: opacity 300ms var(--ease-out), scale 300ms var(--ease-out);
}

/* A veil over the blurred cards. The blur alone leaves them at full strength
   and they compete with the screen sitting on top of them. */
.widgets__stage::before {
  content: "";
  position: absolute;
  inset: -10px;
  border-radius: var(--r-lg);
  background: rgba(250, 250, 248, 0.42);
}

/* The panel the screen sits in (owner, 2026-08-08, third shape in a row: "big
   with free white space in it, like the shape of the widgets but horizontally
   longer").

   So it is a widget: the same --r-lg corner, the same surface, the same
   hairline and the same family of shadow — just wide instead of tall, filling
   the three columns it covers. It went capsule for one revision before this;
   the capsule is what the #helps bubbles are, and having it here as well made
   two different sections speak with the same shape.

   The white space is the point of it, so the phone inside is sized well under
   the panel's height rather than filling it. */
.widgets__panel {
  position: relative;
  display: grid;
  place-items: center;
  /* Same circularity one level down: the phone's height is a percentage of its
     grid area, and an auto area sizes to the phone. Fixing the track here is
     what actually makes `height: 100%` on the image resolve - the panel being
     the right height was not enough on its own, measured. */
  grid-template: minmax(0, 1fr) / minmax(0, 1fr);
  width: 100%;
  /* Exactly as tall as the card beside it (owner, 2026-08-08). No number of
     its own any more: the stage is stretched over the row, the row is as tall
     as its tallest item, and the tallest item is the open card - so the panel
     takes that height by inheriting it. Nothing to keep in step when the copy
     in a card changes length.
     The padding is small on purpose; every pixel of it comes off the phone,
     and the phone is meant to fill this. */
  height: 100%;
  padding: clamp(12px, 1.4vw, 20px);
  border: 1px solid var(--hairline);
  border-radius: var(--r-lg);
  box-shadow: var(--lift-md);

  /* Not solid white (owner, 2026-08-08: "a little transparent so the widgets
     show"). 0.82 is the point where the three cards read as shapes behind the
     panel without their text starting to compete with the screen in front of
     it — they are already blurred by 5px, and this is a second, softer pass
     over the same thing rather than a substitute for it.

     backdrop-filter as well as the alpha: without it the cards show through at
     their own contrast and the panel reads as dirty rather than as glass. The
     saturate is what keeps the white from going grey. */
  background: rgba(255, 255, 255, 0.55);
  backdrop-filter: blur(10px) saturate(150%);
  -webkit-backdrop-filter: blur(10px) saturate(150%);
}

/* The capture, IN A PHONE (owner, 2026-08-08: "with a phone too, not only
   screen").

   The bezel is the image's own border rather than a wrapper element, which
   keeps the markup at one node and means the frame can never come apart from
   the screen inside it. box-sizing is border-box across this stylesheet, so
   the 11px eats into the height rather than adding to it and the phone still
   matches the card beside it exactly.

   Dark, not grey: every capture has a white status bar at the top and a
   near-black gesture bar at the bottom, and a mid-grey frame would read as a
   smudge against both. */
.widgets__panel img {
  display: block;
  border: 11px solid #14161c;
  /* 100% resolves here only because BOTH grid tracks above are definite - see
     the notes on the stage and the panel. With either one left auto it falls
     back to the capture's natural 1196px and hangs out of the panel. */
  height: 100%;
  max-height: 100%;
  width: auto;
  max-width: 100%;
  /* The outer radius is the device's; the screen inside gets the difference
     back automatically, so the two curves stay concentric. */
  border-radius: 34px;
  box-shadow: 0 18px 40px rgba(16, 24, 40, 0.18), 0 3px 10px rgba(16, 24, 40, 0.10);
}

.widgets[data-showing] .widgets__stage { opacity: 1; scale: 1; pointer-events: auto; }

/* The three the reader did not click. Still there, out of focus. */
.widget { transition: filter 300ms var(--ease-out), opacity 300ms var(--ease-out); }
.widgets[data-showing] .widget:not([data-active]) {
  filter: blur(5px);
  opacity: 0.55;
}
/* Whichever card was clicked goes to the left of the row. `order` works here
   because the grid places items in order-modified document order - there is
   no explicit placement on any of them. */
.widget[data-active] { order: -1; }

@media (prefers-reduced-motion: reduce) {
  .widgets, .widgets__stage, .widget { transition: none; }
  .widgets__stage { scale: 1; }
}

.widget {
  display: flex;
  flex-direction: column;
  align-items: center;
  text-align: center;
  padding: clamp(24px, 2.2vw, 32px) clamp(20px, 2.1vw, 30px) clamp(24px, 2.1vw, 30px);
  border: 1px solid var(--hairline);
  border-radius: var(--r-lg);
  background: var(--surface);
  box-shadow: var(--lift-sm);
  transition: box-shadow var(--dur) var(--ease-out),
              transform var(--dur) var(--ease-out);
}
.widget:hover { box-shadow: var(--lift-md); transform: translateY(-2px); }

/* The picture. Round, and it stays round once a real one lands: the <img> is
   cropped by the circle's own overflow rather than by a mask, so any source
   aspect ratio works without being prepared first. Same wash as .media-panel,
   so every empty picture on the site is empty in the same way. */
.widget__circle {
  display: grid;
  place-items: center;
  width: clamp(84px, 7.4vw, 98px);
  aspect-ratio: 1;
  /* Slightly smaller and closer to the title than it was at 250px cards: the
     card got wider, so the same circle would have read as a stray dot with a
     gap under it. */
  margin-bottom: 18px;
  border-radius: 50%;
  overflow: hidden;
  border: 1px solid var(--hairline);
  background:
    radial-gradient(90% 80% at 20% 10%, #e6edfb 0%, rgba(230, 237, 251, 0) 62%),
    radial-gradient(85% 80% at 85% 90%, #efe8fb 0%, rgba(239, 232, 251, 0) 64%),
    var(--surface);
}
.widget__circle img { width: 100%; height: 100%; object-fit: cover; }
.widget__circle svg { width: 24px; height: 24px; color: var(--primary); }

/* Deliberately NOT .h3. Even at the wider 1280 row, .h3's 1.3rem breaks the
   two longer titles onto four lines and the row stops scanning as four equal
   things. This sits just under it, and went up a step with the card. */
.widget__title {
  margin-bottom: 10px;
  font-size: clamp(1.0313rem, 1.2vw, 1.1563rem);
  line-height: 1.3;
  letter-spacing: -0.018em;
  /* Two lines' worth whether the title needs them or not. At the wider card
     two of these four titles fit on one line and two do not, which left the
     paragraphs and the "See more" links sitting at four different heights.
     2.6em is 2 × the line-height above, so it follows the type. */
  min-height: 2.6em;
}

.widget p {
  color: var(--ink-muted);
  font-size: var(--widget-text);
  line-height: var(--widget-leading);
}

/* --- Three lines, then "See more" (owner, 2026-08-01) -------------------
   Collapsed height is computed from the type, not guessed: three lines of
   var(--widget-text) at var(--widget-leading) — 76.5px at the current 0.9375rem
   / 1.7 — so the cut always lands ON a baseline gap. Change either value and
   the clamp follows; do NOT replace this with a rounded px height, or the
   third line starts getting sliced.

   -webkit-line-clamp would do the same job in one declaration but cannot be
   animated, and the grid 0fr→1fr trick the disclosures use cannot express
   "three lines". Hence a max-height that JS raises to the measured height.

   No gradient fade over the cut. One was tried: it dims the third line, and
   this body copy is already --ink-muted, which has no contrast to spare.
   The button carries the meaning instead. */

.widget {
  /* Went up with the card: 0.875rem was set for a 250px column and reads thin
     at 305px. The clamp height follows these two on its own. */
  --widget-text: 0.9375rem;
  --widget-leading: 1.7;
  --widget-lines: 3;
}

.widget__text {
  overflow: hidden;
  transition: max-height 320ms var(--ease-out);
}

/* Only ever clamped once app.js has set data-open — with no JS the button
   would do nothing, so the paragraph stays whole and the button stays hidden. */
.widget[data-open="false"] .widget__text {
  max-height: calc(var(--widget-lines) * var(--widget-text) * var(--widget-leading));
}

.widget__more { display: none; }
.widget[data-open] .widget__more { display: inline-flex; margin-top: 12px; }

/* Desktop is the only target for now (owner, 2026-08-01: phone comes later).
   These two steps exist purely so the four-across row does not collapse into
   unreadable slivers in the meantime — they are not the phone design. */
@media (max-width: 1080px) {
  .widgets { grid-template-columns: repeat(2, minmax(0, 1fr)); }

  /* The 25% + 5.5px above is column two of FOUR. With two columns, and then
     one, there is no right-hand block of three cards to lay the screen over,
     so it stops being an overlay and becomes the last item in the grid: full
     width, under the cards, which still blur behind it. */
  .widgets__stage {
    position: static;
    grid-column: 1 / -1;
    margin-top: 8px;
    pointer-events: auto;
  }
  .widgets__stage::before { display: none; }
  .widgets[data-showing] { min-height: 0; }
  /* As a grid item it would hold an empty row open while closed. */
  .widgets:not([data-showing]) .widgets__stage { display: none; }
}
@media (max-width: 620px)  { .widgets { grid-template-columns: 1fr; } }

/* =========================================================================
   PARKED — Mission: the "no numbers, no judgment" stance

   #promise was removed from index.html 2026-08-01 on the owner's instruction
   and its markup is parked as a comment there; #articles took its slot. These
   rules are kept so uncommenting it restores the section exactly. Delete both
   halves together, or neither.

   The .claim rule below is the one worth not losing: it records why the swash
   is painted as the element's own background rather than a pseudo-element,
   which is a bug that already cost one round of rendering to find.
   ========================================================================= */

.mission__panel {
  position: relative;
  overflow: hidden;
  padding: clamp(56px, 7vw, 92px) clamp(24px, 5vw, 72px) clamp(52px, 6vw, 80px);
  border-radius: var(--r-xl);
  text-align: center;
  background:
    radial-gradient(80% 90% at 15% 0%,  #e9effb 0%, rgba(233, 239, 251, 0) 60%),
    radial-gradient(80% 90% at 85% 100%, #efe8fb 0%, rgba(239, 232, 251, 0) 60%),
    var(--surface);
  border: 1px solid var(--hairline);
  box-shadow: var(--lift-md);
}

.mission__panel h2 { margin-inline: auto; max-width: 16ch; }
.mission__panel .lead { margin: 22px auto 0; }

.claims {
  display: flex;
  flex-wrap: wrap;
  justify-content: center;
  gap: clamp(14px, 2.4vw, 32px);
  margin-top: clamp(40px, 5vw, 60px);
  padding: 0;
  list-style: none;
}

/* The swash PNGs are the app's own brand asset — the hand-drawn stroke that
   replaced the plain "no numbers" pills there in the 2026-07-21 pass.

   Painted as the element's own background, NOT a z-index:-1 pseudo-element:
   .claim creates no stacking context, so a negative z-index would drop the
   swash behind .mission__panel's background and render it invisible. */
.claim {
  display: grid;
  place-items: center;
  /* Asymmetric on purpose: each swash's thick body sits left of centre with a
     thin tail trailing right, so extra padding-right pulls the label back
     onto the stroke rather than over the tail. */
  padding: 22px 48px 22px 32px;
  background: var(--swash) center / 100% 100% no-repeat;
  font-size: clamp(0.9375rem, 1.3vw, 1.0625rem);
  font-weight: 500;
  color: var(--ink);
}

/* =========================================================================
   Articles — five educational reads (#articles)

   THREE THEN TWO, the owner's pick over a 2×2 or a stacked list. Five items
   do not divide into a row, so the grid is SIX columns and every card spans
   two of them; three cards fill a row exactly, and the fourth is pinned to
   column 2 so the leftover pair sits centred under the three above rather
   than hanging off the left edge.

     ┌────┬────┬────┬────┬────┬────┐   6 columns
     │  1      │  2      │  3      │   each card spans 2
     ├────┼────┼────┼────┼────┼────┤
     │    │  4      │  5      │    │   4 starts at column 2 → pair centres
     └────┴────┴────┴────┴────┴────┘

   The same trick repeats at each breakpoint with a different column count, so
   the odd card is always centred: four columns two-up, then one column. Do not
   replace this with grid-template-columns: repeat(3, 1fr) — that is what
   leaves the last two stranded left.

   TEXT IS SET LEFT here, unlike #helps and #inside above it. These are cards
   you scan and pick from, not statements addressed to you, and a left edge is
   what makes five titles of very different lengths comparable at a glance.
   ========================================================================= */

.articles {
  display: grid;
  grid-template-columns: repeat(6, minmax(0, 1fr));
  gap: 22px;
  margin: clamp(40px, 5vw, 64px) 0 0;
  padding: 0;
  list-style: none;
}

.article { grid-column: span 2; display: flex; }
/* The centring pin — see the diagram above. */
.article:nth-child(4) { grid-column: 2 / span 2; }

.article__link {
  display: flex;
  flex-direction: column;
  width: 100%;
  padding: clamp(18px, 1.8vw, 22px);
  border: 1px solid var(--hairline);
  border-radius: var(--r-lg);
  background: var(--surface);
  box-shadow: var(--lift-sm);
  color: inherit;
  text-decoration: none;
  transition: box-shadow var(--dur) var(--ease-out),
              transform var(--dur) var(--ease-out);
}
.article__link:hover { box-shadow: var(--lift-md); transform: translateY(-2px); }

/* Tighter than .media-panel's own --r-xl: a picture inset inside a card has to
   curve LESS than the card around it, or the two radii fight and the corner
   reads as a mistake. */
.article__media {
  margin-bottom: 18px;
  border-radius: var(--r-md);
}

/* Deliberately not .h3, and matched to .widget__title so the two card rows on
   the page carry the same weight. min-height is two lines' worth: three of
   these five titles fit on one line and two do not, which would otherwise
   leave every teaser at a different height across the row. */
.article__title {
  margin-bottom: 8px;
  font-size: clamp(1.0313rem, 1.2vw, 1.1563rem);
  line-height: 1.3;
  letter-spacing: -0.018em;
  min-height: 2.6em;
}

.article__teaser {
  color: var(--ink-muted);
  font-size: 0.9375rem;
  line-height: 1.62;
}

/* Pushed to the bottom of the card by margin-top: auto, so the cue sits on one
   line across the row however tall the title and teaser above it turned out. */
.article__cue {
  display: inline-flex;
  align-items: center;
  gap: 6px;
  margin-top: auto;
  padding-top: 18px;
  font-size: 0.9375rem;
  font-weight: 500;
  color: var(--primary-deep);
}
.article__cue svg { transition: transform var(--dur) var(--ease-out); }
.article__link:hover .article__cue svg { transform: translateX(3px); }

/* Two-up: four columns, cards still span two, and the odd one out is card 5. */
@media (max-width: 980px) {
  .articles { grid-template-columns: repeat(4, minmax(0, 1fr)); }
  .article:nth-child(4) { grid-column: span 2; }
  .article:nth-child(5) { grid-column: 2 / span 2; }
}

@media (max-width: 640px) {
  .articles { grid-template-columns: 1fr; }
  .article,
  .article:nth-child(4),
  .article:nth-child(5) { grid-column: auto; }
  /* One card per row, so nothing is being lined up with anything — let the
     short titles be short. */
  .article__title { min-height: 0; }
}

@media (prefers-reduced-motion: reduce) {
  .article__link { transition: none; }
  .article__link:hover { transform: none; }
  .article__cue svg,
  .article__link:hover .article__cue svg { transition: none; transform: none; }
}

/* =========================================================================
   Reviews — UNUSED IN THIS BUILD

   PRE-LAUNCH VERSION (2026-08-08): #reviews is not in index.html any more,
   so nothing below matches anything. It is kept rather than deleted so the
   section can be dropped back in whole when there are real stories to put in
   it — the markup is in ..\website\index.html.
   ========================================================================= */

.reviews {
  display: grid;
  grid-template-columns: repeat(3, minmax(0, 1fr));
  gap: 20px;
  margin-top: clamp(40px, 5vw, 60px);
}
@media (max-width: 900px) { .reviews { grid-template-columns: 1fr; } }

/* Solid, not the dashed placeholder box it was until 2026-08-01. The cards
   hold sentences now, and a dashed outline round real type reads as broken
   rather than as reserved. The "these are examples" signal is carried by
   .reviews__note and .review__tag instead — in words, where it cannot be
   mistaken for a style choice. */
.review {
  display: flex;
  flex-direction: column;
  gap: 18px;
  min-height: 236px;
  padding: 30px;
  border: 1px solid var(--hairline);
  border-radius: var(--r-lg);
  background: var(--surface);
  box-shadow: var(--lift-sm);
}

.review__stars { display: flex; gap: 4px; color: var(--primary); }
.review__stars svg { width: 17px; height: 17px; }

/* Not italic, and no quotation marks drawn round it. These are people saying
   plain things; setting them as decorative pull-quotes is what makes a
   testimonial read as advertising copy. */
.review__quote {
  margin: 0;
  font-size: 1rem;
  line-height: 1.66;
  color: var(--ink-soft);
}

.review__by {
  margin-top: auto;
  display: flex;
  align-items: center;
  gap: 12px;
  font-size: 0.875rem;
  color: var(--ink-muted);
}

/* The per-card label. Deliberately NOT hidden from screen readers and
   deliberately not styled down into invisibility — its whole job is to be
   read. Goes when the quote it sits under becomes a real one. */
.review__tag {
  display: inline-block;
  margin-left: 6px;
  padding: 2px 8px;
  border-radius: var(--r-pill);
  border: 1px solid rgba(112, 149, 223, 0.34);
  background: rgba(112, 149, 223, 0.08);
  font-size: 0.6875rem;
  font-weight: 500;
  letter-spacing: 0.06em;
  text-transform: uppercase;
  color: var(--primary-deep);
}

/* The section-level disclosure. This is the piece that keeps the page legal
   while the quotes are written by us — see the long note above #reviews in
   index.html. Sized as a quiet line rather than a warning banner: it has to be
   read, not obeyed. */
.reviews__note {
  display: inline-flex;
  align-items: center;
  gap: 9px;
  margin: 22px auto 0;
  padding: 9px 18px 9px 14px;
  border-radius: var(--r-pill);
  border: 1px solid rgba(112, 149, 223, 0.28);
  background: rgba(112, 149, 223, 0.07);
  font-size: 0.875rem;
  line-height: 1.5;
  color: var(--ink-soft);
  text-align: left;
}
.reviews__note svg { flex: none; width: 17px; height: 17px; color: var(--primary-deep); }
.review__avatar {
  width: 34px; height: 34px;
  border-radius: 50%;
  background: linear-gradient(160deg, #ddd8f4, #d6e4f7);
  flex: none;
}

/* =========================================================================
   Get the app — the download panel above the FAQ

   The page's one piece of hard selling, so it is the only block that looks
   like a panel rather than like the page: a raised card on the two brand
   radials, where every other section sits flat on --page. That contrast is
   what makes it read as a call to action instead of another paragraph.

   It sits inside the 1160px .shell. The widget row in #inside is the ONLY
   thing on this site allowed to break out of it — see that block's note.

   The stats row is a rule of three: two figures that behave like every other
   app's proof, then a zero that says the opposite. If the rating and the
   download count ever go, the zero stays; the trio still works as a pair, and
   it is the only one of the three that is true before launch.
   ========================================================================= */

.getapp {
  position: relative;
  overflow: hidden;
  /* PRE-LAUNCH VERSION: the bottom value was clamp(30px, 3.4vw, 46px), tuned
     against the .getapp__stats row that used to close the panel. With the
     figures gone the last thing in the panel is the small muted note, and
     80px of air above the headline against 45px below that line read
     bottom-tight - measured off a 1440px render. Raised to sit level with the
     top. Restore the original value with the stats row (..\website). */
  padding: clamp(40px, 5.4vw, 72px) clamp(24px, 4.4vw, 64px) clamp(38px, 5vw, 66px);
  border: 1px solid var(--hairline);
  border-radius: var(--r-xl);
  text-align: center;
  /* Warmer and more saturated than .media-panel's wash — this one is meant to
     lift off the page, not to sit quietly waiting for a photograph. */
  background:
    radial-gradient(78% 92% at 12% 6%, #e2ebfb 0%, rgba(226, 235, 251, 0) 64%),
    radial-gradient(72% 90% at 90% 96%, #ece4fa 0%, rgba(236, 228, 250, 0) 66%),
    var(--surface);
  box-shadow: var(--lift-lg);
}

/* Narrower than the .h2 default: the headline is the hook here, and a hook
   that runs the full width of the panel stops being one. */
.getapp__title {
  max-width: 21ch;
  margin-inline: auto;
}

.getapp__lead {
  max-width: 54ch;
  margin: 20px auto 0;
  /* Centred and three lines long: without this the last line came down to the
     single word "day." */
  text-wrap: balance;
}

.getapp .btn-row { margin-top: clamp(26px, 3vw, 34px); }

.getapp__note {
  margin-top: 16px;
  font-size: 0.875rem;
  color: var(--ink-muted);
}

.getapp__stats {
  display: grid;
  grid-template-columns: repeat(3, minmax(0, 1fr));
  margin: clamp(34px, 4.2vw, 54px) 0 0;
  padding: clamp(26px, 3vw, 38px) 0 0;
  border-top: 1px solid var(--hairline);
  list-style: none;
}

/* Three fixed rows, explicitly assigned, so the two stats WITHOUT stars still
   reserve the star row and all three figures and labels sit on the same two
   lines. Left to flow, the starred column pushed itself 23px out of step with
   its neighbours. */
.getapp__stat {
  display: grid;
  grid-template-rows: 16px auto auto;
  justify-items: center;
  gap: 7px;
}
.getapp__stars  { grid-row: 1; }
.getapp__figure { grid-row: 2; }
.getapp__label  { grid-row: 3; }
/* Hairlines rather than a gap: the three figures are one row of proof, and a
   gap alone let them drift apart at wide viewports. */
.getapp__stat + .getapp__stat { border-left: 1px solid var(--hairline); }

.getapp__figure {
  font-size: clamp(1.6rem, 2.5vw, 2.05rem);
  font-weight: 600;
  line-height: 1.1;
  letter-spacing: -0.028em;
}

.getapp__label {
  font-size: 0.8125rem;
  font-weight: 500;
  letter-spacing: 0.1em;
  text-transform: uppercase;
  color: var(--ink-muted);
}

/* Brand blue, not the conventional gold. Gold would read as a screenshot of a
   store listing; these are the site's own stars and the palette has no gold in
   it. Solid rather than the translucent fill .review__stars uses — those are
   admitted placeholders, this is a figure. */
.getapp__stars {
  display: flex;
  gap: 3px;
  color: var(--primary);
}
.getapp__stars svg { width: 16px; height: 16px; }

@media (max-width: 620px) {
  .getapp__stats { grid-template-columns: 1fr; gap: 26px; }
  .getapp__stat + .getapp__stat { border-left: 0; }
}

/* =========================================================================
   LAUNCH SIGN-UP — the only form on the site (2026-08-14)

   Sits between #get-app and the FAQ band, which is why it is a SMALL card and
   not another full-width panel: #get-app directly above is the page's one
   raised widget on --lift-lg, and a second one the same size would read as two
   competing offers. This is 480px, --lift-md, and quiet.

   Its structure is the owner's mock, its tokens are this site's. Three things
   the mock could not tell us and that were measured or reasoned here:

   · The inputs are 1rem, not the 0.9375rem the rest of the small copy uses.
     iOS Safari zooms the whole page when a focused field is under 16px, and
     the zoom does not come back out. This is the one place on the site where
     the type size is decided by a browser behaviour rather than the scale.

   · The global :focus-visible rule sets border-radius: 6px. On a 14px field
     that visibly SQUARES the input for as long as it is focused, so the radius
     is restated here — the same override .faq__q needs, for the same reason.

   · The status line is styled by [data-state] and says which state it is in
     with WORDS, not colour alone. #a12d2d is 6.4:1 on white and #1f6b4f 5.6:1,
     so the colour is legible, but it is never the only signal.
   ========================================================================= */

/* #get-app already closes with 66px of its own padding and the FAQ band opens
   below; a second full --section-y on top of that pushed the card away from
   the panel it belongs to. */
.section--tight-top { padding-top: clamp(26px, 3.2vw, 44px); }

.signup {
  max-width: 480px;
  margin-inline: auto;
  padding: clamp(28px, 3.6vw, 40px) clamp(22px, 3vw, 34px) clamp(24px, 3vw, 32px);
  background: var(--surface);
  border: 1px solid var(--hairline);
  border-radius: var(--r-lg);
  box-shadow: var(--lift-md);
}

.signup__badge {
  display: flex;
  align-items: center;
  justify-content: center;
  width: 44px;
  height: 44px;
  margin-bottom: 18px;
  border-radius: var(--r-sm);
  /* --primary at 12% over white — the same family as .getapp's radials, one
     step quieter so the badge never competes with the submit button. */
  background: #e7edfb;
  color: var(--primary-deep);
}
.signup__badge svg { width: 22px; height: 22px; }

.signup__title {
  margin: 0 0 6px;
  font-size: clamp(1.35rem, 2.2vw, 1.6rem);
  font-weight: 600;
  line-height: 1.2;
  letter-spacing: -0.022em;
}

.signup__lead {
  margin: 0 0 22px;
  font-size: 0.9375rem;
  color: var(--ink-muted);
}

.field { margin-bottom: 14px; }

.field__label {
  display: block;
  margin-bottom: 6px;
  font-size: 0.8125rem;
  font-weight: 500;
  color: var(--ink-soft);
}
.field__opt { font-weight: 400; color: var(--ink-muted); }

.field__input {
  width: 100%;
  min-height: 48px;              /* ≥44px touch target, same as .btn */
  padding: 12px 16px;
  font: inherit;
  font-size: 1rem;               /* see the note at the top of this block */
  color: var(--ink);
  background: var(--surface);
  border: 1px solid rgba(0, 0, 0, 0.14);
  border-radius: var(--r-sm);
  transition: border-color var(--dur) var(--ease-out);
}
.field__input::placeholder { color: #8b8b8b; }
.field__input:hover { border-color: rgba(0, 0, 0, 0.22); }
.field__input:focus { border-color: var(--primary-deep); }
.field__input:focus-visible { border-radius: var(--r-sm); outline-offset: 2px; }

.signup__submit { width: 100%; margin-top: 6px; }
.signup__submit svg { transition: transform var(--dur) var(--ease-out); }
.signup__submit:hover svg { transform: translateX(3px); }
.signup__submit[disabled] {
  opacity: 0.55;
  box-shadow: none;
  cursor: default;
}
.signup__submit[disabled]:hover { transform: none; background: var(--primary-deep); }

.signup__status {
  margin: 12px 0 0;
  font-size: 0.875rem;
  text-align: center;
  color: var(--ink-soft);
}
.signup__status:empty { display: none; }
.signup__status[data-state="ok"]    { color: #1f6b4f; }
.signup__status[data-state="error"] { color: #a12d2d; }

.signup__fine {
  margin: 12px 0 0;
  font-size: 0.75rem;
  text-align: center;
  color: var(--ink-muted);
}

/* The honeypot. Not .sr-only: a screen reader user is a person and must not be
   offered a field that marks them as a bot for filling it in. */
.signup__trap { display: none; }

/* =========================================================================
   FAQ — five questions on a tinted band, each row its own card

   Rebuilt 2026-08-10 to the owner's page-structure reference. Three things
   changed and they hold together: the section is a FULL-BLEED tint instead of
   sitting on --page, the rows are SEPARATE WHITE CARDS instead of one list cut
   by hairlines, and the band ends in a WAVE that hands over to the white
   footer. Copy is untouched — this is the same five questions and the same
   closing line, restyled.

   #d2c2ef is the owner's own value, supplied with the reference. It is a
   website-only colour (the app has no such tint) and it is the reason for
   every ink change below: measured against it, --ink-muted is 3.2:1 and
   --primary-deep 2.9:1, both under AA's 4.5. So the two things that sit
   DIRECTLY on the band — the closing note and its link — take darker inks,
   and so does the focus ring, which needs 3:1 as a non-text control.
   Everything inside a card is on white and keeps the inks it always had.
   ========================================================================= */

#faq {
  --faq-band: #d2c2ef;

  /* --primary-deep darkened until it clears AA on the band: 5.5:1 against
     #d2c2ef, where --primary-deep itself is 2.9:1. Kept local to this section
     rather than added to :root — nothing else on the site puts a link on a
     tint this strong, and a token in :root would invite it. */
  --faq-link: #2c4682;

  /* Height of the wave at the foot of the band, and the extra bottom padding
     the section takes so the closing line does not sit inside the curve.

     FLUID, because the wave is a single curve and its whole character is how
     shallow it is. In the reference it drops 26px across a 558px frame — 4.7%
     of the width — and a fixed px height loses that at both ends: 64px reads
     as a gentle sweep at 1440 and as a deep scoop at 560. 5.7vw holds the
     proportion; the floor keeps it from vanishing on a phone and the ceiling
     stops it eating the footer on an ultrawide, where a wave 100px tall would
     be doing more talking than the footer under it.

     5.7 and not 4.7: the curve does not fill its box — it runs y=6..58 of 64,
     so the drawn drop is 81% of whatever this says. At 4.6vw the render
     measured 3.75% of the width against the reference's 4.7%. */
  --faq-wave: clamp(34px, 5.7vw, 96px);

  /* The TOP edge got a wave too (owner, 2026-08-10: "the top line of the
     illustration on faq section also make a little wavy very little so it's
     not so strait"). "Very little" is the whole instruction, so this is a
     THIRD of the bottom's height — 1.5% of the width against 4.65% — and the
     two are not a matching pair: the top curve is the bottom one mirrored, so
     the band is never bounded by two parallel edges. */
  --faq-wave-top: clamp(12px, 1.9vw, 34px);

  position: relative;
  background: var(--faq-band);
  padding-bottom: calc(var(--section-y) + var(--faq-wave));
}

/* Paints --page over the top of the band, the way ::after paints the footer's
   white over the bottom. It needs no extra padding: the wave is ~27px at 1440
   and the section's own padding-top is 72-116px, so the headline never enters
   it. Check that again if either number moves — a positioned pseudo-element
   paints ABOVE the section's own text, so an overlap here would cover the
   heading rather than sit behind it. */
#faq::before {
  content: "";
  position: absolute;
  left: 0;
  right: 0;
  top: -1px;
  height: calc(var(--faq-wave-top) + 1px);
  pointer-events: none;
  background: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 1440 64' preserveAspectRatio='none'%3E%3Cpath d='M0,0 L1440,0 L1440,6 C1320,8 1110,58 893,58 C640,58 440,30 310,30 C240,30 200,38 144,38 C80,38 50,19 0,19 Z' fill='%23fafaf8'/%3E%3C/svg%3E") center / 100% 100% no-repeat;
}

/* The wave is WHITE and it is the footer's colour, not a decoration in its own
   right: it paints the top of the footer's surface over the bottom of the
   band, so the two meet on a curve instead of a line. preserveAspectRatio
   'none' plus a 100% 100% background-size means the one curve stretches to any
   viewport rather than repeating, so the shape reads the same at 520px and at
   2560px — it just gets broader.

   Sits 1px below the band and is 1px taller than it needs to be: at fractional
   zoom the two boxes can round apart and leave a lavender sliver along the top
   of the footer. The overlap costs nothing, since what it overlaps is the same
   white. index.html's footer drops its own border-top for this section's sake
   (.site-footer--after-band) — a hairline across the bottom of a wave reads as
   a mistake.

   ---- the shape, and why it is ONE wave ---------------------------------
   Built twice. The first pass was six repeating scallops, which is what a
   "wave divider" usually is and is not what the owner's reference draws — they
   said so: "make the wave like one". Their mock has a SINGLE shallow curve
   across the whole page, and it is asymmetric, which is what stops it reading
   as a mathematical decoration.

   The path below is traced from it. Sampled every 16px along the boundary in
   the reference (lavender above, cream below — found per column as the first
   row where R >= B) and normalised against the 558px page frame it is drawn
   in:

     across   0%    21%   38%   55%   76%   90%   100%
     depth    top   .69   1.00  .85   .46   .62   .31

   So: one long descent to a broad flat trough at 38%, a slower rise to a crest
   around three quarters, one small secondary dip, and up again at the right
   edge. The total drop is 26px over 558 — 4.7% of the width, which is where
   --faq-wave's 4.6vw comes from.

   Every cubic here arrives and leaves flat (both control points level with the
   endpoint they belong to) so the joins do not kink. That flatness is also the
   trap the first pass fell into: a cubic whose two controls disagree cancels
   itself out, and a +/-26 pair drew a 14px wave where 52 was intended. Measure
   the render, not the numbers in the file. */
#faq::after {
  content: "";
  position: absolute;
  left: 0;
  right: 0;
  bottom: -1px;
  height: calc(var(--faq-wave) + 1px);
  pointer-events: none;
  background: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 1440 64' preserveAspectRatio='none'%3E%3Cpath d='M0,64 L0,6 C120,8 330,58 547,58 C800,58 1000,30 1130,30 C1200,30 1240,38 1296,38 C1360,38 1390,19 1440,19 L1440,64 Z' fill='%23ffffff'/%3E%3C/svg%3E") center / 100% 100% no-repeat;
}

/* Cards, so `gap` and not a border between rows. 10px is deliberately small:
   the rows are one list and should still read as one, and a card that is 68px
   tall separated by 10px is a stack, where 24px would be five islands. */
.faq {
  display: grid;
  gap: 10px;
  max-width: 760px;
  margin: clamp(40px, 5vw, 56px) auto 0;
}

/* margin-top resets the .disclosure default — a FAQ row is also a disclosure.
   NO overflow: hidden, tempting as it is for the rounded corners: the button
   fills the card and its focus ring is drawn 3px OUTSIDE it, so clipping the
   card clips the ring. Nothing inside the card carries a background of its
   own, so there is nothing for the corners to cut. */
/* TRANSLUCENT, not white, at the owner's ask on 2026-08-10: "make the
   questions in the FAQ section a little transparent". 0.72 is the number that
   ask resolves to, and it is bounded on both sides:

   - Above it the effect stops reading. At 0.85 the card lands on #F8F6FD,
     seven points off white in the channel that carries the tint — that is an
     off-white card, not a transparent one.
   - Below it the ANSWERS fail AA. They are --ink-muted (#6b6b6b) and they sit
     on this card, not on white. Computed against the band, the ratio for that
     ink goes 4.86:1 at 0.80, 4.67:1 at 0.72, 4.52:1 at 0.65 and 4.42:1 at
     0.60 — so 0.65 is the true floor and 0.72 keeps a margin under it.

   The questions themselves are black (18.4:1 here) and were never in danger.
   It is the ANSWER text that sets this floor, and it only appears when a row
   is open — which is why the limit is invisible in a screenshot of the closed
   list. Re-measure the answer, not the question, before lowering this. */
.faq__item {
  --faq-card: rgba(255, 255, 255, 0.72);

  margin-top: 0;
  border-radius: var(--r-sm);
  background: var(--faq-card);
  border: 1px solid rgba(112, 149, 223, 0.22);
  transition: border-color var(--dur) var(--ease-out);
}
.faq__item:hover { border-color: rgba(112, 149, 223, 0.5); }

/* The open row keeps the hover border so it stays marked once the pointer
   leaves. :has() only, on purpose — aria-expanded lives on the button and
   app.js sets nothing on the card. Where it is unsupported the row simply
   looks closed while reading open, which costs nothing: the panel below it and
   the sign at its right both already say so. */
.faq__item:has(.faq__q[aria-expanded="true"]) {
  border-color: rgba(112, 149, 223, 0.5);
}

.faq__item h3 { margin: 0; font-size: inherit; letter-spacing: normal; }

.faq__q {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 20px;
  width: 100%;
  min-height: 68px;
  padding: 20px 24px;
  background: none;
  border: 0;
  border-radius: var(--r-sm);
  font: inherit;
  font-size: clamp(1rem, 1.5vw, 1.0938rem);
  font-weight: 500;
  letter-spacing: -0.012em;
  color: var(--ink);
  text-align: left;
  cursor: pointer;
  transition: color var(--dur) var(--ease-out);
}
.faq__q:hover { color: var(--primary-deep); }

/* Two overrides of the global :focus-visible rule, both because the button is
   now card-shaped: its 6px radius would round off a 14px card for as long as
   it is focused, and its --primary-deep ring falls to 2.9:1 where it is drawn,
   which is on the band and not on the card. */
#faq :focus-visible { outline-color: var(--faq-link); }
.faq__q:focus-visible { border-radius: var(--r-sm); }

.faq__sign {
  position: relative;
  flex: none;
  width: 22px; height: 22px;
  color: var(--primary-deep);
}
.faq__sign::before,
.faq__sign::after {
  content: "";
  position: absolute;
  inset: 50% 0 auto;
  height: 1.5px;
  border-radius: 2px;
  background: currentColor;
  translate: 0 -50%;
  transition: transform 300ms var(--ease-out), opacity 200ms var(--ease-out);
}
.faq__sign::after { transform: rotate(90deg); }
.faq__q[aria-expanded="true"] .faq__sign::after { transform: rotate(0deg); opacity: 0; }

/* A written answer. The shared .prose rule already pads the first paragraph
   away from the question above it; this adds the matching gap underneath, or
   the last line sits flush against the bottom of the card. The side padding
   is the question button's own 24px repeated — the answer has to line up with
   the question it belongs to, and the button's padding cannot reach it.
   Slightly narrower measure than body prose so a one-line answer doesn't run
   the full card width. */
.faq .prose { padding: 0 24px 24px; }
.faq .prose p { font-size: 0.9688rem; line-height: 1.74; max-width: 62ch; }

/* The reserved answer slot — still used by any question whose copy hasn't
   landed. Deliberately empty and visibly so: it marks the space rather than
   faking it. */
.faq__slot {
  display: flex;
  align-items: center;
  gap: 10px;
  margin: 0 4px 22px;
  padding: 20px 22px;
  border: 1px dashed rgba(112, 149, 223, 0.36);
  border-radius: var(--r-md);
  background: rgba(112, 149, 223, 0.045);
  font-size: 0.9375rem;
  color: var(--ink-muted);
}
.faq__slot svg { flex: none; width: 17px; height: 17px; color: var(--primary); }

/* Sits directly on the band, so both inks are the band's and not the page's:
   --ink-muted measures 3.2:1 on #d2c2ef and --primary-deep 2.9:1. --ink-soft
   is 6.9:1 and --faq-link 5.5:1. Do not put these two back to the site
   defaults without re-measuring against whatever the band is by then. */
.faq__foot {
  margin-top: 34px;
  text-align: center;
  font-size: 0.9375rem;
  color: var(--ink-soft);
}
.faq__foot a { color: var(--faq-link); text-underline-offset: 3px; }

/* The wave needs nothing here — --faq-wave is fluid and its own floor handles
   the phone. Only the card's side padding comes in, so a long question keeps
   its two lines instead of finding a third. */
@media (max-width: 620px) {
  .faq__q { padding: 18px 18px; }
  .faq .prose { padding: 0 18px 22px; }
}

/* The .closing / .cta rounded panel that sat between the FAQ and the footer was
   removed 2026-07-31 — see the note in index.html where the section was. Its
   headline is now .quiet__hinge at the end of #you. --lift-lg is left in :root
   even though nothing uses it, so the panel can be rebuilt as it was if the
   owner wants it somewhere else on the page.

   The FAQ now runs straight into the footer, and the last thing before it is
   .faq__foot ("Didn't find your answer?") — that is the page's closing offer. */

/* =========================================================================
   Site footer — full-bleed, white, square-cornered

   The owner asked for the composition to stay but the rounded container to
   go: "put it free with white background and rectangular like the websites
   do". So the footer spans the full viewport, is pure white against the page's
   warmer --page ground, and takes no radius, no shadow and no inset.

   It also carries id="contact" now: with the CTA panel gone, this is where the
   nav's "Get launch news" button lands.
   ========================================================================= */

.site-footer {
  /* Absorbs the body column's slack, so on a page shorter than the viewport
     the footer sits at the bottom instead of leaving --page showing under it.
     Put here rather than `flex: 1` on main, so the gap opens directly above
     the footer instead of stretching the content area. */
  margin-top: auto;
  background: var(--surface);
  border-top: 1px solid var(--divider);
  /* For the corner picture below: it is positioned against this box, must not
     spill past the bottom of the page, and must stay under the text. */
  position: relative;
  overflow: hidden;
  isolation: isolate;
}

/* The home page hands over from the FAQ band on a wave, and that wave is
   already this surface — see the note on the footer tag in index.html. The
   hairline is a separator between two flat colours and there is nothing flat
   to separate here. */
.site-footer--after-band { border-top: 0; }

/* ---- the floral corner (owner, 2026-08-10) ------------------------------
   Bottom right, flush with both edges — "fully touching the bottom of the
   screen", and the footer is the last thing on the page, so its bottom edge
   IS that. See the note in index.html for why it is a JPEG and not a PNG.

   Width is capped in px as well as vw, and the cap is a MEASURED number, not
   a taste one. 420px puts the picture's left edge at x=1020 on a 1440
   viewport. The disclaimer, once pulled left (see .foot__legal), ends around
   x=1040 — inside the first 5% of the picture, where the mask below has
   barely started to let it through. Sampled off the render there, the ground
   under that last word stays above 4.5:1 for --ink-muted.

   480px was tried first and measured 1.9:1 under the same word. Widen this
   and you have to re-measure both that line and the Legal column above it. */
.site-footer__bloom {
  position: absolute;
  right: 0;
  bottom: 0;
  z-index: 0;
  width: clamp(240px, 30vw, 420px);
  height: auto;
  pointer-events: none;
  user-select: none;

  /* The source fades to black — i.e. to the footer's white — on its top and
     left, but only in its upper half: along the bottom edge the leaves run all
     the way out to x=0, so the plain rectangle draws a hard vertical seam down
     the middle of the footer. That seam is visible, and it is the one thing
     that would make this read as a pasted-in picture.

     So the two edges that meet the footer get faded in CSS instead. Two
     gradients intersected, not one: fading only the left leaves the top-right
     sprigs cut off square against the Legal column. -webkit-* first for
     Safari, which still wants the prefixed property and the old composite
     keyword. */
  -webkit-mask-image: linear-gradient(to right, transparent 0%, #000 40%),
                      linear-gradient(to bottom, transparent 0%, #000 26%);
  -webkit-mask-composite: source-in;
          mask-image: linear-gradient(to right, transparent 0%, #000 40%),
                      linear-gradient(to bottom, transparent 0%, #000 26%);
          mask-composite: intersect;
}

/* On a phone the desktop clamp's 240px floor is 62% of the screen. This is
   the width .foot__legal's bottom padding is calculated from — the two
   numbers are one decision, so they move together or not at all. */
@media (max-width: 820px) {
  .site-footer__bloom { width: min(58vw, 280px); }
}

/* Everything the footer actually says rides above the picture. Both of these,
   not just the wrapper: .site-footer__legal is a sibling of the .shell, not
   inside it. */
.site-footer > .shell,
.site-footer__legal {
  position: relative;
  z-index: 1;
}

.foot {
  display: grid;
  grid-template-columns: minmax(0, 1.15fr) minmax(0, 2fr);
  gap: clamp(36px, 6vw, 72px);
  /* Block axis only — the horizontal gutter now comes from .shell, so the
     footer lines up with every other section on the page. */
  padding-block: clamp(48px, 5vw, 68px) clamp(32px, 3vw, 44px);
}
@media (max-width: 820px) { .foot { grid-template-columns: 1fr; } }

.foot__brand img { width: 132px; }
.foot__brand p {
  margin-top: 18px;
  max-width: 34ch;
  font-size: 0.9375rem;
  color: var(--ink-muted);
}
.foot__social { display: flex; gap: 10px; margin-top: 22px; }
.foot__social a {
  display: grid;
  place-items: center;
  width: 44px; height: 44px;
  border: 1px solid var(--hairline);
  border-radius: 50%;
  background: var(--surface);
  color: var(--ink-soft);
  transition: border-color var(--dur) var(--ease-out),
              color var(--dur) var(--ease-out),
              transform var(--dur) var(--ease-out);
}
.foot__social a:hover { color: var(--primary-deep); border-color: rgba(112,149,223,.5); transform: translateY(-2px); }
.foot__social svg { width: 19px; height: 19px; }

.foot__cols {
  display: grid;
  grid-template-columns: repeat(3, minmax(0, 1fr));
  gap: 28px;
}
@media (max-width: 520px) { .foot__cols { grid-template-columns: repeat(2, 1fr); } }

.foot__cols h3 {
  font-size: 0.8125rem;
  font-weight: 600;
  letter-spacing: 0.1em;
  text-transform: uppercase;
  color: var(--ink);
  margin-bottom: 6px;
}
.foot__cols ul { margin: 0; padding: 0; list-style: none; }
.foot__cols a {
  display: inline-flex;
  align-items: center;
  min-height: 40px;
  font-size: 0.9375rem;
  color: var(--ink-muted);
  text-decoration: none;
  transition: color var(--dur) var(--ease-out);
}
.foot__cols a:hover { color: var(--ink); }

.foot__tag {
  display: inline-flex;
  align-items: center;
  min-height: 40px;
  font-size: 0.9375rem;
  color: var(--ink-muted);
  opacity: 0.66;
}

.site-footer__legal { border-top: 1px solid var(--divider); }

/* ---- the copyright and the not-a-doctor line ----------------------------
   These two are the last thing on the page and they are the two lines that
   have to be readable whatever else happens, so the corner picture gives way
   to them and not the other way round.

   STACKED, one under the other, since 2026-08-10. They were a single row
   pushed apart with space-between, which was fine until the floral corner
   arrived: the disclaimer's tail then ran into the flowers. Pulling both
   lines left fixed it at 1440 and NOT at 1152, because the picture is 30vw
   and the text column stops growing at 1160px — so past a certain width they
   walk towards each other. Measured at 1152, "or treat any condition" sat on
   the petals. The owner read that as the sentence having been deleted, which
   is exactly how unreadable it was.

   Stacking is the only version that holds at every width, and it is what the
   owner asked for: the two lines directly under the footer.

   `padding-right` is the second half of it — it reserves the corner's own
   width, so no line can reach into the picture's column even when it wraps.
   It over-reserves slightly (the shell already stops short of the viewport
   edge), which is the safe direction. Keep it >= .site-footer__bloom's width
   or this comes back. */
.foot__legal {
  display: flex;
  flex-direction: column;
  align-items: flex-start;
  gap: 10px;
  padding-block: 22px 26px;
  padding-right: min(30vw, 420px);
  font-size: 0.8438rem;
  color: var(--ink-muted);
}
/* No max-width: the disclaimer is meant to sit on ONE line at desktop widths.
   A 60ch cap broke it across two even when there was room to spare. It still
   wraps on its own once the row genuinely runs out of width — and now that it
   has a line to itself, it has more room than it ever did in the old row. */
.foot__legal p { max-width: none; }

/* Below 820 the picture is small in px but LARGE against the screen — 240px
   of a 390px phone — so no amount of reserved width would leave a column to
   read in. It is short there, though, so the room comes out of the bottom
   instead: enough padding under the last line to clear the picture's height,
   which is 2:3 of its width. The two are tied together; change one and the
   other has to move with it. */
@media (max-width: 820px) {
  .foot__legal {
    padding-right: 0;
    padding-bottom: calc(min(58vw, 280px) * 0.667 + 20px);
  }
}

/* =========================================================================
   Legal pages (privacy.html / terms.html) — structure only, no text yet
   ========================================================================= */

.doc { padding: 152px var(--gutter) 0; }

.doc__head {
  max-width: 760px;
  margin-inline: auto;
  text-align: center;
}
.doc__head .eyebrow { margin-bottom: 16px; }
.doc__head .lead { margin: 20px auto 0; }

.doc__body {
  max-width: 760px;
  margin: clamp(44px, 5vw, 64px) auto 0;
  padding-bottom: var(--section-y);
}

/* One reserved block per required topic — heading present, body empty. */
.doc__block + .doc__block { margin-top: 18px; }

.doc__block h2 {
  font-size: 1.0625rem;
  font-weight: 600;
  letter-spacing: -0.012em;
  margin-bottom: 14px;
}

.doc__slot {
  display: flex;
  align-items: center;
  gap: 10px;
  min-height: 96px;
  padding: 22px 24px;
  border: 1px dashed rgba(112, 149, 223, 0.36);
  border-radius: var(--r-md);
  background: rgba(112, 149, 223, 0.045);
  font-size: 0.9375rem;
  color: var(--ink-muted);
}
.doc__slot svg { flex: none; width: 17px; height: 17px; color: var(--primary); }

.doc__notice {
  display: flex;
  gap: 14px;
  max-width: 760px;
  margin: 0 auto;
  padding: 22px 26px;
  border: 1px solid rgba(112, 149, 223, 0.28);
  border-radius: var(--r-md);
  background: rgba(112, 149, 223, 0.06);
  font-size: 0.9375rem;
  line-height: 1.66;
  color: var(--ink-soft);
}
.doc__notice svg { flex: none; width: 20px; height: 20px; margin-top: 2px; color: var(--primary-deep); }

/* --- Article pages ------------------------------------------------------
   The five article-*.html pages reuse .doc wholesale — same 152px clearance
   under the nav pill, same centred head, same 760px reading column, same
   reserved-slot language. Only these two pieces are theirs: the picture
   between the head and the body, and the way back.

   .doc is a stylesheet name, not a claim that the page is a legal document —
   the same call already made for .mission__panel. Reusing it is deliberate:
   an article and a policy are both one column of prose under a title, and
   forking a near-identical layout is how the two quietly drift apart.        */

.read__media {
  max-width: 760px;
  margin: clamp(32px, 4vw, 44px) auto 0;
}

/* Centre the reading column inside .doc__body. .prose caps each paragraph at
   --measure (62ch ≈ 625px) while .doc__body is 760px wide, so without this the
   text sits hard left with ~135px of nothing down its right edge — under a
   centred title and a full-width picture, which is exactly where the eye
   notices it. Caught on the render; invisible on the legal pages, where the
   reserved slots are full-bleed and there is no long prose to give it away. */
.doc__body .prose {
  max-width: var(--measure);
  margin-inline: auto;
}

/* Two lines of lead under a big title strand their last word often enough to
   be worth balancing. Scoped to the head so body prose keeps normal ragging. */
.doc__head .lead { text-wrap: balance; }

/* The closing "Gentle reflection" of an article — added 2026-08-14 with the
   app's own five articles.

   Every piece in the app's library ends the same way: the words "Gentle
   reflection:" and one question. In the app those are two ordinary
   paragraphs, because the reader there is one 393dp column and a paragraph
   already reads as its own block. At this page's 625px measure they do not —
   a bare label on its own line reads as a paragraph that lost its body. So
   the pair is set as one quiet block instead, and the markup keeps them as
   the two pieces they are.

   Restrained on purpose: a tint, a left rule and nothing else. No card, no
   shadow, no radius beyond the site's smallest — this closes a reading
   column, it does not compete with the picture at the top of it.

   Contrast, measured on --page (#fafaf8), which is what shows through the 6%
   primary wash: the label's --primary-deep is 5.4:1, the question's --ink is
   19.5:1. The label is not the only signal either — it says what it is in
   words, so the colour carries no meaning of its own.

   It sits INSIDE .prose so it inherits the centred reading column; its own
   max-width has to be none, or .prose p's --measure cap would inset it
   again inside a block that is already at the measure.                       */
.reflection {
  margin-top: 2em;
  padding: 20px 24px;
  border-left: 2px solid var(--primary);
  border-radius: 0 var(--r-sm) var(--r-sm) 0;
  background: rgba(112, 149, 223, 0.06);
}
.reflection p { max-width: none; }
.reflection__label {
  margin: 0;
  color: var(--primary-deep);
  font-size: 0.875rem;
  font-weight: 500;
  letter-spacing: 0.01em;
}
.reflection__question {
  color: var(--ink);
  font-size: 1.0313rem;
  line-height: 1.62;
}
/* .prose pads its FIRST paragraph and spaces every p after another p. Both
   would fire inside here — the label would take a 22px top pad on a block
   that has just been padded, and the question the full 1.1em paragraph gap
   where these two are one statement in two parts. */
.reflection .reflection__label:first-child { padding-top: 0; }
.reflection > p + p { margin-top: 8px; }

@media (max-width: 640px) {
  .reflection { padding: 18px 20px; }
}

.read__back {
  margin-top: clamp(36px, 4.5vw, 52px);
  padding-top: 22px;
  border-top: 1px solid var(--divider);
}

/* =========================================================================
   Legal pages — long-form policy text
   -------------------------------------------------------------------------
   privacy.html and terms.html stopped being reserved-slot scaffolds and now
   carry the real documents, whose source of truth is ..\legal\*.md. That text
   needs things .prose never had to style: headings inside a section, lists,
   tables, and three kinds of callout.

   The measure deliberately splits. Running text stays at --measure (62ch) like
   every other page, but a table gets the full 760px of .doc__body, because the
   data tables here run to four columns and squeezing them into 62ch turns
   every cell into a two-word column. Tables scroll inside their own wrapper so
   the page body never moves sideways.
   ========================================================================= */

.doc__meta {
  display: flex;
  flex-wrap: wrap;
  gap: 8px 22px;
  max-width: var(--measure);
  margin: 26px auto 0;
  padding-bottom: 20px;
  border-bottom: 1px solid var(--divider);
  font-size: 0.8125rem;
  color: var(--ink-muted);
}

/* Why a page that is otherwise published still shows one unfilled field.
   Added 2026-08-15 with the removal of the .doc__notice banner, which used to
   carry this explanation; the effective date is set on the day the app goes
   live. Full row of its own inside the meta line, not a fourth chip, because
   it is a sentence and the others are labels. DELETE THIS LINE, the
   .legal-token span and the token itself in the same edit on launch day. */
.doc__meta-note { flex: 1 0 100%; }

/* An index, not decoration: these documents run to 15 sections and the reason
   someone opens one is usually to find a single answer. */
.doc__toc {
  max-width: var(--measure);
  margin: 30px auto 0;
  padding: 22px 26px;
  border-radius: var(--r-md);
  background: var(--surface-muted);
}
.doc__toc-title {
  margin-bottom: 12px;
  font-size: 0.75rem;
  font-weight: 600;
  letter-spacing: 0.08em;
  text-transform: uppercase;
  color: var(--ink-muted);
}
.doc__toc ol {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(210px, 1fr));
  gap: 7px 26px;
  margin: 0;
  padding-left: 1.15em;
  font-size: 0.9063rem;
}
.doc__toc li { color: var(--ink-muted); }
.doc__toc a { color: var(--ink-soft); text-decoration: none; }
.doc__toc a:hover { color: var(--primary-deep); text-decoration: underline; }

.doc__block h3 {
  max-width: var(--measure);
  margin: 26px auto 12px;
  font-size: 0.9688rem;
  font-weight: 600;
  letter-spacing: -0.008em;
}

.prose ul {
  max-width: var(--measure);
  margin: 1.1em auto 0;
  padding-left: 1.25em;
  font-size: 0.9688rem;
  line-height: 1.74;
  color: var(--ink-soft);
}
.prose li + li { margin-top: 0.62em; }
.prose li::marker { color: var(--primary); }
.prose a { color: var(--primary-deep); }

.prose__table {
  max-width: 760px;
  margin: 22px auto 0;
  overflow-x: auto;
  border: 1px solid var(--divider);
  border-radius: var(--r-sm);
  background: var(--surface);
}
.prose__table table {
  width: 100%;
  border-collapse: collapse;
  font-size: 0.875rem;
  line-height: 1.6;
}
.prose__table th,
.prose__table td {
  padding: 13px 16px;
  text-align: left;
  vertical-align: top;
}
.prose__table th {
  font-size: 0.75rem;
  font-weight: 600;
  letter-spacing: 0.05em;
  text-transform: uppercase;
  color: var(--ink-muted);
  background: var(--surface-muted);
  white-space: nowrap;
}
.prose__table tbody tr + tr td { border-top: 1px solid var(--divider); }
.prose__table td { color: var(--ink-soft); }

.legal-contact {
  max-width: var(--measure);
  margin: 18px auto 0;
  padding: 18px 22px;
  border-left: 2px solid var(--primary);
  border-radius: 0 var(--r-sm) var(--r-sm) 0;
  background: rgba(112, 149, 223, 0.055);
  font-size: 0.9375rem;
  font-style: normal;
  line-height: 1.72;
  color: var(--ink-soft);
}

/* An unfilled value. Deliberately loud, and deliberately NOT the brand blue:
   these are the one thing on the page that must never quietly ship as-is, so
   they read as missing rather than as styled text. */
.legal-token {
  display: inline-block;
  padding: 1px 8px;
  border: 1px dashed rgba(180, 118, 31, 0.5);
  border-radius: var(--r-pill);
  background: rgba(180, 118, 31, 0.09);
  font-family: ui-monospace, "Cascadia Mono", Consolas, monospace;
  font-size: 0.8125em;
  color: #8a5a15;
  white-space: nowrap;
}

/* Where the document admits something the app does not do yet. Same visual
   family as .doc__notice — this is the page telling on itself, and it should
   look like it means it. */
.legal-flag {
  max-width: var(--measure);
  margin: 20px auto 0;
  padding: 18px 22px;
  border: 1px solid rgba(112, 149, 223, 0.3);
  border-radius: var(--r-sm);
  background: rgba(112, 149, 223, 0.06);
}
.legal-flag p { padding-top: 0 !important; font-size: 0.9063rem; }

/* A section that is written but inert. Quieter than everything around it, so
   nobody mistakes it for something currently in force. */
.legal-dormant {
  max-width: var(--measure);
  margin: 0 auto 22px;
  padding: 16px 20px;
  border: 1px dashed var(--hairline);
  border-radius: var(--r-sm);
  background: var(--surface-muted);
  color: var(--ink-muted);
}
.legal-dormant p { padding-top: 0 !important; font-size: 0.9063rem; color: var(--ink-muted); }

/* The crisis table is the one thing on either page someone might need in a
   hurry. It gets the app's own sky accent and a heavier rule so it separates
   from the clauses around it at a glance. */
.legal-crisis { border-color: rgba(96, 176, 226, 0.45); }
.legal-crisis th { background: rgba(96, 176, 226, 0.1); color: var(--ink-soft); }
.legal-crisis td strong { font-size: 1.0625em; letter-spacing: 0.01em; }

@media (max-width: 640px) {
  .doc__toc ol { grid-template-columns: 1fr; }
  .prose__table th, .prose__table td { padding: 11px 13px; }
}

/* =========================================================================
   Mailto fallback panel  (mail.js)
   -------------------------------------------------------------------------
   Shown ONLY when a `mailto:` click did not hand off to a mail client — see
   the header of mail.js for how that is detected. It is positioned in JS
   against the link that was clicked, so it has no anchor of its own here.

   Deliberately not a modal: nothing is blocked, there is no scrim, and the
   page stays readable behind it. The visitor was trying to do one small
   thing and the panel is one small answer to it.
   ========================================================================= */

.mailfall {
  position: fixed;
  z-index: 300;
  width: min(320px, calc(100vw - 24px));
  padding: 16px 18px 14px;
  border: 1px solid var(--hairline);
  border-radius: var(--r-sm);
  background: var(--surface);
  box-shadow: var(--lift-md);
}
.mailfall.is-open { animation: mailfall-in 180ms var(--ease-out) both; }

@keyframes mailfall-in {
  from { opacity: 0; translate: 0 6px; }
  to   { opacity: 1; translate: 0 0; }
}

.mailfall__title {
  font-size: 0.9375rem;
  font-weight: 600;
  color: var(--ink);
  /* Room for the close button, which sits in the corner over this line. */
  padding-right: 26px;
}

/* The address is the point of the panel: even if every button here fails,
   this line can be read off the screen and typed. `break-all` because a long
   address must not widen the panel past the viewport clamp above. */
.mailfall__addr {
  margin-top: 6px;
  font-size: 0.9375rem;
  color: var(--ink-soft);
  word-break: break-all;
  user-select: all;
}

.mailfall__row {
  display: flex;
  flex-wrap: wrap;
  gap: 8px;
  margin-top: 12px;
}

.mailfall__btn {
  flex: 1 1 0;
  display: inline-flex;
  align-items: center;
  justify-content: center;
  min-height: 44px;              /* Quick Reference §2 — touch target floor */
  padding: 0 14px;
  border: 1px solid var(--hairline);
  border-radius: var(--r-pill);
  background: var(--surface);
  color: var(--ink-soft);
  font: inherit;
  font-size: 0.9375rem;
  font-weight: 500;
  text-decoration: none;
  cursor: pointer;
  transition: border-color var(--dur) var(--ease-out),
              color var(--dur) var(--ease-out),
              background-color var(--dur) var(--ease-out);
}
.mailfall__btn:hover { color: var(--primary-deep); border-color: rgba(112, 149, 223, 0.5); }

/* Copy takes the whole first row: it is the one action that always works,
   and three pills across 320px would break the words inside them. White on
   --primary-deep is 4.66:1 — the plain --primary would be 2.98:1. */
.mailfall__btn--primary {
  flex: 1 1 100%;
  background: var(--primary-deep);
  border-color: var(--primary-deep);
  color: #fff;
}
.mailfall__btn--primary:hover { background: #3f63b6; border-color: #3f63b6; color: #fff; }

.mailfall__status {
  margin-top: 10px;
  min-height: 1.2em;             /* reserved, so a copy never nudges the row */
  font-size: 0.8125rem;
  color: var(--ink-muted);
}

.mailfall__close {
  position: absolute;
  top: 8px;
  right: 8px;
  display: grid;
  place-items: center;
  width: 32px;
  height: 32px;
  padding: 0;
  border: 0;
  border-radius: 50%;
  background: transparent;
  color: var(--ink-muted);
  cursor: pointer;
}
.mailfall__close:hover { color: var(--ink); background: var(--surface-muted); }
.mailfall__close svg { width: 18px; height: 18px; }

/* =========================================================================
   Motion preferences
   ========================================================================= */

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