/* Public website styles (Phase 12A, batch 4; brand alignment + marketing sections
   2026-08-09).

   Separate from css/app.css on purpose. That file styles a signed-in application
   shell and is precached by the service worker; this one styles crawlable marketing
   pages that must render with JavaScript disabled and must never be pulled into the
   app's offline cache. They share only the palette, deliberately duplicated as
   literals here rather than imported — the public site must not depend on a file
   that lives inside /app/. */

/* ---- self-hosted webfonts (2026-08-09) -----------------------------------------
   The same four files the app bundles, copied to /assets/fonts/ rather than
   referenced across the /app/ boundary (same rule as the palette above). Both are
   VARIABLE fonts, so one file covers the whole weight range; Fraunces carries its
   opsz axis and `font-optical-sizing:auto` (the initial value) is what makes the
   big marketing headlines render the display design rather than the small-text
   design blown up — see app/css/fonts.css for the full history of that regression.
   Self-hosted so no visitor IP ever reaches a font CDN; the OFL notice is in
   legal/credits/ and the licence text ships beside the files. latin-ext is what
   carries Romanian (ă â î ș ț). */
@font-face {
  font-family: 'Nunito';
  font-style: normal;
  font-weight: 400 800;
  font-display: swap;
  src: url(fonts/nunito-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: 'Nunito';
  font-style: normal;
  font-weight: 400 800;
  font-display: swap;
  src: url(fonts/nunito-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: 'Fraunces';
  font-style: normal;
  font-weight: 400 600;
  font-display: swap;
  src: url(fonts/fraunces-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: 'Fraunces';
  font-style: normal;
  font-weight: 400 600;
  font-display: swap;
  src: url(fonts/fraunces-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 {
  --cream: #FBF6EC;
  --ink: #111722;
  --muted: #55606f;
  --line: #e3dccc;
  --card: #ffffff;
  /* The sticky header's translucent surface — the app's own header is the page
     colour at 92% over a blur, and the public chrome mirrors it (owner, 2026-08-09). */
  --head-bg: rgba(251, 246, 236, 0.92);
  /* The brand's primary action pairing (kiri-brand-guide.html §2): moon-yellow
     surface with night-blue text. Until 2026-08-09 this was a green (#2f6f5f) that
     appears nowhere in the brand guide; the marketing pages are built to the guide,
     and one site cannot carry two brands, so every public page moved with them.
     Yellow is a SURFACE colour only — text on cream uses --link (the app's own
     gold, AA at body size) or --accent-text (large text, ≥24px, the app's tuned
     3:1 value), because moon-yellow text on cream is the first thing the brand
     guide's "don't" list forbids. */
  --accent: #F2D06B;
  --accent-ink: #1C2432;
  --link: #85651D;
  --accent-text: #AB8931;
  /* THE SAME GOLD, ONE STEP DARKER, FOR TEXT ON A TINTED SURFACE.

     --link is the app's --gold-text unchanged, and it is 5.03:1 on --cream. On the two
     surfaces that sit a step off the page it is not: 4.65 on --band and 4.93 on
     --warn-bg. Both clear WCAG AA, and the site shipped that way on 2026-08-09 —
     the review measured it and found the marketing sections had become the lowest-
     contrast text on the whole site, below every page that existed before them.

     Rather than move --link and lose the app's own value, the two tinted surfaces
     carry this one: 5.04 on --band, 5.34 on --warn-bg. The floor is then 5.03
     everywhere — the app's gold on the app's cream — and nothing a marketing section
     introduced is the worst thing on the site. tests/public-contrast.test.js pins it.

     Dark needs no such step (10.06 on the night band), so the token simply resolves
     back to the dark --link below and the rule that uses it stays one line. */
  --link-tinted: #7E601C;
  /* The moon pair, from the brand guide's palette — NOT from app/css/app.css,
     where several supporter themes redefine --moon-deep. Used by the night-sky
     decor below and by small brand accents. */
  --moon-glow: #FBEBB0;
  --moon-deep: #D9AE3E;
  /* A band surface one step off the page, for marketing sections that alternate. */
  --band: #F6EDD8;
  --night: #1C2432;
  /* Body text on the night band. The band paints the SAME surface in both schemes, so
     this resolves once here beside --night and is deliberately ABSENT from the dark
     block below — redefining it there would be the bug, not the fix.

     It was a hard-coded #A7CBD6 until 2026-08-09, with its link at #CBE1E8: a cool
     blue-grey, the only pairing on the site off the brand's warm axis, and unnamed, so
     tests/public-contrast.test.js had to carry two literal hex values where every other
     row names a token. Contrast was never the problem (9.02 and 11.48) — the palette
     was. This is the cream dimmed to sit quietly under the quote (9.52 on --night), and
     the link now takes --moon-glow, which the blockquote directly above it already uses,
     so the band is lit by one colour instead of borrowing a second family. */
  --on-night: #D6C9AE;
  --font-display: 'Fraunces', Georgia, serif;
  --font-body: 'Nunito', system-ui, -apple-system, "Segoe UI", Roboto, Helvetica, Arial, sans-serif;
  --warn-bg: #fdf3e3;
  --warn-line: #e4c48a;
  /* Risk bands on the generated reference directories (Phase 12B wave 1). The colours
     carry no information on their own — every band also states its verdict in words,
     from the app's own FOOD_ZONE table — so a reader who cannot separate red from green
     loses nothing.
     FOUR bands, not three: FOOD_ZONE has a fourth, `unknown` ("⚪ Unknown / do not use"),
     and it shipped unstyled on 2026-08-08 because this comment and the rules below said
     three. It renders SECOND on /guide/foods/, right under "Dangerous / avoid", and it
     was the only section on a colour-coded page with no colour. Grey rather than a
     fourth hue: "we do not know" is not a fifth severity between caution and danger. */
  --safe-bg: #eef7f1;
  --safe-line: #a9cdb8;
  --danger-bg: #fdeeee;
  --danger-line: #e0a7a7;
  --unknown-bg: #f2f0ea;
  --unknown-line: #c7c0b0;
  /* Ink for the `.cap` line at the top of a coloured callout (Phase 12B wave 3). The
     chapter HTML arrives with `.callout.warn|danger|safe` and a `.cap` inside it, and
     app/css/app.css tints that line to match the band. The band background is not
     enough on its own — a `.cap` reads as a heading and needs to be the strongest thing
     in the box. Separate tokens rather than the line colours above, which are borders
     and are nowhere near a text contrast ratio. */
  --safe-ink: #2c6544;
  --warn-ink: #79570f;
  --danger-ink: #a03028;
  --maxw: 760px;
}

/* ============================================================================
   THE DARK PALETTE IS DECLARED TWICE, ON PURPOSE, AND A TEST KEEPS THEM EQUAL.

   Until 2026-08-09 the public site had no theme control at all: it followed
   `prefers-color-scheme` and nothing else, while the app has had a real day/night
   button since long before. A visitor who reads the site dark and the app light —
   or the reverse — sees two products.

   A toggle needs a second way in, and CSS gives no way to share one declaration
   block between a media query and a plain rule. So:

     - `@media (prefers-color-scheme: dark) { :root:not([data-theme="light"]) }`
       is the DEFAULT — it is what a visitor with no stored choice gets, and it is
       what a visitor with JavaScript off always gets. The `:not()` is what lets an
       explicit "light" win over a dark operating system.
     - `:root[data-theme="dark"]` is the STORED CHOICE, and it needs no `:not()`
       because nothing else sets that attribute.

   Duplication is the cost. `tests/public-contrast.test.js` §b compares the two
   blocks token for token and fails if they drift, which is the only reason this is
   safe to write twice — a palette that disagrees with itself by one value is
   invisible until somebody with the other setting looks.
   ============================================================================ */
@media (prefers-color-scheme: dark) {
  :root:not([data-theme="light"]) {
    --cream: #111722;
    --ink: #f2efe7;
    --muted: #a5aebd;
    --line: #263043;
    --card: #182031;
    --head-bg: rgba(17, 23, 34, 0.92);
    /* The moon button keeps its daytime face at night — the app's dark theme does
       the same (body.dark leaves --moon alone), and it is the one warm glow on the
       page, which is the brand's whole idea. --link and --accent-text take the
       app's own dark-theme gold values. */
    --accent: #F2D06B;
    --accent-ink: #1C2432;
    --link: #E4C674;
    --accent-text: #E0BE5E;
    --link-tinted: #E4C674;
    --band: #161e2b;
    --warn-bg: #2a2413;
    --warn-line: #6b5a2c;
    --safe-bg: #14241c;
    --safe-line: #2f5b45;
    --danger-bg: #2a1717;
    --danger-line: #6b3434;
    --unknown-bg: #1d2331;
    --unknown-line: #3d4759;
    --safe-ink: #8fd3ac;
    --warn-ink: #e2c179;
    --danger-ink: #f0a6a0;
  }
}
:root[data-theme="dark"] {
  --cream: #111722;
  --ink: #f2efe7;
  --muted: #a5aebd;
  --line: #263043;
  --card: #182031;
  --head-bg: rgba(17, 23, 34, 0.92);
  --accent: #F2D06B;
  --accent-ink: #1C2432;
  --link: #E4C674;
  --accent-text: #E0BE5E;
  --link-tinted: #E4C674;
  --band: #161e2b;
  --warn-bg: #2a2413;
  --warn-line: #6b5a2c;
  --safe-bg: #14241c;
  --safe-line: #2f5b45;
  --danger-bg: #2a1717;
  --danger-line: #6b3434;
  --unknown-bg: #1d2331;
  --unknown-line: #3d4759;
  --safe-ink: #8fd3ac;
  --warn-ink: #e2c179;
  --danger-ink: #f0a6a0;
}

* { box-sizing: border-box; }

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

body {
  margin: 0;
  background: var(--cream);
  color: var(--ink);
  font: 17px/1.65 var(--font-body);
  -webkit-font-smoothing: antialiased;
}

a { color: var(--link); }
/* Focus rings are --link rather than --accent: moon-yellow on cream is ~1.4:1,
   nowhere near the 3:1 a focus indicator needs, and the gold clears it in both
   schemes. */
a:focus-visible, button:focus-visible,
input:focus-visible, select:focus-visible, textarea:focus-visible {
  outline: 3px solid var(--link); outline-offset: 2px;
}

.wrap { max-width: var(--maxw); margin: 0 auto; padding: 0 20px; }

/* --- header / nav ----------------------------------------------------------
   Styled off the app's own signed-in header (owner, 2026-08-09): sticky, the page
   colour at 92% over an 8px blur, a hairline seam, and the lowercase `kiri.`
   wordmark with the moon-gold dot. The two headers are meant to read as one
   product. */
.site-head {
  position: sticky; top: 0; z-index: 40;
  border-bottom: 1px solid var(--line);
  background: var(--head-bg);
  -webkit-backdrop-filter: blur(8px); backdrop-filter: blur(8px);
}
/* Narrow screens wrap the nav to two or three rows; a 200px+ sticky bar would eat
   the viewport, so the header only sticks where it is one row tall. */
@media (max-width: 880px) { .site-head { position: static; } }
/* `position: relative` so the narrow-screen Guide panel can span the whole header
   rather than hang off one wrapped nav item. See the media query below. */
.site-head .wrap { display: flex; align-items: center; gap: 18px; min-height: 66px; flex-wrap: wrap; position: relative; }
/* THE DRAWN LOGO, since 2026-08-15 (owner). It was set type — Fraunces `kiri` plus
   the gold dot, "the wordmark IS the logo, no custom mark" (brand guide §4,
   2026-08-09) — until the owner drew one; the type lockup survives underneath as
   the fallback described below.

   A BACKGROUND IMAGE, NOT AN `<img>`, and that is the whole reason this works.
   The logo comes in two files that are byte-identical apart from the pill colour
   (#141C2A for light, #1E2838 for dark), and each is 105 KB over the wire. Two
   `<img>` tags swapped with `display:none` would download BOTH on every visit,
   because a hidden image is still fetched; a `<picture>` would key off
   `prefers-color-scheme` and ignore this site's own day/night toggle. A background
   image is fetched only when the rule that names it actually applies, so a visitor
   pays for one file, and only pays for the second if they toggle.

   The text stays in the markup and is pushed out of the box rather than deleted:
   if the stylesheet or the SVG fails, the header still says the product's name
   instead of showing a 140px hole. `aria-label` on the <a> is what assistive tech
   reads either way, so nothing here is announced twice. */
.brand {
  display: block; flex-shrink: 0;
  width: 154px; height: 44px;
  text-indent: -9999px; overflow: hidden; white-space: nowrap;
  background: url("/media/logo/logo-light.svg") left center / contain no-repeat;
}
/* Same three states as the colour tokens above: bare `:root` is daylight, the
   media query is the system preference where no choice is stored, and the
   attribute is the stored choice and must win in both directions. */
@media (prefers-color-scheme: dark) {
  :root:not([data-theme="light"]) .brand { background-image: url("/media/logo/logo-dark.svg"); }
}
:root[data-theme="dark"] .brand { background-image: url("/media/logo/logo-dark.svg"); }
/* The header is one row down to 880px and wraps below it; a 154px logo alongside a
   wrapped nav crowds a phone, and the bird inside the pill is only ~29% of its
   width, so shrinking much further makes it a smudge rather than a mark. */
@media (max-width: 560px) { .brand { width: 126px; height: 36px; } }
.site-nav { display: flex; gap: 16px; margin-left: auto; flex-wrap: wrap; align-items: center; }
.site-nav a { text-decoration: none; color: var(--muted); font-weight: 700; font-size: 0.95rem; }
.site-nav a:hover, .site-nav a[aria-current="page"] { color: var(--ink); text-decoration: underline; }
/* "Open the app" is the header's one real call to action — the app's primary button
   at nav scale: moon surface, night ink, 12px radius, weight 800. */
.site-nav a.nav-cta {
  background: var(--accent); color: var(--accent-ink);
  padding: 9px 16px; border-radius: 12px; font-weight: 800;
}
.site-nav a.nav-cta:hover { text-decoration: none; filter: brightness(1.08); color: var(--accent-ink); }

/* ---- Day/night toggle -------------------------------------------------------
   Sized and bordered like the app's own `.theme-btn` (app/css/app.css) so the two
   headers read as one product. It shows the moon in daylight and the sun at night —
   the icon of what the next press GIVES you, which is the same convention the app
   uses and the reason the visible label and `aria-pressed` say opposite things.

   Hidden until <html> carries `data-js`, which /assets/site-theme.js sets before the
   first paint. With JavaScript off the media query still themes the page correctly;
   what disappears is a button that could not have done anything. */
:root:not([data-js]) .theme-toggle { display: none; }
.theme-toggle {
  display: inline-flex; align-items: center; justify-content: center;
  width: 38px; height: 38px; padding: 0; flex-shrink: 0;
  background: var(--card); border: 1px solid var(--line); border-radius: 9px;
  color: var(--link); cursor: pointer;
}
.theme-toggle:hover { border-color: var(--moon-deep); }
.theme-toggle svg { width: 19px; height: 19px; display: block; }
.theme-toggle .tt-sun { display: none; }
@media (prefers-color-scheme: dark) {
  :root:not([data-theme="light"]) .theme-toggle .tt-sun { display: block; }
  :root:not([data-theme="light"]) .theme-toggle .tt-moon { display: none; }
}
:root[data-theme="dark"] .theme-toggle .tt-sun { display: block; }
:root[data-theme="dark"] .theme-toggle .tt-moon { display: none; }

/* The Guide disclosure. `<details>` rather than a hover menu — the public site carries no
   script that could close a menu, and `:hover` does not exist on a phone. See the comment
   in _partials/nav-en.html.

   `position: relative` on the <details> and `absolute` on the panel is what keeps the
   header from growing 5 rows taller when it opens; the nav already wraps, so a panel in
   normal flow would reflow the whole bar under it. */
.nav-menu { position: relative; }
.nav-menu > summary {
  cursor: pointer; color: var(--muted); list-style: none;
  display: inline-flex; align-items: center; gap: 5px;
}
/* Safari still paints the default triangle through `list-style: none`. */
.nav-menu > summary::-webkit-details-marker { display: none; }
.nav-menu > summary::after {
  content: "▾"; font-size: 0.8em; line-height: 1; transition: transform 120ms ease;
}
.nav-menu[open] > summary::after { transform: rotate(180deg); }
.nav-menu > summary:hover, .nav-menu[open] > summary { color: var(--ink); }
.nav-menu > summary:focus-visible { outline: 3px solid var(--link); outline-offset: 2px; }
/* The panel wears the app's account-menu clothes: warm card, 16px radius, and the
   app's own ink-toned pop shadow — the one place the site uses a shadow, exactly
   like the app (cards never, floating menus yes). */
.nav-menu-panel {
  position: absolute; z-index: 20; top: calc(100% + 10px); right: 0; min-width: 232px;
  display: flex; flex-direction: column; gap: 2px;
  background: var(--card); border: 1px solid var(--line); border-radius: 16px;
  padding: 10px; box-shadow: 0 16px 42px rgba(28, 36, 50, 0.2);
}
.nav-menu-panel a { padding: 8px 10px; border-radius: 8px; white-space: nowrap; }
.nav-menu-panel a:hover { background: var(--cream); color: var(--ink); text-decoration: none; }
/* The hub link is the parent of the four below it, so it reads as a heading with them
   indented under it rather than as a fifth sibling. */
.nav-menu-panel a:first-child { font-weight: 700; color: var(--ink); }
.nav-menu-panel a:first-child + a { margin-top: 2px; }

/* Narrow screens: the panel spans the full header instead of hanging off one nav item,
   because 232px of menu anchored to the right of the word "Guide" in a bar that has
   already wrapped lands somewhere arbitrary.

   It anchors to `.site-head .wrap` rather than to the <details>, so the disclosure keeps
   its natural width and the bar stays one or two tidy rows. The first attempt gave
   `.nav-menu` `width: 100%`, which forced a line break before AND after it and turned a
   six-item bar into four rows with a 423px header. */
@media (max-width: 560px) {
  .nav-menu { position: static; }
  .nav-menu-panel { left: 20px; right: 20px; min-width: 0; top: calc(100% + 6px); }
}

/* --- content --------------------------------------------------------------- */
main { padding: 34px 0 10px; }
/* Fraunces at every heading level, weight 500 — measured off the running app on
   2026-08-09: its card headings are Fraunces 19px/500, and matching them is most of
   what makes the two surfaces read as one product. (This file used to keep h3 in
   Nunito on the claim that the app's card titles were Nunito; they are not.) The
   footer's column headings opt back into the Nunito micro-caption voice explicitly,
   exactly as the app's own footer does. */
h1, h2, h3 { font-family: var(--font-display); font-weight: 500; letter-spacing: -0.01em; }
h1 { font-size: 2.1rem; line-height: 1.15; margin: 0 0 12px; }
h2 { font-size: 1.45rem; line-height: 1.25; margin: 32px 0 10px; }
h3 { font-size: 1.15rem; margin: 24px 0 8px; }
.lead { font-size: 1.12rem; color: var(--muted); margin: 0 0 26px; }

.cta {
  display: inline-block; background: var(--accent); color: var(--accent-ink);
  padding: 13px 26px; border-radius: 999px; text-decoration: none; font-weight: 700;
}
.cta:hover { filter: brightness(1.08); }
.cta-row { margin: 26px 0 8px; display: flex; gap: 14px; align-items: center; flex-wrap: wrap; }
.cta-note { color: var(--muted); font-size: 0.95rem; }

.card {
  background: var(--card); border: 1px solid var(--line); border-radius: 16px;
  padding: 18px 20px; margin: 18px 0;
}
.callout {
  background: var(--warn-bg); border: 1px solid var(--warn-line);
  border-radius: 14px; padding: 16px 20px; margin: 22px 0;
}
.callout p:last-child { margin-bottom: 0; }

/* EVERY SURFACE THAT SITS A STEP OFF THE PAGE, in one place, switching gold text to
   --link-tinted. Custom properties inherit, so one declaration covers every link,
   kicker and ghost button inside without any of them being named.

   The list is the complete set of `background: var(--…-bg)` and `var(--band)` rules in
   this file; keep it that way when a new tinted block is added, or that block quietly
   becomes the site's worst contrast. That is exactly what happened on 2026-08-09: the
   link colour moved from an off-brand green to the app's gold, which is 5.03:1 on
   --cream and less on everything tinted, and the browser sweep found the floor had
   dropped to 4.65 on the marketing band and 4.75 on the `unknown` risk band of
   /guide/foods/ — below every measurement in the documents describing this site.
   tests/public-contrast.test.js now pins the floor so it cannot move unnoticed. */
.callout, .transnotice, .dir-zone, .az-z, .out-band, .m-sec.tint, .m-myth-m, .m-myth-f,
.m-ps-panel, .m-smp-panel, .m-bt-top, .m-bm-tile.b, .m-bi-band {
  --link: var(--link-tinted);
}

.feature-list { list-style: none; padding: 0; margin: 18px 0; }
.feature-list li { padding: 12px 0; border-bottom: 1px solid var(--line); }
.feature-list li:last-child { border-bottom: 0; }

.post-list { list-style: none; padding: 0; margin: 24px 0; }
.post-list li { padding: 16px 0; border-bottom: 1px solid var(--line); }
.post-list a { font-weight: 700; font-size: 1.08rem; }
.post-meta { color: var(--muted); font-size: 0.92rem; margin-top: 4px; }

/* --- media placeholders (2026-08-09) --------------------------------------------
   There is no photography and no video for the marketing site yet, and the sections
   that will hold them are being built now. Rather than leave holes, every image and
   video slot renders as a grey block at the right aspect ratio with an icon in the
   middle — so a section can be judged for layout, rhythm and length before a single
   asset exists, and so a missing asset never collapses a section to nothing.

   The app already works this way: `.video-placeholder` on the chapter videos and the
   "photographs coming" note in the mutation gallery. This is the same idea, one
   stylesheet over. Swap `.ph` for a real `<img>` or `<video>`; nothing else changes.

   `--ph-ratio` is set per use, so a 16/9 hero and a 1/1 avatar share this code. */
.ph {
  --ph-ratio: 16 / 9;
  aspect-ratio: var(--ph-ratio);
  width: 100%;
  display: grid; place-items: center; gap: 6px;
  background: var(--card);
  border: 1px dashed var(--line);
  border-radius: 14px;
  color: var(--muted);
  overflow: hidden;
}
.ph-ic { font-size: 2rem; line-height: 1; opacity: 0.75; }
/* The label says what belongs here, not "placeholder" — the person reviewing the page
   needs to know what is missing, and the person supplying it needs to know what to
   send. Keep it short: it is read at a glance, not studied. */
.ph-label { font-size: 0.82rem; letter-spacing: 0.04em; text-transform: uppercase; }
/* `square` is the AVATAR circle (quotes' footer). A square-cornered 1:1 slot is
   `tile` — added in step 6 after every square-media section in the library had to
   override the circle individually (docs/SECTION_LIBRARY.md §14.9). */
.ph.square { --ph-ratio: 1 / 1; border-radius: 50%; }
.ph.tile { --ph-ratio: 1 / 1; border-radius: 16px; }
.ph.portrait { --ph-ratio: 3 / 4; }
.ph.wide { --ph-ratio: 21 / 9; }

/* --- the machine-translation notice (tools/lib/public-page.js) ------------------
   Shown on every page of a published locale that no native speaker has read. It sits
   above the page's own h1, so it has to be legible without being the loudest thing on
   a safety page: the amber callout surface, which the site already uses for "read this
   before you act", at a smaller size than the lead beneath it. */
.transnotice {
  background: var(--warn-bg); border: 1px solid var(--warn-line);
  border-radius: 14px; padding: 12px 18px; margin: 0 0 22px;
  font-size: 0.94rem;
}
.transnotice p { margin: 0; }
.transnotice a { font-weight: 700; }

/* --- the blog's categories (tools/build-blog-index.js, Phase 12B §5) ------------
   Two short lists rather than a second post list: the topic list on the blog index
   and the hub list on a category page are both navigation away from where you are,
   and styling them like articles would make the index read as twice as long as it is. */
.blog-topics, .blog-hubs { list-style: none; padding: 0; margin: 16px 0; }
.blog-topics li, .blog-hubs li { padding: 8px 0; border-bottom: 1px solid var(--line); }
.blog-topics li:last-child, .blog-hubs li:last-child { border-bottom: 0; }
.blog-topics a, .blog-hubs a { font-weight: 700; }

.entity-table { margin: 10px 0; }
.entity-row { display: flex; gap: 12px; padding: 7px 0; border-bottom: 1px solid var(--line); }
.entity-k { flex: 0 0 42%; color: var(--muted); }
.entity-v { flex: 1 1 auto; }

/* --- generated reference directories (tools/build-public-guide.js) ------------
   A directory page is one long list on a phone, so the rules below are about
   scanning: the band is a heading you can find, the name is the only bold thing in
   the row, and the note sits under it rather than beside it. Nothing here uses a
   table — 106 rows of three columns is unreadable at 360 px, and the risk band is
   already the grouping a table's first column would have carried. */
.crumb { margin: 0 0 10px; font-size: 0.94rem; }
.dir-zone {
  border: 1px solid var(--line); border-radius: 14px;
  padding: 4px 20px 8px; margin: 26px 0;
}
.dir-zone.safe { background: var(--safe-bg); border-color: var(--safe-line); }
.dir-zone.warn { background: var(--warn-bg); border-color: var(--warn-line); }
.dir-zone.danger { background: var(--danger-bg); border-color: var(--danger-line); }
.dir-zone.unknown { background: var(--unknown-bg); border-color: var(--unknown-line); }
.dir-zone h2 { font-size: 1.15rem; margin: 18px 0 6px; }
.dir-count { color: var(--muted); font-size: 0.9rem; font-weight: 400; white-space: nowrap; }
.dir-list { list-style: none; padding: 0; margin: 0; }
.dir-list li { padding: 12px 0; border-top: 1px solid var(--line); }
.dir-list li:first-child { border-top: 0; }
.dir-name { margin: 0; font-weight: 700; }
.dir-alt, .dir-sci { color: var(--muted); font-weight: 400; font-size: 0.93rem; }
.dir-sci { font-style: italic; }
.dir-note { margin: 3px 0 0; }
.dir-src { margin: 3px 0 0; font-size: 0.88rem; }
.dir-src a { margin-right: 10px; white-space: nowrap; }
.dir-sources, .dir-next { padding-left: 22px; }
.dir-next li { padding: 4px 0; }

/* --- the two "find it fast" blocks above the directory ---------------------- */
.qa-block { margin: 26px 0; }
.qa-block > p { color: var(--muted); margin: 0 0 6px; }
.qa { padding: 12px 0; border-top: 1px solid var(--line); }
.qa:first-of-type { border-top: 0; }
.qa h3 { margin: 0 0 3px; font-size: 1.05rem; }
.qa p { margin: 0; }

/* The A–Z lookup. `<details>` and not a script: the public site carries two scripts and
   ROADMAP 12.3 forbids fetching primary content with JavaScript, so the full list is in
   the markup — where a crawler reads it — and only the RENDER waits for the reader. */
.az { border: 1px solid var(--line); border-radius: 14px; padding: 2px 20px; margin: 22px 0; }
.az > summary {
  cursor: pointer; padding: 13px 0; font-weight: 700; list-style: none;
  display: flex; align-items: center; gap: 8px;
}
.az > summary::-webkit-details-marker { display: none; }
.az > summary::before { content: "🔎"; }
.az > summary::after { content: "▾"; margin-left: auto; transition: transform 120ms ease; }
.az[open] > summary::after { transform: rotate(180deg); }
.az > summary:focus-visible { outline: 3px solid var(--link); outline-offset: 2px; }
.az-lead { color: var(--muted); font-size: 0.94rem; margin: 0 0 10px; }
.az-list {
  list-style: none; padding: 0; margin: 0 0 16px;
  /* Two or three columns on anything but a phone: 114 single-column rows is a scroll,
     and an alphabetical index is read by scanning down a narrow column. */
  columns: 220px 3; column-gap: 26px;
}
.az-list li {
  break-inside: avoid; padding: 4px 0;
  display: flex; align-items: baseline; gap: 8px; justify-content: space-between;
}
/* The verdict rides along as words, never as colour alone — same rule as the bands. */
.az-z {
  font-size: 0.8rem; color: var(--muted); white-space: nowrap;
  border: 1px solid var(--line); border-radius: 999px; padding: 1px 8px;
}
.az-z.safe { background: var(--safe-bg); border-color: var(--safe-line); }
.az-z.warn { background: var(--warn-bg); border-color: var(--warn-line); }
.az-z.danger { background: var(--danger-bg); border-color: var(--danger-line); }
.az-z.unknown { background: var(--unknown-bg); border-color: var(--unknown-line); }

/* --- generated training modules (tools/build-public-guide.js, wave 2) ---------
   A module page is one long ordered list read on a phone while a bird is on a perch,
   so the step number has to be findable and the body has to be a paragraph rather
   than a table cell. Nothing here needs a new colour — the risk bands are the
   directories' vocabulary and a training step has no verdict to carry. */
.mod-list { list-style: none; padding: 0; margin: 20px 0; }
.mod-list li { padding: 16px 0; border-bottom: 1px solid var(--line); }
.mod-list li:last-child { border-bottom: 0; }
.mod-list > li > a { font-weight: 700; font-size: 1.08rem; }
.mod-sum { margin: 6px 0 0; }
.mod-pace { color: var(--muted); font-size: 0.94rem; }
.mod-meta { color: var(--muted); font-size: 0.94rem; margin: -18px 0 22px; }

/* The prerequisite note is a callout that is NOT a warning, and it sits directly above
   the "training advice, not veterinary advice" one. Left in the shared amber, a module
   page opened two identical alert boxes in a row and the safety one stopped reading as
   the safety one. Neutral card here; amber stays for the disclaimer. */
.mod-needs { background: var(--card); border-color: var(--line); }

/* The numbers are the point of this list — they are what "the steps, in order" means —
   so they sit outside the text block and stay legible rather than shrinking into it. */
.mod-steps { margin: 14px 0; padding-left: 26px; }
.mod-steps > li { padding: 4px 0 14px; }
.mod-steps > li::marker { color: var(--muted); font-weight: 700; }
.mod-steps h3 { margin: 0 0 4px; font-size: 1.05rem; }
.mod-steps p { margin: 0; }

/* "You will know it worked when" is the one thing on the page a reader comes back to
   check, so it is a card rather than another run of prose. */
.mod-ready {
  background: var(--card); border: 1px solid var(--line); border-radius: 14px;
  padding: 2px 20px 16px; margin: 26px 0;
}
.mod-ready h2 { font-size: 1.15rem; margin: 18px 0 6px; }
.mod-ready p { margin: 0; }

/* Three lists that look the same and mean different things: the sources, the modules
   this one unlocks (`mod-next`) and the remaining siblings (`mod-all`). They share a
   rule because they share an appearance, not because they are the same list — the last
   two carried one class until 2026-08-08 and nothing scanning by class could tell the
   prerequisite graph from the sibling nav. */
.mod-sources, .mod-next, .mod-all { padding-left: 22px; }
.mod-sources li, .mod-next li, .mod-all li { padding: 4px 0; }

/* --- markup that arrives INSIDE the content (Phase 12B wave 3) -----------------
   Everything above is styling for classes the generator invents. These five are
   different, and the difference is the reason this block is separate: `.block`,
   `.cap` and the three `.callout` modifiers are written into `app/data/chapters.js`
   by whoever writes the guide, and they reach the page as raw HTML. No generator
   owns them and no template mentions them, so the only thing standing between a
   new class in the data and an unstyled paragraph on a safety page is the allowlist
   in tools/build-public-guide.js (refusal 7) plus the check in
   tests/public-guide.test.js that every class the pages emit has a rule here.

   Copied in SPIRIT from app/css/app.css:582-624, not imported: that file styles a
   signed-in app shell, is precached by the service worker, and lives behind the
   /app/ boundary the public site must not depend on. The palette tokens here are
   this file's own. */
.block {
  background: var(--card); border: 1px solid var(--line); border-radius: 14px;
  padding: 16px 20px; margin: 14px 0;
}
.block h3 { font-size: 1.08rem; margin: 0 0 6px; }
.block p { margin: 0 0 10px; }
.block p:last-child, .block ul:last-child { margin-bottom: 0; }
.block ul, .callout ul { margin: 6px 0 10px; padding-left: 22px; }
.block li, .callout li { padding: 3px 0; }

/* The three coloured callouts. The colour is never the only signal — every one of
   them opens with a `.cap` that states in words what it is ("⚑ Alarm signs",
   "🔴 Red zone · deadly toxic"), which is the same rule the risk bands follow. */
.callout.safe { background: var(--safe-bg); border-color: var(--safe-line); }
.callout.warn { background: var(--warn-bg); border-color: var(--warn-line); }
.callout.danger { background: var(--danger-bg); border-color: var(--danger-line); }
.cap {
  font-size: 0.78rem; font-weight: 700; letter-spacing: 0.08em; text-transform: uppercase;
  margin-bottom: 8px; display: flex; align-items: center; gap: 7px;
}
.callout.safe .cap { color: var(--safe-ink); }
.callout.warn .cap { color: var(--warn-ink); }
.callout.danger .cap { color: var(--danger-ink); }
.callout h3 { font-size: 1.05rem; margin: 0 0 6px; }
/* A paragraph closing a callout after its list. This is where chapter 12's inline
   `style="margin-top:8px;"` went when wave 3 removed it from the base and all
   seventeen overlays — the global reset zeroes margins in the app, so the spacing
   was real and had to land somewhere. app/css/app.css carries the same rule. */
.callout ul + p { margin-top: 8px; }

/* --- the care guide (tools/build-public-guide.js, wave 3) ----------------------
   Fifteen chapters over five pages. A part is long — 322 to 485 rendered words of
   prose in cards — so the page needs a way in at the top and a visible seam between
   one chapter and the next. */
.care-list { list-style: none; padding: 0; margin: 20px 0; }
.care-list li { padding: 16px 0; border-bottom: 1px solid var(--line); }
.care-list li:last-child { border-bottom: 0; }
.care-list > li > a { font-weight: 700; font-size: 1.08rem; }
.care-sum { margin: 6px 0 0; }
/* The chapter titles inside a part, as direct links. Smaller and muted: they are a
   contents line under the part, not five more headline links competing with it. */
.care-in { margin: 6px 0 0; font-size: 0.92rem; color: var(--muted); }
.care-meta { color: var(--muted); font-size: 0.94rem; margin: -18px 0 22px; }
.care-toc {
  background: var(--card); border: 1px solid var(--line); border-radius: 14px;
  padding: 14px 20px; margin: 22px 0;
}
.care-toc-h { margin: 0 0 6px; font-weight: 700; font-size: 0.94rem; }
.care-toc ul { margin: 0; padding-left: 22px; }
.care-toc li { padding: 3px 0; }
/* The seam. A chapter is several cards, so the boundary between chapters has to be
   stronger than the boundary between two cards inside one, or the page reads as one
   undifferentiated run of boxes. */
.care-ch { border-top: 1px solid var(--line); margin-top: 34px; padding-top: 6px; }
.care-ch h2 { display: flex; align-items: baseline; gap: 10px; }
.care-num { color: var(--muted); font-size: 0.9rem; font-variant-numeric: tabular-nums; }
.care-tag { color: var(--muted); font-size: 0.94rem; margin: -4px 0 14px; }
.care-next { padding-left: 22px; }
.care-next li { padding: 4px 0; }

/* --- the 21 triage outcomes (tools/build-public-guide.js, wave 3) --------------
   One page, three severity bands, and the bands reuse the directories' colours on
   purpose: a reader who has met red on /guide/foods/ should not have to learn a
   second colour language here. Same rule too — the band always states its severity
   in words, from the app's own label, so the colour carries nothing on its own. */
.out-kicker {
  color: var(--muted); font-size: 0.85rem; letter-spacing: 0.06em;
  text-transform: uppercase; margin: -18px 0 22px;
}
/* The way into the twenty-one anchors. Same card as `.care-toc` on the care parts,
   because it is the same thing doing the same job on a page in the same wave — and this
   is the page that needed it most: 17,009 px at 390 px, against roughly 5,000 for a care
   part. Compact rather than clever: three band labels, twenty-one links, no nesting
   beyond that, so the whole box costs under one screen on a phone. */
.out-toc {
  background: var(--card); border: 1px solid var(--line); border-radius: 14px;
  padding: 14px 20px; margin: 22px 0;
}
.out-toc-cap { margin: 0 0 6px; font-weight: 700; font-size: 0.94rem; }
.out-toc > ul { margin: 0; padding: 0; list-style: none; }
.out-toc-band { margin-top: 10px; }
.out-toc-band:first-child { margin-top: 0; }
.out-toc-h {
  display: block; font-size: 0.78rem; font-weight: 700; letter-spacing: 0.06em;
  text-transform: uppercase; color: var(--muted); margin-bottom: 2px;
}
.out-toc-band ul { margin: 0; padding-left: 22px; }
.out-toc-band li { padding: 3px 0; font-size: 0.94rem; }

.out-band {
  border: 1px solid var(--line); border-radius: 14px;
  padding: 4px 20px 8px; margin: 26px 0;
}
.out-band.safe { background: var(--safe-bg); border-color: var(--safe-line); }
.out-band.warn { background: var(--warn-bg); border-color: var(--warn-line); }
.out-band.danger { background: var(--danger-bg); border-color: var(--danger-line); }
.out-band h2 { font-size: 1.15rem; margin: 18px 0 6px; }
.out { padding: 14px 0; border-top: 1px solid var(--line); }
.out:first-of-type { border-top: 0; }
.out h3 { margin: 0 0 4px; font-size: 1.05rem; }
.out-sum { margin: 0 0 10px; }
/* "What to do" / "What to avoid" / "Sources used" are labels for the list under
   them, not headings — the outcome's own h3 is the heading, and three more heading
   levels per outcome would put sixty-three of them in the page outline. */
.out-h {
  margin: 10px 0 2px; font-size: 0.8rem; font-weight: 700; letter-spacing: 0.06em;
  text-transform: uppercase; color: var(--muted);
}
.out-do, .out-avoid, .out-src { margin: 0; padding-left: 22px; }
.out-do li, .out-avoid li, .out-src li { padding: 3px 0; }
.out-src { font-size: 0.9rem; }
.out-cta { margin: 12px 0 0; font-weight: 700; }

/* --- the emergency card (tools/build-public-guide.js, wave 4) ------------------
   Four callouts and a list of links out, and that is the whole page. It borrows the
   three callout levels rather than inventing a fourth colour language: a reader who
   has met red on /guide/foods/ and on /guide/behaviour/ meets the same red here. The
   only thing it owns is the links-out list, which has to read as the point of the
   page rather than as a footer — so it is spaced like a list of destinations. */
.emg-more { padding-left: 22px; }
.emg-more li { padding: 6px 0; }
.emg-more a { font-weight: 700; }

/* --- the first week (tools/build-public-guide.js, wave 4) ----------------------
   Seven days, three steps each. The day is the unit a reader works through, so each
   one is a seam like a care chapter rather than a card in a run — the page is read
   over a week, a day at a time, and coming back to it means finding day four. */
.fw-meta { color: var(--muted); font-size: 0.94rem; margin: -18px 0 22px; }
.fw-toc {
  background: var(--card); border: 1px solid var(--line); border-radius: 14px;
  padding: 14px 20px; margin: 22px 0;
}
.fw-toc-cap { margin: 0 0 6px; font-weight: 700; font-size: 0.94rem; }
.fw-toc ul { margin: 0; padding-left: 22px; }
.fw-toc li { padding: 3px 0; }
.fw-day { border-top: 1px solid var(--line); margin-top: 30px; padding-top: 6px; }
.fw-day h2 { display: flex; align-items: baseline; gap: 10px; }
.fw-ic { font-size: 1.1rem; }
.fw-sum { color: var(--muted); margin: -4px 0 12px; }
.fw-steps { margin: 0; padding-left: 22px; }
.fw-steps li { padding: 5px 0; }

.legal-index { list-style: none; padding: 0; margin: 20px 0; }
.legal-index li { padding: 12px 0; border-bottom: 1px solid var(--line); }
.legal-body h3:first-of-type { margin-top: 8px; }
.legal-list { padding-left: 22px; }
.doc-date { color: var(--muted); font-size: 0.92rem; margin-top: 26px; }

/* --- footer ---------------------------------------------------------------- */
/* The footer mirrors the signed-in app's (owner, 2026-08-09): the big Fraunces
   wordmark, link columns under 11px tracked micro-caption headings, gold w700
   links, and a hairline bottom bar for the copyright and the disclaimer. The
   markup lives in _partials/footer-{en,ro}.html. */
.site-foot { border-top: 1px solid var(--line); margin-top: 48px; padding: 40px 0 26px; }
.foot-grid { display: grid; gap: 28px 22px; }
@media (min-width: 780px) { .foot-grid { grid-template-columns: 1.4fr 0.9fr 1fr 1.1fr 1.3fr; } }
/* The drawn logo again (2026-08-15) — see `.brand` for why this is a background
   image and why the type stays in the markup. Bigger here because the footer has
   the room the header does not, and this is where the mark is looked AT rather
   than glanced past. */
.foot-mark {
  display: block; width: 210px; height: 60px; margin: 0 0 12px;
  text-indent: -9999px; overflow: hidden; white-space: nowrap;
  background: url("/media/logo/logo-light.svg") left center / contain no-repeat;
}
@media (prefers-color-scheme: dark) {
  :root:not([data-theme="light"]) .foot-mark { background-image: url("/media/logo/logo-dark.svg"); }
}
:root[data-theme="dark"] .foot-mark { background-image: url("/media/logo/logo-dark.svg"); }
/* `.foot-tag` and `.foot-made` used to be here — the tagline under the mark and
   "made with heart by VANA Digital" as a sentence. Both went on 2026-08-15: the
   tagline was the third place on every page to say what LunarKiri is, and the
   credit is now VANA's actual logo, matching the signed-in app's footer.

   The story link is `.foot-col a`'s colour and weight without being in a column,
   so the brand block reads as part of the same footer rather than as loose text. */
.foot-story { display: inline-block; margin: 0 0 16px; color: var(--link); font-weight: 700; font-size: 0.9rem; text-decoration: none; }
.foot-story:hover { text-decoration: underline; }
.foot-by { display: flex; flex-direction: column; align-items: flex-start; gap: 8px; }
.foot-by-text { font-size: 0.85rem; color: var(--muted); line-height: 1.2; }
/* `currentColor` on the mark, so the one `color` here is the whole day/night story:
   the app carries a `body.dark … path{fill:#FFFFFF}` override instead, which is a
   second place to keep in step and does not follow the supporter themes. `--ink`
   already flips, so this needs no dark rule at all. */
.foot-vana { display: inline-flex; align-items: center; color: var(--ink); transition: opacity .12s; }
.foot-vana:hover { opacity: 0.65; }
.foot-vana .vana-logo { display: block; height: 22px; width: auto; }
.foot-col { display: flex; flex-direction: column; gap: 8px; align-items: flex-start; }
.foot-col h2 {
  font-family: var(--font-body); font-size: 0.72rem; font-weight: 800;
  letter-spacing: 0.09em; text-transform: uppercase; color: var(--ink); margin: 0 0 4px;
}
.foot-col a { color: var(--link); font-weight: 700; font-size: 0.9rem; text-decoration: none; overflow-wrap: anywhere; }
.foot-col a:hover { text-decoration: underline; }
.foot-bottom {
  margin-top: 34px; padding-top: 16px; border-top: 1px solid var(--line);
  display: flex; flex-wrap: wrap; gap: 8px 28px; justify-content: space-between;
  font-size: 0.8rem; color: var(--muted);
}
.foot-bottom p { margin: 0; max-width: 46em; }

.skip {
  position: absolute; left: -9999px; top: 0;
  background: var(--card); color: var(--ink); padding: 10px 16px; z-index: 10;
}
.skip:focus { left: 8px; top: 8px; }

/* ================================================================================
   THE NIGHT SKY, AND THE MARKETING SECTION LIBRARY (2026-08-09)

   Everything below styles the hand-composed marketing pages generated by
   tools/build-public-marketing.js, plus the site-wide background decor. The section
   classes are a LIBRARY: each `.m-*` section is self-contained, assumes nothing
   about what sits above or below it, and can be moved between pages by moving its
   markup. Prefix `m-` keeps them clearly apart from the generated-reference classes
   above.
   ================================================================================ */

/* --- the night sky (and the daytime sparkles) -----------------------------------
   The app draws these with renderDecor() in app/app/theme.js because it has a theme
   switcher; the public site has none, so the same glyphs ship as static markup in
   the page shell (tools/lib/public-page.js) and CSS decides which set shows. The
   positions, sizes and opacities are DECOR.dark and DECOR.light from
   app/app/theme.js:17, copied exactly — the same product should not be two
   different nights. Tints are the brand tokens above, matching the app's choice:
   moon-glow stars at night, moon-deep suns by day. */
.decor {
  position: fixed; inset: 0; overflow: hidden;
  pointer-events: none; user-select: none; z-index: 0;
}
.decor span { position: absolute; line-height: 1; }
.decor .dk { display: none; color: var(--moon-glow); }
.decor .lt { color: var(--moon-deep); }
.decor .d1 { left: 8%;  top: 13%; font-size: 22px; opacity: .35; }
.decor .d2 { left: 88%; top: 9%;  font-size: 14px; opacity: .30; }
.decor .d3 { left: 70%; top: 25%; font-size: 26px; opacity: .28; }
.decor .d4 { left: 31%; top: 7%;  font-size: 22px; opacity: .40; }
.decor .d5 { left: 92%; top: 58%; font-size: 12px; opacity: .25; }
.decor .d6 { left: 6%;  top: 69%; font-size: 20px; opacity: .30; }
.decor .d7 { left: 50%; top: 88%; font-size: 16px; opacity: .22; }
.decor .d8 { left: 20%; top: 50%; font-size: 10px; opacity: .20; }
.decor .d9 { left: 78%; top: 80%; font-size: 24px; opacity: .26; }
.decor .l1 { left: 86%; top: 11%; font-size: 30px; opacity: .13; }
.decor .l2 { left: 10%; top: 19%; font-size: 16px; opacity: .10; }
.decor .l3 { left: 70%; top: 80%; font-size: 20px; opacity: .10; }
.decor .l4 { left: 22%; top: 73%; font-size: 12px; opacity: .10; }
.decor .l5 { left: 8%;  top: 58%; font-size: 20px; opacity: .09; }
.decor .l6 { left: 90%; top: 50%; font-size: 14px; opacity: .10; }
/* Same two-way switch as the palette above: the media query is the default, the
   attribute is the stored choice. Swapping the sky is what makes the toggle feel
   like the app's rather than like a filter — the app does exactly this. */
@media (prefers-color-scheme: dark) {
  :root:not([data-theme="light"]) .decor .dk { display: block; }
  :root:not([data-theme="light"]) .decor .lt { display: none; }
}
:root[data-theme="dark"] .decor .dk { display: block; }
:root[data-theme="dark"] .decor .lt { display: none; }
/* The content sits above the sky — same layering as app/css/app.css:251. The header
   is deliberately NOT in this list: it is sticky at z-index 40 (the chrome pass,
   2026-08-09), and re-declaring it here at z-index 1 put the open Guide panel UNDER
   the page text — same selector, later in the file, silently wins. */
main, .site-foot { position: relative; z-index: 1; }

/* --- the marketing canvas -------------------------------------------------------
   Marketing pages use <main class="m-main"> with NO outer .wrap: each section owns
   the full page width so it can paint a band, and centres its own content with
   .m-wrap. Reference pages keep .wrap and never see any of this. */
.m-main { padding: 0 0 10px; }
.m-wrap { max-width: 1080px; margin: 0 auto; padding: 0 20px; }
.m-main .transnotice { max-width: 1040px; margin: 18px auto 0; }
.m-sec { padding: clamp(44px, 7vw, 76px) 0; }
/* The alternating band. Gold text on it takes --link-tinted, with the tinted-surface
   list near the top of this file. */
.m-sec.tint { background: var(--band); }
.m-sec.on-night { background: var(--night); }
/* Section header block: kicker + heading + standfirst. Shared rhythm. */
.m-kicker {
  font-size: 0.8rem; font-weight: 800; letter-spacing: 0.14em; text-transform: uppercase;
  color: var(--link); margin: 0 0 10px;
}
.m-h2 { font-size: clamp(1.6rem, 3.2vw, 2.1rem); margin: 0 0 10px; }
.m-sub { font-size: 1.08rem; color: var(--muted); max-width: 44em; margin: 0 0 30px; }
.m-note { font-size: 0.92rem; color: var(--muted); margin: 14px 0 0; }

/* Secondary CTA — the brand guide's outline button, in the AA gold rather than
   moon-deep (2:1 on cream is below what button text is allowed). */
.cta-ghost {
  display: inline-block; background: transparent; color: var(--link);
  border: 1.5px solid var(--link); padding: 11.5px 24px; border-radius: 999px;
  text-decoration: none; font-weight: 700;
}
.cta-ghost:hover { background: var(--band); }

/* --- 1 - hero -------------------------------------------------------------------
   Text + media slot, media below on a phone. `.compact` is the about/contact
   variant: one column, no media, tighter. */
.m-hero { padding: clamp(40px, 6vw, 72px) 0 clamp(36px, 5vw, 60px); }
.m-hero-grid { display: grid; gap: 34px; align-items: center; }
@media (min-width: 900px) { .m-hero-grid { grid-template-columns: 1.1fr 0.9fr; gap: 48px; } }
.m-hero h1 {
  font-size: clamp(2.3rem, 5.2vw, 3.6rem); line-height: 1.04;
  letter-spacing: -0.015em; margin: 0 0 16px;
}
.m-hero .m-lead { font-size: clamp(1.08rem, 1.6vw, 1.22rem); color: var(--muted); max-width: 36em; margin: 0 0 26px; }
.m-hero .cta-row { margin: 0 0 14px; }
.m-hero .callout { margin: 26px 0 0; max-width: 620px; }
.m-hero.compact { text-align: center; }
.m-hero.compact h1 { font-size: clamp(2.1rem, 4.5vw, 3rem); }
.m-hero.compact .m-lead, .m-hero.compact .m-kicker { margin-left: auto; margin-right: auto; }
.m-hero.compact .cta-row { justify-content: center; }
.m-hero.compact .callout { margin-left: auto; margin-right: auto; text-align: left; }
/* The little moon — the app's own hero SVG, inline in the markup. */
.m-moon { width: 74px; height: 74px; margin: 0 auto 18px; }
.m-moon svg { width: 100%; height: 100%; display: block; }

/* --- 2 - stat strip ------------------------------------------------------------- */
.m-stats { list-style: none; margin: 0; padding: 0; display: grid; gap: 14px; grid-template-columns: repeat(2, 1fr); }
@media (min-width: 700px)  { .m-stats { grid-template-columns: repeat(3, 1fr); } }
@media (min-width: 1000px) { .m-stats { grid-template-columns: repeat(6, 1fr); } }
.m-stats li { background: var(--card); border: 1px solid var(--line); border-radius: 16px; padding: 18px 16px 14px; }
.m-stat-n {
  display: block; font-family: var(--font-display); font-weight: 600;
  font-size: clamp(1.9rem, 3vw, 2.4rem); line-height: 1; color: var(--accent-text);
  font-variant-numeric: tabular-nums;
}
.m-stat-l { display: block; margin-top: 6px; font-size: 0.92rem; line-height: 1.35; }
.m-stat-s { display: block; margin-top: 3px; font-size: 0.8rem; color: var(--muted); }

/* --- 3 - large landscape video --------------------------------------------------
   Ships as a .ph media slot until the asset exists; swapping in a real <video>
   changes nothing else. The caption is part of the markup, not injected. */
.m-video figure { margin: 0; }
.m-video .ph { border-radius: 20px; }
.m-video figcaption { margin-top: 12px; font-size: 0.95rem; color: var(--muted); max-width: 54em; }

/* --- 4 - split text + media ------------------------------------------------------
   Alternating sides on repeat use: `.flip` swaps the columns on wide screens only —
   in the source (and on phones) the text always comes first. */
.m-split { display: grid; gap: 30px; align-items: center; }
@media (min-width: 840px) {
  .m-split { grid-template-columns: 1fr 1fr; gap: 56px; }
  .m-split.flip .m-split-media { order: -1; }
}
.m-split h2 { font-size: clamp(1.5rem, 2.6vw, 1.9rem); margin: 0 0 10px; }
.m-split p { margin: 0 0 14px; max-width: 34em; }
.m-split .m-more { font-weight: 700; }
.m-split-media .ph { border-radius: 18px; }

/* --- 5 - feature card grid ------------------------------------------------------- */
.m-cards { list-style: none; margin: 0; padding: 0; display: grid; gap: 16px; grid-template-columns: repeat(auto-fit, minmax(min(250px, 100%), 1fr)); }
.m-cards li { background: var(--card); border: 1px solid var(--line); border-radius: 16px; padding: 22px; }
.m-card-ic {
  display: inline-grid; place-items: center; width: 44px; height: 44px;
  background: var(--band); border-radius: 12px; font-size: 1.35rem; margin-bottom: 12px;
}
.m-sec.tint .m-card-ic { background: var(--cream); }
.m-cards h3 { margin: 0 0 6px; font-size: 1.08rem; }
.m-cards p { margin: 0; font-size: 0.98rem; }

/* --- 57 - the whole feature list ---------------------------------------------------
   Rows, not cards, and the difference is the length of the list. Thirty-five cards is a
   wall the eye scans by icon; thirty-five rows is a list somebody reads. The icon sits
   BESIDE the text rather than above it, which is what makes a row a row — and what keeps
   the whole band roughly the height of a long article instead of a poster.

   Two columns from 760, and no `auto-fit`. A fitting grid would put three or four columns
   on a wide screen and hand each description a 250 px measure, which is the narrow
   wrapping this shape exists to avoid. Two is the most that keeps a readable line. */
.m-feats { list-style: none; margin: 0; padding: 0; display: grid; gap: 14px; }
.m-feats li {
  display: flex; align-items: flex-start; gap: 14px;
  background: var(--card); border: 1px solid var(--line); border-radius: 16px; padding: 16px 18px;
}
.m-feat-ic {
  flex: 0 0 auto; display: inline-grid; place-items: center; width: 40px; height: 40px;
  background: var(--band); border-radius: 11px; font-size: 1.2rem;
}
.m-sec.tint .m-feat-ic { background: var(--cream); }
.m-feat-body { min-width: 0; }
.m-feats h3 { margin: 0 0 4px; font-size: 1.02rem; }
.m-feats p { margin: 0; font-size: 0.95rem; color: var(--muted); }
@media (min-width: 760px) {
  .m-feats { grid-template-columns: 1fr 1fr; gap: 16px; }
}

/* --- 58 - the supporters wall (ROADMAP 5.4.5) -------------------------------------
   A grid of names and nothing else: no avatars, no amounts, no ordering by size. Every
   tile is identical because every name here did the same thing — asked to be on the
   page. `auto-fill` rather than `auto-fit` so a wall of three names stays three tiles
   at their natural width instead of stretching each one across a desktop.

   The empty state is a paragraph, not a tile: "nobody yet" is a sentence, and drawing
   it as an item in the grid would read as somebody called nobody. */
/* REDRAWN 2026-08-16 (owner: "you should feel proud to be there"). It was the feature
   list's grey card with a name in it — correct, identical for everybody, and completely
   flat. The names sit in an actual night sky now: the brand moon in the corner, the site's
   own star glyphs behind them, each name on a hairline gold plaque in the display face.

   THE PLAQUES CARRY NO FILL, and that is a contrast decision rather than a look.
   `tests/public-contrast.test.js` freezes `--moon-glow` on `--night` as a captured
   pairing in both schemes; a translucent fill would make the real composited surface
   something slightly lighter than `--night` and the frozen row would stop being literally
   true. Border-only keeps it true, so this redraw needed no re-sweep. The corner glow is
   the one gradient and it ends well before any text.

   `--night` in BOTH schemes, like `.m-sec.on-night` — a wall of thanks is a night sky
   whichever way the reader has the site set, and the two inks used on it are the two the
   table already carries. */
.m-wall-sky {
  position: relative; overflow: hidden; isolation: isolate;
  border-radius: 28px; border: 1px solid var(--line);
  background: var(--night);
  background-image: radial-gradient(42% 52% at 88% 4%, rgba(242, 208, 107, 0.17), transparent 70%);
  /* The top padding is the MOON'S BAND, not breathing room. The moon is absolutely
     positioned in the corner and the plaques are a centred wrap, so a first row wide
     enough to reach the right edge runs straight under it — which is what happened with
     twelve names at 1280 px. Reserving the moon's own height above the names fixes it for
     every count instead of for the one that was on screen. */
  padding: clamp(86px, 11vw, 128px) clamp(20px, 5vw, 56px) clamp(34px, 6vw, 68px);
}
.m-wall-moon {
  position: absolute; z-index: 0;
  right: clamp(18px, 4vw, 46px); top: clamp(16px, 3.5vw, 38px);
  width: clamp(44px, 7vw, 76px); opacity: 0.92;
}
.m-wall-moon svg { display: block; width: 100%; height: auto; }
/* Positions and opacities echo `.decor` (DECOR.dark in app/app/theme.js, copied exactly
   into this stylesheet) so the panel's sky and the page's sky are the same sky. They stay
   clear of the centre, where the plaques are. */
.m-wall-stars { position: absolute; inset: 0; z-index: 0; pointer-events: none; }
.m-wall-stars span { position: absolute; color: var(--moon-glow); line-height: 1; }
.m-wall-stars .s1 { left: 7%;  top: 16%; font-size: 20px; opacity: 0.34; }
.m-wall-stars .s2 { left: 17%; top: 74%; font-size: 13px; opacity: 0.26; }
.m-wall-stars .s3 { left: 33%; top: 9%;  font-size: 14px; opacity: 0.30; }
.m-wall-stars .s4 { left: 47%; top: 88%; font-size: 22px; opacity: 0.20; }
.m-wall-stars .s5 { left: 62%; top: 13%; font-size: 11px; opacity: 0.28; }
.m-wall-stars .s6 { left: 80%; top: 82%; font-size: 18px; opacity: 0.26; }
.m-wall-stars .s7 { left: 92%; top: 46%; font-size: 20px; opacity: 0.22; }
.m-wall-stars .s8 { left: 5%;  top: 48%; font-size: 12px; opacity: 0.24; }
.m-wall-body { position: relative; z-index: 1; }
/* A centred wrap, NOT a column grid. Three names should read as three names on a wall
   rather than as three cells of a mostly empty table, and `justify-content: center` is
   what makes one name look deliberate instead of left-over. */
.m-wall-names {
  list-style: none; margin: 0; padding: 0;
  display: flex; flex-wrap: wrap; justify-content: center; gap: 12px;
}
.m-wall-names li {
  display: inline-flex; align-items: center; gap: 10px;
  padding: 12px 22px; border-radius: 999px;
  border: 1px solid rgba(251, 235, 176, 0.26);
  color: var(--moon-glow);
  font-family: var(--font-display); font-weight: 500; font-size: 1.05rem;
  line-height: 1.25; max-width: 100%; overflow-wrap: anywhere;
}
.m-wall-mark { color: var(--moon-deep); font-size: 0.72em; flex-shrink: 0; }
/* The empty state is a SENTENCE, deliberately — a ghost "your name here" plaque was
   considered and rejected, because it advertises a vacancy and this page's locked
   decision is that it must never make the next person feel bought. */
.m-wall-empty {
  margin: 0 auto; max-width: 46ch; text-align: center;
  color: var(--on-night); font-size: 1.02rem; line-height: 1.6;
}
.m-wall-empty a { color: var(--moon-glow); }

/* --- 6 - numbered step cards ----------------------------------------------------- */
.m-steps { list-style: none; margin: 0; padding: 0; counter-reset: step; display: grid; gap: 16px; grid-template-columns: repeat(auto-fit, minmax(min(230px, 100%), 1fr)); }
.m-steps li { counter-increment: step; background: var(--card); border: 1px solid var(--line); border-radius: 16px; padding: 20px; }
.m-steps li::before {
  content: counter(step); display: inline-grid; place-items: center;
  width: 40px; height: 40px; border-radius: 50%;
  background: var(--accent); color: var(--accent-ink);
  font-family: var(--font-display); font-weight: 600; font-size: 1.25rem;
  margin-bottom: 12px;
}
.m-steps h3 { margin: 0 0 6px; font-size: 1.05rem; }
.m-steps p { margin: 0; font-size: 0.97rem; }

/* --- 7 - carousel (Swiper) -------------------------------------------------------
   The slides are complete in the markup; assets/marketing.js hands them to Swiper
   when it can. UNTIL it does — or with JavaScript off — the strip is a native
   horizontal scroller, so every slide stays reachable with no script at all. */
.m-shots .swiper { --swiper-navigation-color: var(--link); --swiper-pagination-color: var(--link); --swiper-navigation-size: 28px; }
.m-shots .swiper-slide { width: min(300px, 72vw); }
.m-shots figure { margin: 0; }
.m-shots .ph { border-radius: 16px; }
.m-shots figcaption { margin-top: 8px; font-size: 0.9rem; color: var(--muted); }
.m-shots .swiper-initialized { padding-bottom: 38px; }
.swiper:not(.swiper-initialized) { overflow-x: auto; padding-bottom: 14px; scroll-snap-type: x proximity; }
.swiper:not(.swiper-initialized) .swiper-wrapper { gap: 16px; }
.swiper:not(.swiper-initialized) .swiper-slide { scroll-snap-align: center; }
.swiper:not(.swiper-initialized) .swiper-button-prev,
.swiper:not(.swiper-initialized) .swiper-button-next,
.swiper:not(.swiper-initialized) .swiper-pagination { display: none; }
@media (hover: none), (max-width: 700px) {
  .m-shots .swiper-button-prev, .m-shots .swiper-button-next { display: none; }
}

/* --- 8 - written testimonials ---------------------------------------------------- */
.m-quotes { display: grid; gap: 16px; grid-template-columns: repeat(auto-fit, minmax(min(260px, 100%), 1fr)); }
.m-quotes blockquote { margin: 0; background: var(--card); border: 1px solid var(--line); border-radius: 16px; padding: 22px; display: flex; flex-direction: column; gap: 16px; }
.m-quotes blockquote::before { content: "\201C"; font-family: var(--font-display); font-size: 2.6rem; line-height: 0.6; color: var(--moon-deep); }
.m-quotes p { margin: 0; flex: 1; }
.m-quotes footer { display: flex; align-items: center; gap: 12px; font-size: 0.9rem; color: var(--muted); }
.m-quotes footer .ph { width: 44px; flex: 0 0 44px; }
.m-quotes footer .ph .ph-ic { font-size: 1.1rem; }
.m-quotes footer .ph .ph-label { display: none; }

/* --- 9 - video testimonials ------------------------------------------------------ */
.m-vquotes { display: grid; gap: 16px; grid-template-columns: repeat(auto-fit, minmax(min(240px, 100%), 1fr)); }
.m-vquotes figure { margin: 0; }
.m-vquotes .m-vthumb { position: relative; }
.m-vquotes .ph { border-radius: 16px; }
.m-play {
  position: absolute; inset: 0; display: grid; place-items: center; pointer-events: none;
}
.m-play::before {
  content: "\25B6"; display: grid; place-items: center; padding-left: 4px;
  width: 54px; height: 54px; border-radius: 50%;
  background: var(--accent); color: var(--accent-ink); font-size: 1.15rem;
  box-shadow: 0 6px 18px rgba(17, 23, 34, 0.25);
}
.m-vquotes figcaption { margin-top: 8px; font-size: 0.9rem; color: var(--muted); }

/* --- 10 - horizontal timeline ----------------------------------------------------
   A left rail on phones; from 860px the rail rotates to run along the top. */
.m-timeline { list-style: none; margin: 0; padding: 0; position: relative; }
.m-timeline li { position: relative; padding: 0 0 26px 26px; }
.m-timeline li::before {
  content: ""; position: absolute; left: 0; top: 10px; bottom: -10px;
  width: 2px; background: var(--moon-deep); opacity: 0.5;
}
.m-timeline li:last-child::before { display: none; }
.m-timeline li::after {
  content: ""; position: absolute; left: -5px; top: 6px; width: 12px; height: 12px;
  border-radius: 50%; background: var(--accent); border: 2px solid var(--moon-deep);
}
.m-timeline h3 { margin: 0 0 4px; font-size: 1.02rem; }
.m-timeline p { margin: 0; font-size: 0.95rem; color: var(--muted); }
@media (min-width: 860px) {
  .m-timeline { display: grid; grid-auto-flow: column; grid-auto-columns: 1fr; gap: 22px; }
  .m-timeline li { padding: 34px 6px 0 0; }
  .m-timeline li::before { left: 12px; right: -22px; top: 5px; bottom: auto; width: auto; height: 2px; }
  .m-timeline li::after { left: 0; top: 0; }
}

/* --- 11 - vertical timeline ------------------------------------------------------
   The section the brief flags as the first to break on a phone, so the phone IS the
   base design: a single left rail, everything full width. From 820px the rail moves
   to the centre and entries alternate sides. */
.m-tlv { list-style: none; margin: 0; padding: 0; position: relative; }
.m-tlv::before {
  content: ""; position: absolute; left: 9px; top: 6px; bottom: 6px; width: 2px;
  background: var(--moon-deep); opacity: 0.5;
}
.m-tlv li { position: relative; padding: 0 0 24px 34px; }
.m-tlv li:last-child { padding-bottom: 0; }
.m-tlv li::before {
  content: ""; position: absolute; left: 3px; top: 5px; width: 14px; height: 14px;
  border-radius: 50%; background: var(--accent); border: 2px solid var(--moon-deep);
}
.m-tlv-day {
  display: block; font-size: 0.78rem; font-weight: 800; letter-spacing: 0.1em;
  text-transform: uppercase; color: var(--link); margin-bottom: 2px;
}
.m-tlv h3 { margin: 0 0 4px; font-size: 1.05rem; }
.m-tlv p { margin: 0; font-size: 0.95rem; color: var(--muted); }
@media (min-width: 820px) {
  .m-tlv::before { left: 50%; margin-left: -1px; }
  .m-tlv li { width: 50%; padding: 0 44px 30px 0; text-align: right; }
  .m-tlv li::before { left: auto; right: -8px; }
  .m-tlv li:nth-child(even) { margin-left: 50%; padding: 0 0 30px 44px; text-align: left; }
  .m-tlv li:nth-child(even)::before { right: auto; left: -8px; }
}

/* --- 12 - accordion / FAQ --------------------------------------------------------
   <details>, like every disclosure on this site — the panels are in the markup and
   readable with no script. */
.m-faq { max-width: 760px; }
.m-faq details { background: var(--card); border: 1px solid var(--line); border-radius: 14px; padding: 2px 20px; margin: 0 0 10px; }
.m-faq summary {
  cursor: pointer; padding: 14px 0; font-weight: 700; list-style: none;
  display: flex; align-items: center; gap: 10px;
}
.m-faq summary::-webkit-details-marker { display: none; }
.m-faq summary::after { content: "\25BE"; margin-left: auto; color: var(--muted); transition: transform 120ms ease; }
.m-faq details[open] summary::after { transform: rotate(180deg); }
.m-faq details > p { margin: 0 0 16px; }

/* --- 13 - sources strip ---------------------------------------------------------- */
.m-src { list-style: none; margin: 0 0 14px; padding: 0; display: flex; flex-wrap: wrap; gap: 10px; }
.m-src li {
  background: var(--card); border: 1px solid var(--line); border-radius: 999px;
  padding: 9px 18px; font-weight: 700; font-size: 0.95rem;
}

/* --- 14 - myth versus fact ------------------------------------------------------- */
.m-myth { display: grid; gap: 14px; }
.m-myth-pair { display: grid; gap: 0; border: 1px solid var(--line); border-radius: 16px; overflow: hidden; }
@media (min-width: 720px) { .m-myth-pair { grid-template-columns: 1fr 1fr; } }
.m-myth-m, .m-myth-f { padding: 18px 20px; margin: 0; }
.m-myth-m { background: var(--danger-bg); }
.m-myth-f { background: var(--safe-bg); }
.m-myth-cap {
  display: block; font-size: 0.75rem; font-weight: 800; letter-spacing: 0.09em;
  text-transform: uppercase; margin-bottom: 6px;
}
.m-myth-m .m-myth-cap { color: var(--danger-ink); }
.m-myth-f .m-myth-cap { color: var(--safe-ink); }
.m-myth-m p, .m-myth-f p { margin: 0; font-size: 0.98rem; }

/* --- 15 - pull quote / statement band --------------------------------------------
   The brand lockup "on night": night surface, moon text, Fraunces italic (the
   variable files carry no italic axis, so the browser synthesises the slant — see
   app/css/fonts.css on why the italic faces were dropped). On the dark scheme the
   band is the same night blue, one step lighter than the page, with a seam. */
.m-pull { text-align: center; }
.m-pull blockquote { margin: 0 auto; max-width: 30em; }
/* Scoped to the blockquote: the band's follow-up note is a normal-size line, and an
   unscoped `.m-pull p` styled it into a second giant italic sentence. */
.m-pull blockquote p {
  font-family: var(--font-display); font-style: italic; font-weight: 400;
  font-size: clamp(1.45rem, 3vw, 2.1rem); line-height: 1.3; color: var(--moon-glow); margin: 0;
}
.m-pull .m-note { color: var(--on-night); }
.m-pull .m-note a { color: var(--moon-glow); }
@media (prefers-color-scheme: dark) { :root:not([data-theme="light"]) .m-sec.on-night { border-block: 1px solid var(--line); } }
:root[data-theme="dark"] .m-sec.on-night { border-block: 1px solid var(--line); }

/* --- 16 - closing call to action ------------------------------------------------- */
.m-close { text-align: center; }
.m-close .m-h2 { margin-bottom: 8px; }
.m-close .m-sub { margin: 0 auto 26px; }
.m-close .cta-row { justify-content: center; }

/* --- 17 - contact details -------------------------------------------------------- */
.m-contacts { list-style: none; margin: 0; padding: 0; display: grid; gap: 16px; grid-template-columns: repeat(auto-fit, minmax(min(240px, 100%), 1fr)); }
.m-contacts li { background: var(--card); border: 1px solid var(--line); border-radius: 16px; padding: 22px; }
.m-contacts h3 { margin: 0 0 4px; font-size: 1.02rem; }
.m-contacts .m-mail { display: inline-block; margin: 4px 0 8px; font-weight: 800; font-size: 1.05rem; overflow-wrap: anywhere; }
.m-contacts p { margin: 0; font-size: 0.93rem; color: var(--muted); }

/* --- 18 - contact form -----------------------------------------------------------
   Markup and styling only, and it must not LOOK functional: the submit control is
   disabled and the notice beside it says plainly that nothing is wired up yet.
   docs/ROADMAP.md "Owed by the marketing pages" records what making it work costs. */
.m-form { background: var(--card); border: 1px solid var(--line); border-radius: 20px; padding: clamp(22px, 4vw, 34px); max-width: 760px; }
.m-form-grid { display: grid; gap: 16px; }
@media (min-width: 640px) { .m-form-grid { grid-template-columns: 1fr 1fr; } }
.m-form-grid .m-field-full { grid-column: 1 / -1; }
.m-field label { display: block; font-weight: 700; font-size: 0.92rem; margin-bottom: 6px; }
.m-field input, .m-field select, .m-field textarea {
  width: 100%; padding: 12px 14px; border: 1px solid var(--line); border-radius: 10px;
  background: var(--cream); color: var(--ink); font: inherit;
}
.m-field textarea { min-height: 140px; resize: vertical; }
.m-form-foot { margin-top: 18px; display: flex; align-items: center; gap: 16px; flex-wrap: wrap; }
.m-form-foot button.cta { border: 0; cursor: pointer; }
.m-form-foot button.cta:disabled { opacity: 0.55; cursor: not-allowed; filter: none; }
.m-form-note { margin: 18px 0 0; }

/* Topic pillboxes (2026-08-09). A <fieldset> because three radios that share a name are
   one control and need one label; the browser default border/padding are stripped because
   the card around them is already the box. `legend` is styled to match .m-field label
   rather than inheriting the browser's, which sizes it off the parent font. */
.m-topics { margin-top: 16px; border: 0; padding: 0; min-width: 0; }
.m-topics legend { display: block; padding: 0; font-weight: 700; font-size: 0.92rem; margin-bottom: 8px; }
.m-pills { display: flex; flex-wrap: wrap; gap: 10px; }
.m-pill { position: relative; display: inline-flex; }
/* The input stays in the layout and stays focusable — it is only visually replaced by its
   own <span>. `opacity:0` over `display:none` on purpose: a display-none radio is removed
   from the accessibility tree and from keyboard navigation, which would leave the group
   unreachable without a mouse. Arrow keys still move between them. */
.m-pill input { position: absolute; inset: 0; width: 100%; height: 100%; margin: 0; opacity: 0; cursor: pointer; }
/* `--cream` and `--ink` swap between the schemes (`:167`), so this one pair gives a
   recessed chip in light and in dark without a second rule: unselected sits on the page
   colour, one step off the white card under it; selected inverts to the page's text
   colour with the page colour as its label. That is the highest-contrast pairing the
   palette has (#111722 on #f2efe7, about 15:1) in both directions.

   THIS WAS WRONG WHEN IT SHIPPED HERE FIRST and the browser is what caught it: both rules
   named `var(--bg)`, which is not a token this stylesheet defines. An undefined custom
   property falls back to the INHERITED value, so `color: var(--bg)` resolved to `--ink` —
   the same colour as the background beside it — and the selected pill rendered as a blank
   cream lozenge with its label invisible. tests/public-contrast.test.js did not see it:
   it measures a frozen list of observed pairings, and a pairing introduced today is not
   on that list until the sweep is re-run. */
.m-pill span {
  display: inline-block; padding: 10px 16px; border: 1px solid var(--line);
  border-radius: 999px; background: var(--cream); font-size: 0.94rem; font-weight: 600;
  line-height: 1.2; transition: background .15s, border-color .15s, color .15s;
}
.m-pill input:checked + span { background: var(--ink); color: var(--cream); border-color: var(--ink); }
/* Focus lands on the input, which is `opacity: 0` — and opacity takes the outline with it,
   so the site-wide `input:focus-visible` ring at `:238` is drawn and then rendered
   invisible. The ring therefore has to be repainted on the span the input controls.
   :focus-visible only, so a mouse click does not leave a ring behind.

   `--link` and 3px, matching that site-wide rule rather than inventing a value: the note
   above it records that the gold clears the 3:1 a focus indicator needs in both schemes,
   and a focus ring that is a different colour on one control is a worse answer than a
   consistent one. **This said `2px solid var(--moon)` when it was written, and `--moon` is
   not a token this stylesheet defines** — `--moon-glow` and `--moon-deep` are. An
   undefined var makes the whole shorthand invalid, so the declaration was dropped and
   keyboard users had no visible focus at all. Second token invented in one session; the
   first was `--bg` immediately above. Check the `:root` block before naming a token. */
.m-pill input:focus-visible + span { outline: 3px solid var(--link); outline-offset: 2px; }
.m-pill:hover input:not(:checked) + span { border-color: var(--ink); }

/* The honeypot (tools/lib/sections/form.js). Off-screen rather than display:none, so a
   bot that deliberately skips hidden fields still fills it. Never remove this rule
   thinking it is dead styling — without it the field is VISIBLE and every human fills it,
   which fails every submission closed. */
.m-hp { position: absolute; left: -9999px; width: 1px; height: 1px; overflow: hidden; }

/* ============================================================================
   SECTION LIBRARY — step 5 (docs/SECTION_LIBRARY.md §14, 2026-08-09)

   The 38 Figma-audit sections, added to the library batch by batch. None is on a
   published page yet (that is step 6); they render in tools/sections.html. The
   visual bar is THE APP, not the six pages that exist (§14.5). What that means
   here, measured off the running app and the brand guide:

     - boxed items are the app's card: a `--card` surface on a 1px `--line`
       hairline, radius 16px, padding 18–22px, and NO box-shadow anywhere;
     - row-like content inside one card uses hairline separators, not nested
       boxes (the app's routine list and training steps both work this way);
     - item gaps are 14–16px — the app stacks its cards at 14px;
     - text on imagery always sits on a night-based scrim and uses the measured
       on-night inks (`--moon-glow`, `--on-night`), identical in both schemes;
     - sky-blue and lime stay small accents (brand guide §2 "don't").

   Every colour below is an existing token, and every text-on-surface pairing is
   one tests/public-contrast.test.js already measures — new sections introduce no
   new pairings, so composing one onto a page (step 6) starts from known-good
   contrast. A section that paints `--band` itself joins the `--link-tinted`
   switch list near the top of this file.
   ============================================================================ */

/* --- 19 · hero-stack / hero-stack-left (c1-1, c1-2) -----------------------------
   Stacked hero: heading, lead, CTAs, then the near-full-width media. `left` is the
   c1-2 alignment variant and the only difference between the two sections. */
.m-herostack { text-align: center; }
.m-herostack h1 {
  font-size: clamp(2.2rem, 4.8vw, 3.3rem); line-height: 1.05;
  letter-spacing: -0.015em; margin: 0 0 16px;
}
.m-herostack .m-lead { font-size: clamp(1.08rem, 1.6vw, 1.2rem); color: var(--muted); max-width: 38em; margin: 0 auto 4px; }
.m-herostack .cta-row { justify-content: center; }
.m-herostack .m-hs-media { margin-top: clamp(26px, 4vw, 40px); }
.m-herostack .m-hs-media .ph { border-radius: 20px; }
.m-herostack.left { text-align: left; }
.m-herostack.left .m-lead { margin-left: 0; }
.m-herostack.left .cta-row { justify-content: flex-start; }

/* --- 20 · overlay-lines (c1-3) ---------------------------------------------------
   Display lines crossing a portrait media block and overflowing past both its
   edges. THE SCRIM IS PER LINE: each span is a solid night pill under moon-glow
   text — a translucent wash over a light .ph would not clear the contrast floor,
   and the pills keep the "lines crossing the picture" read the capture shows. */
.m-ovl { position: relative; display: grid; justify-items: center; }
/* Narrower than the heading lines on purpose — the capture's whole idea is the
   text overflowing past both media edges. */
.m-ovl-media { width: min(330px, 72vw); }
.m-ovl-media .ph { border-radius: 22px; }
.m-ovl-text {
  position: absolute; inset: 0; display: grid; place-content: center;
  justify-items: center; gap: 18px; padding: 10px 0;
}
.m-ovl-h {
  display: flex; flex-direction: column; align-items: center; gap: 8px; margin: 0;
  font-size: clamp(1.7rem, 4.4vw, 2.7rem); line-height: 1.1; letter-spacing: -0.015em;
}
.m-ovl-h span {
  display: inline-block; background: var(--night); color: var(--moon-glow);
  padding: 0.08em 0.55em 0.16em; border-radius: 0.35em;
}

/* --- 21 · media-panel (c1-4) -----------------------------------------------------
   Text inside a full-bleed media panel. The scrim is near-solid night-deep —
   rgba() of #111722, the one panel colour identical in both schemes — so the
   interior text keeps the measured on-night pairings whatever the photograph is.
   --link is re-pointed at moon-glow inside, so a linkified line cannot render the
   daytime gold on the dark scrim. */
.m-panel {
  position: relative; border-radius: 24px; overflow: hidden;
  min-height: clamp(360px, 46vw, 540px); display: flex;
  background: var(--card);
}
/* The panel itself is the photo stand-in surface; the .ph keeps only its icon and
   label, lifted ABOVE the scrim so the reviewer can still read what asset belongs
   here. Swapping in a real image replaces the panel background, not the layout. */
.m-panel > .ph {
  position: absolute; inset: 0; height: 100%; aspect-ratio: auto;
  border-radius: 0; border: 0; background: none; color: var(--on-night); z-index: 1;
  /* BOTTOM-RIGHT, not centred, and this is a real defect rather than a preference.
     `.ph` centres its icon and label; here the .ph fills the whole panel and the COPY is
     laid on top of it, so centred put "SCREENSHOT — THE EMERGENCY CARD" straight through
     the middle of the sentence. Found at 1280 px the first time this section was
     composed. Cornering it keeps the reviewer's information — which is the whole reason
     §13.6 renders empty slots at all — without it colliding with the text it is behind.
     Three other sections solved the same collision by hiding the label outright
     (`.m-quotes footer`, `.m-ht-tile`, `.m-op-inset`); a full-bleed panel has room to
     keep it. */
  align-content: end; justify-items: end; padding: 0 clamp(18px, 3vw, 34px) 16px;
}
.m-panel-scrim { position: absolute; inset: 0; background: rgba(17, 23, 34, 0.85); }
.m-panel-text {
  --link: var(--moon-glow);
  position: relative; z-index: 2; width: 100%;
  display: flex; flex-direction: column; justify-content: space-between;
  gap: clamp(40px, 8vw, 110px); padding: clamp(22px, 4.5vw, 44px);
}
.m-panel-text h1, .m-panel-text h2 {
  color: var(--moon-glow); font-size: clamp(1.7rem, 3.6vw, 2.6rem);
  line-height: 1.12; margin: 0; max-width: 18em;
}
.m-panel-sub { color: var(--on-night); max-width: 34em; margin: 0 0 10px; }
.m-panel-foot .cta { margin-top: 10px; }

/* --- 22 · hero-square (c1-5) -----------------------------------------------------
   Centred heading + CTA over a constrained rounded-square media block, on `.ph.tile`
   with the radius stepped up to the app's photo-tile 28px. */
.m-herosq { text-align: center; }
.m-herosq h1 {
  font-size: clamp(2.1rem, 4.2vw, 3rem); line-height: 1.08;
  letter-spacing: -0.015em; margin: 0 0 18px; }
.m-herosq .cta-row { justify-content: center; margin: 0; }
.m-herosq .ph.tile { width: min(300px, 72vw); margin: clamp(24px, 4vw, 36px) auto 0; border-radius: 28px; }

/* --- 23 · display-statement (c1-6) -----------------------------------------------
   Three staggered heavy display lines, then a small centred sub cluster. The stagger
   is the design: the alternating offsets keep the block deliberately ragged, and they
   are fractions of the viewport so the raggedness survives every width. */
.m-display { text-align: center; }
.m-display-h {
  display: flex; flex-direction: column; align-items: center; margin: 0 0 26px;
  font-size: clamp(2.2rem, 6vw, 4rem); line-height: 1.04; letter-spacing: -0.02em;
}
.m-display-h span:nth-child(1) { transform: translateX(min(5vw, 56px)); }
.m-display-h span:nth-child(2) { transform: translateX(max(-4vw, -46px)); }
.m-display-h span:nth-child(3) { transform: translateX(min(2.5vw, 30px)); }
.m-display-sub { color: var(--muted); max-width: 30em; margin: 0 auto; }

/* --- 24 · hero-tiles (c1-7) ------------------------------------------------------
   Centred hero with four decorative tiles bleeding off the side edges. The section
   clips its own bleed (overflow hidden) so the page can never scroll sideways, and
   the tiles vanish below 900 px — the capture's mobile column drops them entirely. */
.m-herotiles { position: relative; overflow: hidden; text-align: center; }
.m-herotiles h1 {
  font-size: clamp(2.2rem, 4.6vw, 3.2rem); line-height: 1.06;
  letter-spacing: -0.015em; margin: 0 0 16px;
}
.m-herotiles .m-lead { font-size: clamp(1.08rem, 1.6vw, 1.2rem); color: var(--muted); max-width: 36em; margin: 0 auto 4px; }
.m-herotiles .cta-row { justify-content: center; }
.m-herotiles .m-ht-media { margin: clamp(26px, 4vw, 40px) auto 0; max-width: 760px; }
.m-herotiles .m-ht-media .ph { border-radius: 20px; }
.m-ht-tile { display: none; position: absolute; width: clamp(110px, 11vw, 150px); }
.m-ht-tile .ph { border-radius: 18px; }
.m-ht-tile .ph .ph-ic { font-size: 1.3rem; }
.m-ht-tile .ph .ph-label { display: none; }
@media (min-width: 900px) {
  .m-ht-tile { display: block; }
  .m-ht-tile.t1 { left: -34px; top: 12%; transform: rotate(-7deg); }
  .m-ht-tile.t2 { left: 3%; top: 58%; transform: rotate(4deg); }
  .m-ht-tile.t3 { right: -34px; top: 16%; transform: rotate(6deg); }
  .m-ht-tile.t4 { right: 3.5%; top: 60%; transform: rotate(-4deg); }
}

/* --- 25 · hero-media-first (c1-8) ------------------------------------------------
   The media leads: constrained landscape block, then heading, sub, CTA. */
.m-heromedia { text-align: center; }
.m-heromedia .m-hm-media { max-width: 640px; margin: 0 auto clamp(26px, 4vw, 38px); }
.m-heromedia .m-hm-media .ph { border-radius: 20px; }
.m-heromedia h1 {
  font-size: clamp(2.1rem, 4.2vw, 3rem); line-height: 1.08;
  letter-spacing: -0.015em; margin: 0 0 14px;
}
.m-heromedia .m-lead { font-size: clamp(1.08rem, 1.6vw, 1.2rem); color: var(--muted); max-width: 36em; margin: 0 auto 4px; }
.m-heromedia .cta-row { justify-content: center; }

/* --- 26 · intro-media-pair (c2-1) ------------------------------------------------
   Heading | standfirst+CTA over two equal media tiles. The header keeps the app's
   heading-beside-prose rhythm; the tiles share one row above 840 px. */
.m-id-head { display: grid; gap: 10px 56px; margin-bottom: clamp(24px, 4vw, 36px); }
.m-id-head h2 {
  font-size: clamp(1.6rem, 3vw, 2.1rem); line-height: 1.18;
  letter-spacing: -0.01em; margin: 0;
}
.m-id-side p { margin: 0 0 12px; color: var(--muted); max-width: 34em; }
.m-introduo .m-more { font-weight: 700; margin: 0; }
.m-id-media { display: grid; gap: 16px; }
.m-id-media .ph { border-radius: 18px; }
@media (min-width: 840px) {
  .m-id-head { grid-template-columns: 1.05fr 0.95fr; align-items: start; }
  .m-id-media { grid-template-columns: 1fr 1fr; }
}

/* --- 27 · cards-media-cta (c2-2) -------------------------------------------------
   Two cards, each heading → body → media → its own CTA. App card treatment: --card
   on the --line hairline, 16 px radius, 22 px padding, no shadow; the CTA sits at
   the card's foot whatever the body length, which is the flex column's job. */
.m-cc-grid { list-style: none; margin: 0; padding: 0; display: grid; gap: 16px; }
@media (min-width: 840px) { .m-cc-grid { grid-template-columns: 1fr 1fr; } }
.m-cc-grid li {
  background: var(--card); border: 1px solid var(--line); border-radius: 16px;
  padding: 22px; display: flex; flex-direction: column; gap: 12px;
}
.m-sec.tint .m-cc-grid li { background: var(--cream); }
.m-cc-grid h3 { margin: 0; font-size: 1.15rem; }
.m-cc-grid p { margin: 0; }
.m-cc-grid .ph { border-radius: 12px; }
.m-cc-cta { margin-top: auto; }

/* --- 28 · split-overlap (c2-4) ---------------------------------------------------
   Portrait media with a smaller square lapping its top-right corner, text beside it
   with the heading pinned top and the sub + CTA pinned bottom. The inset is
   positioned against the media pair, so the lap survives the mobile stack (§9). */
.m-op-grid { display: grid; gap: 34px; }
@media (min-width: 840px) {
  .m-op-grid { grid-template-columns: 1fr 1fr; gap: 56px; }
  .m-ovpair.flip .m-op-media { order: 2; }
  .m-op-text { display: flex; flex-direction: column; justify-content: space-between; gap: 40px; }
}
.m-op-media { position: relative; margin: 0 44px 30px 0; }
.m-op-media > .ph { border-radius: 18px; }
.m-op-inset { position: absolute; top: -22px; right: -38px; width: min(38%, 150px); }
.m-op-inset .ph .ph-label { display: none; }
.m-op-text h2 { font-size: clamp(1.5rem, 2.6vw, 1.9rem); margin: 0 0 14px; }
.m-op-foot p { margin: 0 0 12px; max-width: 34em; color: var(--muted); }
.m-ovpair .m-more { font-weight: 700; margin: 0; }

/* --- 29 · panel-split / panel-split-narrow (c2-5, c2-6) --------------------------
   A tinted text panel beside a full-height portrait media, at two mirrored ratios.
   ONE block, one modifier — §9 says the ratio drifts the moment they fork. The
   panel is `--band`, so `.m-ps-panel` sits on the --link-tinted switch list. */
.m-ps-grid { display: grid; gap: 16px; }
@media (min-width: 840px) {
  .m-pansplit .m-ps-grid { grid-template-columns: 2fr 1fr; }
  .m-pansplit.narrow .m-ps-grid { grid-template-columns: 1fr 3fr; }
  .m-pansplit .m-ps-media { order: 2; }
  .m-pansplit.narrow .m-ps-media { order: 0; }
}
.m-ps-panel {
  background: var(--band); border-radius: 20px; padding: clamp(24px, 3.5vw, 40px);
  display: flex; flex-direction: column; justify-content: space-between;
  gap: clamp(30px, 6vw, 90px);
}
.m-ps-panel h2 { font-size: clamp(1.5rem, 2.8vw, 2rem); margin: 0; max-width: 20em; }
.m-ps-foot p { margin: 0 0 12px; max-width: 36em; }
.m-pansplit .m-more { font-weight: 700; margin: 0; }
.m-ps-media .ph { border-radius: 20px; height: 100%; }

/* --- 30 · icon-rows (c2-7) -------------------------------------------------------
   THE RULED TREATMENT, decided once for the audit's three ruled sections (c2-7,
   c4-4, c5-6): 1px --line hairlines, the same separator the app draws between its
   routine rows — never a new grey. Icon cell | vertical rule | label. */
.m-ir-list { list-style: none; margin: 0; padding: 0; max-width: 760px; border-top: 1px solid var(--line); }
.m-ir-list li {
  display: flex; align-items: center; gap: 18px;
  padding: 14px 4px; border-bottom: 1px solid var(--line);
}
.m-ir-ic {
  flex: 0 0 44px; display: inline-grid; place-items: center; width: 44px; height: 44px;
  font-size: 1.3rem; border-right: 1px solid var(--line); padding-right: 18px;
  box-sizing: content-box;
}
.m-ir-label { font-weight: 600; }

/* --- 31 · split-media-panel (c2-8) -----------------------------------------------
   Header across the top, then landscape media (~55%) beside a tinted panel (~45%)
   whose prose and CTA sit at the bottom. `.m-smp-panel` is on the --link-tinted
   switch list with `.m-ps-panel`. */
.m-smp-grid { display: grid; gap: 16px; }
@media (min-width: 840px) { .m-smp-grid { grid-template-columns: 11fr 9fr; align-items: stretch; } }
.m-smp-media .ph { border-radius: 20px; height: 100%; }
.m-smp-panel {
  background: var(--band); border-radius: 20px; padding: clamp(24px, 3.5vw, 38px);
  display: flex; flex-direction: column; justify-content: flex-end;
}
.m-smp-foot p { margin: 0 0 12px; max-width: 36em; }
.m-splitmp .m-more { font-weight: 700; margin: 0; }

/* --- 32 · media-stagger-cards (c2-9) ---------------------------------------------
   Large media with a chip in its corner, beside three horizontally-offset cards.
   The chip is the app's streak-badge: moon-glow pill, night ink. The offsets are
   the design; they drop with the collapse. */
.m-sg-grid { display: grid; gap: 30px; }
@media (min-width: 900px) { .m-sg-grid { grid-template-columns: 1.2fr 1fr; gap: 48px; align-items: center; } }
.m-sg-media { position: relative; }
.m-sg-media .ph { border-radius: 20px; }
.m-sg-chip {
  position: absolute; top: 14px; right: 14px;
  background: var(--moon-glow); color: var(--accent-ink);
  font-size: 0.82rem; font-weight: 800; padding: 5px 12px; border-radius: 999px;
}
.m-sg-cards { list-style: none; margin: 0; padding: 0; display: grid; gap: 14px; }
.m-sg-cards li { background: var(--card); border: 1px solid var(--line); border-radius: 16px; padding: 18px 20px; }
@media (min-width: 900px) {
  .m-sg-cards li:nth-child(2) { margin-left: 28px; }
  .m-sg-cards li:nth-child(3) { margin-left: 56px; }
}
.m-sg-cards h3 { margin: 0 0 4px; font-size: 1.05rem; }
.m-sg-cards p { margin: 0; font-size: 0.97rem; color: var(--muted); }

/* --- 33 · bento-text (c2-10) -----------------------------------------------------
   Full-width tinted card over two unequal text cards. `.m-bt-top` is on the
   --link-tinted switch list. */
.m-bt-top { background: var(--band); border-radius: 20px; padding: clamp(22px, 3.5vw, 36px); margin-bottom: 16px; }
.m-bt-top h2 { font-size: clamp(1.5rem, 2.8vw, 2rem); margin: 0 0 10px; }
.m-bt-top p { margin: 0; max-width: 46em; }
.m-bt-row { display: grid; gap: 16px; }
@media (min-width: 840px) { .m-bt-row { grid-template-columns: 2fr 3fr; } }
.m-bt-card { background: var(--card); border: 1px solid var(--line); border-radius: 16px; padding: 22px; }
.m-bt-card h3 { margin: 0 0 6px; font-size: 1.1rem; }
.m-bt-card p { margin: 0; color: var(--muted); }

/* --- 34 · collage (c3-1) ---------------------------------------------------------
   Free-positioned overlapping media on a fixed-aspect canvas — no grid can draw
   this (§9). The canvas keeps the composition at every width; captions ride with
   their blocks. */
.m-cl-text { max-width: 34em; margin-bottom: clamp(20px, 3vw, 30px); }
.m-cl-text h2 { font-size: clamp(1.6rem, 3vw, 2.1rem); margin: 0 0 10px; }
.m-cl-text .m-lead { color: var(--muted); margin: 0 0 4px; }
.m-cl-canvas { position: relative; aspect-ratio: 16 / 10; }
.m-cl-canvas .ph { border-radius: 18px; }
.m-cl-a { position: absolute; left: 0; bottom: 0; width: 50%; }
.m-cl-b { position: absolute; left: 30%; top: 26%; width: 42%; z-index: 1; }
.m-cl-c { position: absolute; right: 0; top: 0; width: 24%; }
.m-cl-cap { margin: 8px 0 0; font-size: 0.85rem; color: var(--muted); }
@media (max-width: 600px) { .m-cl-canvas { aspect-ratio: 4 / 5; } .m-cl-cap { display: none; } }

/* --- 35 · cards-3-media-top (c3-2) -----------------------------------------------
   Media on top, and the card's text and CTA pill SHARE the foot row — that baseline
   is the capture's whole point. */
.m-c3-grid { list-style: none; margin: 0; padding: 0; display: grid; gap: 16px; }
@media (min-width: 840px) { .m-c3-grid { grid-template-columns: repeat(3, 1fr); } }
.m-c3-grid li {
  background: var(--card); border: 1px solid var(--line); border-radius: 16px;
  padding: 16px; display: flex; flex-direction: column; gap: 14px;
}
.m-sec.tint .m-c3-grid li { background: var(--cream); }
.m-c3-grid .ph { border-radius: 12px; }
.m-c3-foot { margin-top: auto; display: flex; align-items: flex-end; justify-content: space-between; gap: 12px; }
.m-c3-foot h3 { margin: 0 0 4px; font-size: 1.05rem; }
.m-c3-foot p { margin: 0; font-size: 0.92rem; color: var(--muted); }
.m-c3-foot .cta-ghost { flex: 0 0 auto; padding: 8px 16px; font-size: 0.9rem; }

/* --- 36 · cards-tilted (c3-3) ----------------------------------------------------
   Rotated, overlapping cards. Narrow screens get a horizontal PEEK, not a stack
   (§9): the strip owns the overflow so the page never scrolls sideways. */
.m-tl-strip { overflow-x: auto; padding: 18px 4px 22px; }
.m-tl-row { list-style: none; margin: 0; padding: 0; display: flex; align-items: stretch; }
.m-tl-row li {
  flex: 0 0 min(300px, 78vw); background: var(--card); border: 1px solid var(--line);
  border-radius: 18px; padding: 22px;
}
.m-tl-row li:nth-child(1) { transform: rotate(-2.5deg); }
.m-tl-row li:nth-child(2) { transform: rotate(1.8deg); margin-left: -14px; z-index: 1; }
.m-tl-row li:nth-child(3) { transform: rotate(-1.6deg); margin-left: -14px; }
.m-tl-row h3 { margin: 0 0 6px; font-size: 1.1rem; }
.m-tl-row p { margin: 0; font-size: 0.95rem; color: var(--muted); }
@media (min-width: 900px) { .m-tl-strip { overflow: visible; } .m-tl-row { justify-content: center; } }

/* --- 37 · bento-mosaic (c3-4) ----------------------------------------------------
   Grid-template-areas bento; three tiles put text ON media, so each carries the
   bottom-anchored night scrim — text only ever sits in the ≥0.86 zone, keeping the
   measured on-night pairings. The b tile is --band and is on the switch list below
   with the other tinted tiles. */
.m-bm-grid { display: grid; gap: 16px; }
@media (min-width: 900px) {
  .m-bm-grid {
    grid-template-columns: 1.1fr 1fr 1fr;
    grid-template-areas: "a b c" "a d e";
  }
  .m-bm-tile.a { grid-area: a; } .m-bm-tile.b { grid-area: b; } .m-bm-tile.c { grid-area: c; }
  .m-bm-tile.d { grid-area: d; } .m-bm-tile.e { grid-area: e; }
}
.m-bm-tile { position: relative; border-radius: 18px; overflow: hidden; min-height: 200px; background: var(--card); }
.m-bm-tile.a { min-height: 320px; }
.m-bm-tile > .ph {
  position: absolute; inset: 0; height: 100%; aspect-ratio: auto; border-radius: 0;
  border: 0; background: none; color: var(--on-night); z-index: 1;
  place-items: start center; place-content: start center; padding-top: 22px;
}
.m-bm-tile.e > .ph { position: static; aspect-ratio: 1 / 1; height: auto; color: var(--muted); border-radius: 0; }
.m-bm-tile.e { display: grid; border: 1px dashed var(--line); }
.m-bm-scrim { position: absolute; inset: 0; background: linear-gradient(180deg, rgba(17, 23, 34, 0.30), rgba(17, 23, 34, 0.88) 62%); }
.m-bm-text { position: absolute; left: 0; right: 0; bottom: 0; z-index: 2; padding: 18px 20px; }
.m-bm-text h3 { margin: 0 0 4px; color: var(--moon-glow); font-size: 1.05rem; }
.m-bm-text p { margin: 0; color: var(--on-night); font-size: 0.93rem; }
.m-bm-tile.b {
  background: var(--band); overflow: visible; display: grid; align-content: center;
  padding: 22px; min-height: 160px;
}
.m-bm-tile.b p { margin: 0; font-size: 1.05rem; }

/* --- 38 · cards-3-media-bottom (c3-5) — shares .m-c3-grid with c3-2 -------------- */
.m-c3-head h3 { margin: 0 0 4px; font-size: 1.05rem; }
.m-c3-head p { margin: 0; font-size: 0.95rem; color: var(--muted); }
.m-cards3bot .m-c3-grid .ph { margin-top: auto; }

/* --- 39 · split-offset-duo (c3-6) ------------------------------------------------
   Long text with the CTA after a deliberate gap, beside a portrait media with a
   small block lapping its lower-right. */
.m-od-grid { display: grid; gap: 34px; }
@media (min-width: 840px) {
  .m-od-grid { grid-template-columns: 1fr 1fr; gap: 64px; }
  .m-offsetduo.flip .m-od-media { order: -1; }
  .m-od-text { display: flex; flex-direction: column; justify-content: space-between; gap: 44px; }
}
.m-od-text h2 { font-size: clamp(1.5rem, 2.6vw, 1.9rem); margin: 0 0 12px; }
.m-od-text p { margin: 0 0 12px; max-width: 32em; color: var(--muted); }
.m-offsetduo .m-more { font-weight: 700; margin: 0; }
.m-od-media { position: relative; margin: 0 40px 34px 0; }
.m-od-media > .ph { border-radius: 18px; }
.m-od-inset { position: absolute; right: -36px; bottom: -30px; width: 30%; }
.m-od-inset .ph { border-radius: 14px; }
.m-od-inset .ph .ph-label { display: none; }

/* --- 40 · feature-trio-wide (c3-7) -----------------------------------------------
   Wide media beside two narrow cards; the first card is the night surface — the
   reference alternates dark and light and the night card is the brand's own lockup
   (moon text on night, both measured pairings). */
.m-ft-grid { display: grid; gap: 16px; }
@media (min-width: 900px) { .m-ft-grid { grid-template-columns: 2fr 1fr 1fr; align-items: stretch; } }
.m-ft-media .ph { border-radius: 18px; height: 100%; }
.m-ft-card { background: var(--card); border: 1px solid var(--line); border-radius: 18px; padding: 22px; display: flex; flex-direction: column; justify-content: space-between; gap: 30px; }
.m-ft-card h3 { margin: 0; font-size: 1.15rem; }
.m-ft-card p { margin: 0; font-size: 0.95rem; color: var(--muted); }
.m-ft-card.dark { background: var(--night); border-color: var(--night); }
.m-ft-card.dark h3 { color: var(--moon-glow); }
.m-ft-card.dark p { color: var(--on-night); }

/* --- 41 · grid-six-media (c3-8) --------------------------------------------------
   Six small cards in a grid that KEEPS TWO COLUMNS at every width (§14.3 trap 4 —
   one of exactly two sections where the one-column assumption is wrong), beside a
   large square media that drops below at 900 px. */
.m-g6-grid { display: grid; gap: 16px; }
@media (min-width: 900px) { .m-g6-grid { grid-template-columns: 3fr 2fr; align-items: center; } }
.m-g6-cards { list-style: none; margin: 0; padding: 0; display: grid; gap: 12px; grid-template-columns: 1fr 1fr; }
.m-g6-cards li { background: var(--card); border: 1px solid var(--line); border-radius: 14px; padding: 16px 18px; }
.m-g6-cards h3 { margin: 0 0 4px; font-size: 0.98rem; }
.m-g6-cards p { margin: 0; font-size: 0.9rem; color: var(--muted); }
.m-g6-media .ph.tile { border-radius: 24px; }

/* --- 42 · triptych (c3-9) --------------------------------------------------------
   Two portrait media flanking a narrower centred text column; stacks in the same
   order on mobile. */
.m-tp-grid { display: grid; gap: 20px; }
@media (min-width: 900px) { .m-tp-grid { grid-template-columns: 1fr 0.9fr 1fr; align-items: center; gap: 32px; } }
.m-tp-media .ph { border-radius: 20px; }
.m-tp-text { text-align: center; padding: 10px clamp(0px, 2vw, 20px); }
.m-tp-text h2 { font-size: clamp(1.4rem, 2.4vw, 1.8rem); margin: 0 0 12px; }
.m-tp-text p { margin: 0 0 10px; color: var(--muted); }

/* --- 43 · bento-icons (c4-1) -----------------------------------------------------
   Heading zone + three icon cards on the brand's three surfaces (band, night,
   moon) + three plain media tiles, in a two-row bento. `.m-bi-band` is on the
   --link-tinted switch list. */
.m-bi-grid { display: grid; gap: 14px; }
@media (min-width: 900px) {
  .m-bi-grid {
    grid-template-columns: repeat(4, 1fr);
    grid-template-areas: "h h c1 m1" "m2 c2 m3 c3";
  }
  .m-bi-h { grid-area: h; }
  .m-bi-card.c1 { grid-area: c1; } .m-bi-card.c2 { grid-area: c2; } .m-bi-card.c3 { grid-area: c3; }
  .m-bi-tile.t1 { grid-area: m1; } .m-bi-tile.t2 { grid-area: m2; } .m-bi-tile.t3 { grid-area: m3; }
}
.m-bi-h { font-size: clamp(1.5rem, 2.8vw, 2rem); line-height: 1.2; margin: 0; align-self: center; padding-right: 12px; }
.m-bi-card { border-radius: 18px; padding: 20px; min-height: 190px; display: flex; flex-direction: column; }
.m-bi-card .m-bi-ic { font-size: 1.5rem; margin-bottom: auto; }
.m-bi-card h3 { margin: 14px 0 4px; font-size: 1.05rem; }
.m-bi-card p { margin: 0; font-size: 0.93rem; }
.m-bi-band { background: var(--band); }
.m-bi-card.night { background: var(--night); }
.m-bi-card.night h3 { color: var(--moon-glow); }
.m-bi-card.night p { color: var(--on-night); }
.m-bi-card.moon { background: var(--accent); }
.m-bi-card.moon h3, .m-bi-card.moon p { color: var(--accent-ink); }
.m-bi-tile .ph { height: 100%; aspect-ratio: auto; min-height: 150px; border-radius: 18px; }
.m-bi-tile .ph .ph-label { display: none; }

/* --- 44 · cards-stagger-4 (c4-2) -------------------------------------------------
   Four cards, alternately offset vertically, media at each card's foot. NOT c4-6's
   mosaic stagger — different thing, different CSS (§9). */
.m-s4-grid { list-style: none; margin: 0; padding: 0; display: grid; gap: 16px; }
@media (min-width: 960px) {
  .m-s4-grid { grid-template-columns: repeat(4, 1fr); padding-bottom: 28px; }
  .m-s4-grid li:nth-child(even) { transform: translateY(28px); }
}
.m-s4-grid li {
  background: var(--card); border: 1px solid var(--line); border-radius: 16px;
  padding: 18px; display: flex; flex-direction: column; gap: 10px;
}
.m-s4-grid h3 { margin: 0; font-size: 1.05rem; }
.m-s4-grid p { margin: 0; font-size: 0.93rem; color: var(--muted); }
.m-s4-grid .ph { margin-top: auto; border-radius: 12px; }
.m-s4-grid .ph .ph-label { display: none; }

/* --- 45 · carousel-intro (c4-3) --------------------------------------------------
   Second carousel. Base class is m-shots — the slide CSS and marketing.js key on
   it — plus this modifier for the two-column intro. */
.m-ci-head { display: grid; gap: 8px 56px; margin-bottom: 22px; }
@media (min-width: 840px) { .m-ci-head { grid-template-columns: 1fr 1fr; align-items: start; } }
.m-ci-head h2 { font-size: clamp(1.6rem, 3vw, 2.1rem); line-height: 1.18; margin: 0; }
.m-ci-head p { margin: 0; color: var(--muted); max-width: 34em; }

/* --- 46 · listing-rows (c4-4) ----------------------------------------------------
   Thumb | text | meta list | CTA, hairline rules between rows (the icon-rows
   treatment). On a phone each row stacks and the CTA goes full width. */
.m-lr-rows { list-style: none; margin: 0; padding: 0; border-top: 1px solid var(--line); }
.m-lr-rows > li { display: grid; gap: 14px; padding: 20px 4px; border-bottom: 1px solid var(--line); }
@media (min-width: 840px) {
  .m-lr-rows > li { grid-template-columns: 84px 1.4fr 1fr auto; align-items: center; gap: 22px; }
}
.m-lr-thumb .ph.tile { width: 84px; }
.m-lr-thumb .ph .ph-label { display: none; }
.m-lr-text h3 { margin: 0 0 4px; font-size: 1.1rem; }
.m-lr-text p { margin: 0; font-size: 0.95rem; color: var(--muted); }
.m-lr-meta { list-style: none; margin: 0; padding: 0; font-size: 0.9rem; color: var(--muted); }
.m-lr-meta li { padding: 2px 0 2px 16px; position: relative; }
.m-lr-meta li::before { content: "•"; position: absolute; left: 2px; color: var(--moon-deep); }
.m-lr-cta .cta-ghost { display: block; text-align: center; }

/* --- 47 · gallery-captioned (c4-5) -----------------------------------------------
   Narrow text column beside a 3-up captioned media grid over two rows. */
.m-gc-grid { display: grid; gap: 30px; }
@media (min-width: 900px) { .m-gc-grid { grid-template-columns: 1fr 3fr; gap: 48px; align-items: start; } }
.m-gc-text h2 { font-size: clamp(1.5rem, 2.6vw, 1.9rem); margin: 0 0 10px; }
.m-gc-text p { margin: 0; color: var(--muted); max-width: 26em; }
.m-gc-items { display: grid; gap: 20px 16px; }
@media (min-width: 640px) { .m-gc-items { grid-template-columns: repeat(3, 1fr); } }
.m-gc-items figure { margin: 0; }
.m-gc-items .ph { border-radius: 14px; }
.m-gc-items .ph .ph-label { display: none; }
.m-gc-items h3 { margin: 10px 0 2px; font-size: 1rem; }
.m-gc-items figcaption p { margin: 0; font-size: 0.88rem; color: var(--muted); }

/* --- 48 · mosaic-phones (c4-6) ---------------------------------------------------
   The staggered phone wall. Per-COLUMN offsets (not c4-2's per-card alternation —
   §9 keeps them apart on purpose), clipped by the section so the bleed never
   scrolls the page. The wall is aria-hidden decoration. */
.m-mosaicph { overflow: hidden; }
.m-mp-grid { display: grid; gap: 30px; }
@media (min-width: 900px) { .m-mp-grid { grid-template-columns: 1fr 1.6fr; align-items: center; gap: 48px; } }
.m-mp-text h2 { font-size: clamp(1.6rem, 3vw, 2.1rem); margin: 0 0 12px; }
.m-mp-text p { margin: 0 0 6px; color: var(--muted); max-width: 30em; }
.m-mp-wall {
  display: grid; grid-template-columns: repeat(2, 1fr); gap: 14px;
  max-height: 560px; overflow: hidden;
}
@media (min-width: 640px) { .m-mp-wall { grid-template-columns: repeat(4, 1fr); } }
.m-mp-wall .m-mp-tile:nth-child(4n+2), .m-mp-wall .m-mp-tile:nth-child(4n+4) { transform: translateY(-26px); }
.m-mp-tile .ph { --ph-ratio: 9 / 19; border-radius: 18px; }
.m-mp-tile .ph .ph-label { display: none; }

/* --- 49 · five-up (c5-1) ---------------------------------------------------------
   Five equal tiles across the full width; on mobile a TWO-COLUMN staggered grid,
   never a single column (§14.3 trap 4 — the second refusal, with c3-8). */
.m-fu-head { max-width: 40em; margin-bottom: clamp(20px, 3vw, 30px); }
.m-fu-head h2 { font-size: clamp(1.6rem, 3vw, 2.1rem); margin: 0 0 10px; }
.m-fiveup .m-more { font-weight: 700; margin: 0; }
.m-fu-row { display: grid; grid-template-columns: 1fr 1fr; gap: 14px; }
@media (max-width: 999px) { .m-fu-row figure:nth-child(even) { transform: translateY(18px); } .m-fu-row { padding-bottom: 18px; } }
@media (min-width: 1000px) { .m-fu-row { grid-template-columns: repeat(5, 1fr); } }
.m-fu-row figure { margin: 0; text-align: center; }
.m-fu-row .ph { border-radius: 999px 999px 22px 22px; }
.m-fu-row .ph .ph-label { display: none; }
.m-fu-row figcaption { margin-top: 8px; font-weight: 700; font-size: 0.95rem; }

/* --- 50 · flow-grid (c5-3) -------------------------------------------------------
   Eight steps with arrow connectors, 4×2 wide and 2×4 narrow — the arrows are the
   design and they stay at every width (§9). The last arrow is the journey
   continuing, not a bug. */
.m-fg-grid { list-style: none; margin: 0; padding: 0; display: grid; gap: 18px 10px; grid-template-columns: repeat(2, 1fr); counter-reset: none; }
@media (min-width: 900px) { .m-fg-grid { grid-template-columns: repeat(4, 1fr); } }
.m-fg-grid li { display: flex; align-items: center; gap: 10px; min-width: 0; }
.m-fg-ic {
  flex: 0 0 46px; display: inline-grid; place-items: center; width: 46px; height: 46px;
  background: var(--card); border: 1px solid var(--line); border-radius: 12px; font-size: 1.25rem;
}
/* `overflow-wrap` is the floor, not the plan. At 390 px a flow cell leaves about 100 px
   for text, and Romanian words are long — `Cincisprezece`, `Directorul` — so three of the
   eight steps overflowed their cell on the RO features page while English fitted. The copy
   was shortened to fit; this is what stops the NEXT language from silently overflowing
   instead, which is the failure a locale nobody reads would ship with. */
.m-fg-body { min-width: 0; font-size: 0.92rem; line-height: 1.35; overflow-wrap: break-word; }
.m-fg-label {
  display: block; font-size: 0.72rem; font-weight: 800; letter-spacing: 0.1em;
  text-transform: uppercase; color: var(--link);
}
.m-fg-arrow { margin-left: auto; color: var(--moon-deep); font-weight: 700; padding: 0 4px; }
/* AN ARROW AT THE END OF A ROW POINTS AT NOTHING, which is the one thing a flow diagram
   must not do — it reads as a step that was cut off rather than as the end of the flow.
   The module draws one arrow per item deliberately (they are characters in the markup, so
   they survive with CSS off); which of them is a connector and which is a dangling stub is
   a question only the grid knows, so it is answered here. Two columns under 900 px, four
   above it, plus the final item in both — the `:last-child` rule is what catches an
   author who composes a count that does not fill its last row. */
.m-fg-grid li:nth-child(2n) .m-fg-arrow,
.m-fg-grid li:last-child .m-fg-arrow { visibility: hidden; }
@media (min-width: 900px) {
  .m-fg-grid li:nth-child(2n) .m-fg-arrow { visibility: visible; }
  .m-fg-grid li:nth-child(4n) .m-fg-arrow,
  .m-fg-grid li:last-child .m-fg-arrow { visibility: hidden; }
}

/* --- 51 · defs-list (c5-4) -------------------------------------------------------
   Heading | definition list | two offset portraits. The labels are the app's
   micro-caption: 12 px, 800, tracked uppercase gold; the rows separate on the
   hairline like the app's routine list. */
.m-dl-grid { display: grid; gap: 34px; }
@media (min-width: 900px) { .m-dl-grid { grid-template-columns: 1fr 1.3fr 0.8fr; gap: 44px; align-items: start; } }
.m-dl-text h2 { font-size: clamp(1.5rem, 2.6vw, 1.9rem); margin: 0 0 12px; }
.m-dl-text p { margin: 0; color: var(--muted); max-width: 28em; }
.m-dl-list { margin: 0; }
.m-dl-row { display: grid; grid-template-columns: 130px 1fr; gap: 14px; padding: 12px 0; border-bottom: 1px solid var(--line); }
.m-dl-row:last-child { border-bottom: 0; }
.m-dl-row dt {
  font-size: 0.74rem; font-weight: 800; letter-spacing: 0.1em; text-transform: uppercase;
  color: var(--link); padding-top: 3px;
}
.m-dl-row dd { margin: 0; font-size: 0.95rem; }
.m-dl-media { display: grid; gap: 16px; }
@media (min-width: 900px) { .m-dl-bottom { transform: translateY(26px); margin-right: 18%; } .m-dl-top { margin-left: 12%; } }
.m-dl-media .ph { border-radius: 16px; }
.m-dl-media .ph .ph-label { display: none; }

/* --- 52 · accordion-h (c5-2) -----------------------------------------------------
   Horizontal accordion (§13.4). DEFAULT = every panel expanded and stacked — the
   no-script rendering AND the narrow layout, so the fallback is a layout that has
   to work anyway. assets/marketing.js adds `.is-ready` and drives aria-expanded;
   the collapse only exists inside that class and above 900 px. */
.m-acc { display: grid; gap: 14px; }
.m-acc-panel { background: var(--card); border: 1px solid var(--line); border-radius: 16px; overflow: hidden; }
.m-acc-tab { margin: 0; }
.m-acc-tab button {
  width: 100%; text-align: left; font: inherit; font-weight: 700; font-size: 1.02rem;
  color: var(--ink); background: none; border: 0; padding: 14px 18px; cursor: pointer;
}
.m-acc-body { padding: 0 18px 18px; }
.m-acc-body .ph { border-radius: 12px; }
.m-acc-body p { margin: 10px 0 0; font-size: 0.95rem; color: var(--muted); }
@media (min-width: 900px) {
  .m-acc.is-ready { display: flex; align-items: stretch; }
  /* THE SLIDE IS THE SECTION (owner, 2026-08-09: every interaction is fluid). The
     panels animate flex-grow AND a numeric flex-basis — `auto` cannot interpolate —
     and the collapsed body fades out instead of display:none, which cannot animate
     at all. The body keeps a fixed min-width so its text CLIPS during the slide
     rather than reflowing line by line; the panel's overflow:hidden does the rest. */
  .m-acc.is-ready .m-acc-panel {
    display: flex; flex-direction: column; height: 440px;
    transition: flex-grow 480ms cubic-bezier(0.24, 0.85, 0.25, 1),
      flex-basis 480ms cubic-bezier(0.24, 0.85, 0.25, 1);
  }
  .m-acc.is-ready .m-acc-panel[data-open="true"] { flex: 1 1 300px; }
  .m-acc.is-ready .m-acc-panel[data-open="false"] { flex: 0.001 1 64px; }
  .m-acc.is-ready .m-acc-body {
    flex: 1; display: flex; flex-direction: column; min-width: min(520px, 52vw);
    opacity: 1; visibility: visible;
    transition: opacity 260ms ease 160ms, visibility 0s linear 0s;
  }
  .m-acc.is-ready .m-acc-panel[data-open="false"] .m-acc-body {
    opacity: 0; visibility: hidden;
    transition: opacity 140ms ease, visibility 0s linear 140ms;
  }
  .m-acc.is-ready .m-acc-panel[data-open="false"] .m-acc-tab button {
    writing-mode: vertical-rl; height: 100%; padding: 18px 0; text-align: start;
    display: block; margin: 0 auto;
  }
  .m-acc.is-ready .m-acc-body .ph { flex: 1; aspect-ratio: auto; min-height: 0; }
}
.m-acc-tab button { transition: background-color 140ms ease; }
.m-acc-tab button:hover { background: var(--band); }

/* --- 53 · mosaic-news (c5-5) -----------------------------------------------------
   Lead story + two mid stories + a rail of small entries, at unequal sizes. Narrow
   screens keep a two-column mosaic (§9), never a single stack. */
.m-mn-grid { display: grid; gap: 24px 20px; }
@media (min-width: 900px) { .m-mn-grid { grid-template-columns: 2fr 1.2fr 1fr; } }
.m-mn-lead .ph, .m-mn-story .ph { border-radius: 14px; }
.m-mn-lead .ph .ph-label, .m-mn-story .ph .ph-label, .m-mn-rail .ph .ph-label { display: none; }
.m-mn-lead h3 { font-size: clamp(1.3rem, 2.2vw, 1.7rem); margin: 12px 0 6px; }
.m-mn-lead p { margin: 0 0 8px; color: var(--muted); }
.m-mosnews .m-more { font-weight: 700; margin: 0; }
.m-mn-mid { display: grid; gap: 20px; align-content: start; }
@media (max-width: 899px) { .m-mn-grid { grid-template-columns: 1fr 1fr; } .m-mn-lead { grid-column: 1 / -1; } }
.m-mn-story h4 { font-size: 1rem; margin: 10px 0 4px; }
.m-mn-story p { margin: 0; font-size: 0.9rem; color: var(--muted); }
.m-mn-rail { list-style: none; margin: 0; padding: 0; align-self: start; }
@media (max-width: 899px) { .m-mn-rail { grid-column: 1 / -1; } }
.m-mn-rail li { display: flex; gap: 12px; align-items: center; padding: 10px 0; border-bottom: 1px solid var(--line); }
.m-mn-rail li:last-child { border-bottom: 0; }
.m-mn-thumb { flex: 0 0 56px; }
.m-mn-thumb .ph.tile { border-radius: 12px; }
.m-mn-rail h4 { margin: 0; font-size: 0.95rem; }

/* --- 54 · ruled-table (c5-6) -----------------------------------------------------
   The third ruled section: a cell grid drawn with the same hairlines, pill labels
   inside, a three-dot cell, and a wide footer line. */
.m-rt-table { border: 1px solid var(--line); border-radius: 16px; overflow: hidden; }
.m-rt-cells { list-style: none; margin: 0; padding: 0; display: grid; grid-template-columns: repeat(2, 1fr); }
@media (min-width: 700px) { .m-rt-cells { grid-template-columns: repeat(5, 1fr); } }
.m-rt-cells li {
  display: grid; place-items: center; min-height: 76px; padding: 12px;
  border-bottom: 1px solid var(--line); border-right: 1px solid var(--line);
}
/* `white-space: nowrap` + the table's `overflow: hidden` used to CLIP a label that did
   not fit, silently — found at 390 px the first time this section was composed with real
   copy, where four of the twelve pills ended in a cut-off word with nothing to say it had
   happened. A pill is meant to hold two or three words, and the copy above it obeys that;
   this is what makes a label one word too long wrap rather than disappear. The cell's
   76 px min-height already has room for the second line. */
.m-rt-pill {
  background: var(--card); border: 1px solid var(--line); border-radius: 999px;
  padding: 8px 16px; font-weight: 700; font-size: 0.92rem;
  max-width: 100%; text-align: center; overflow-wrap: break-word; hyphens: auto;
}
.m-sec.tint .m-rt-pill { background: var(--cream); }
.m-rt-dots { display: flex; gap: 8px; }
.m-rt-dots span { width: 12px; height: 12px; border-radius: 50%; background: var(--moon-deep); display: inline-block; }
.m-rt-dots span:nth-child(2) { background: var(--accent); }
.m-rt-dots span:nth-child(3) { background: var(--moon-glow); }
.m-rt-foot { margin: 0; padding: 16px 20px; font-size: 0.92rem; color: var(--muted); }

/* --- the pre-Figma alignment pass (owner, 2026-08-09) ----------------------------
   The eighteen existing sections predate the §14.5 bar and the owner asked, during
   step 5, for them to be brought up to the app's standard in the same step — CSS
   only, so the six committed pages stay byte-identical while their look moves.
   Three observations measured off the running app, applied to the whole marketing
   family (they sit AFTER every .m-* block on purpose — the cascade is the
   mechanism):
     - card and item HEADINGS are Fraunces 500 (the app's card heading is Fraunces
       at 19 px, weight 500, and it is most of why the app reads warmer than the
       site did) — since the chrome pass this lives on the GLOBAL h3 rule near the
       top of this file, so the guide and blog pages get it too;
     - the primary and ghost buttons are the app's own: 12 px radius, weight 800 —
       the brand guide's rounded rectangle, not a pill;
     - boxed items sit on a 16 px radius everywhere (the FAQ was 14). */
.cta, .cta-ghost { border-radius: 12px; font-weight: 800; }
.m-faq details { border-radius: 16px; }

/* --- the interaction pass (owner, 2026-08-09) ------------------------------------
   Everything a visitor can click or hover moves fluidly instead of snapping. All of
   it is CSS — the published pages' markup does not change — and all of it is undone
   under prefers-reduced-motion at the end of this block. The accordion's slide
   lives in its own block above (52); this one carries the site-wide pieces. */

/* Height-to-auto interpolation for the FAQ below. A no-op wherever the keyword is
   not implemented — those browsers keep the instant open they have today. */
:root { interpolate-size: allow-keywords; }

/* Hover states ease instead of snapping — nav, footer, buttons, onward links, the
   theme toggle. Color-ish properties only; nothing moves. */
.site-nav a, .foot-col a, .nav-menu-panel a, .nav-menu > summary,
.cta, .cta-ghost, .theme-toggle, .m-more a, .site-foot a, .post-list a {
  transition: color 140ms ease, background-color 140ms ease,
    border-color 140ms ease, filter 140ms ease;
}

/* The FAQ opens by sliding, where ::details-content and interpolate-size exist
   (progressive — elsewhere it opens instantly, exactly as before). */
.m-faq details::details-content {
  opacity: 0; height: 0; overflow: clip;
  transition: height 300ms ease, opacity 220ms ease,
    content-visibility 300ms allow-discrete;
}
.m-faq details[open]::details-content { opacity: 1; height: auto; }

/* The Guide dropdown enters with the app popover's small drop-and-fade.
   @starting-style is the entry state on the first rendered frame; browsers without
   it keep the instant open. */
.nav-menu-panel { transition: opacity 160ms ease, transform 160ms ease; }
@starting-style {
  .nav-menu[open] .nav-menu-panel { opacity: 0; transform: translateY(-6px); }
}

/* The tilted cards straighten and lift under the pointer on wide screens — the
   reference site's move, at Kiri's restraint. The strip also snaps per card while
   it is a scroller. */
.m-tl-strip { scroll-snap-type: x proximity; }
.m-tl-row li { scroll-snap-align: center; position: relative; }
@media (min-width: 900px) {
  .m-tl-row li { transition: transform 280ms ease; }
  .m-tl-row li:hover { transform: rotate(0deg) translateY(-6px); z-index: 2; }
}

/* One switch turns the whole pass off. The accordion keeps working — it just cuts
   instead of sliding, which is what reduced motion asks for. */
@media (prefers-reduced-motion: reduce) {
  .m-acc.is-ready .m-acc-panel, .m-acc.is-ready .m-acc-body, .m-acc-tab button,
  .m-faq details::details-content, .nav-menu-panel, .m-tl-row li,
  .site-nav a, .foot-col a, .nav-menu-panel a, .nav-menu > summary,
  .cta, .cta-ghost, .theme-toggle, .m-more a, .site-foot a, .post-list a {
    transition: none;
  }
}

/* ============================================================================
   GENETICS CALCULATOR (ROADMAP Phase 11) — /genetics/, /ro/genetica/

   Styled from the tokens above and nothing else, so `tests/public-contrast.test.js`
   keeps covering it. A second stylesheet would have put this page outside the one
   guard that measures whether its text can be read.

   THE SWATCHES ARE A COLOUR KEY, NOT PHOTOGRAPHS, and the distinction is the point.
   ROADMAP 11.6 wants thirteen consistent illustrations and calls them an
   administrator-supplied asset that does not exist yet; 11.4 wants the picker to be a
   grid of images. A gradient chip is the honest stand-in: it shows body-over-flight
   colour in the right relationship without claiming to be a bird, so nobody mistakes
   it for an identification photograph. When the illustrations land they replace these
   in one rule each and the layout does not move.
   ============================================================================ */
/* The hero and the widget are one unit — the calculator is the point of the page, not
   something below it. The shared 60px section padding put 90px of empty band between
   the standfirst and the first card, measured in the browser. */
.gx-hero { padding-bottom: clamp(14px, 2vw, 22px); }
.gx-hero .m-lead { max-width: 40em; margin-inline: auto; }
.gx-appsec { padding-top: 0; }
.gx-app { display: grid; gap: 22px; }
.gx-noscript {
  margin: 0; padding: 16px 18px; border: 1px solid var(--warn-line);
  background: var(--warn-bg); color: var(--warn-ink); border-radius: 14px;
}

/* ---- the two parent cards ---- */
.gx-parents { display: grid; gap: 18px; grid-template-columns: 1fr 1fr; }
@media (max-width: 780px) { .gx-parents { grid-template-columns: 1fr; } }
.gx-card {
  background: var(--card); border: 1px solid var(--line);
  border-radius: 18px; padding: 18px 18px 16px;
}
.gx-card h3 { margin: 0 0 14px; font-size: 1.12rem; }
.gx-lbl {
  margin: 16px 0 8px; font-size: 0.74rem; font-weight: 800;
  letter-spacing: 0.12em; text-transform: uppercase; color: var(--muted);
}
.gx-lbl:first-of-type { margin-top: 0; }
.gx-hint { margin: 8px 0 0; font-size: 0.86rem; color: var(--muted); }
/* The printed refusal (11.4): a reason, not a blank space where a control was. */
.gx-why {
  margin: 10px 0 0; padding: 10px 12px; font-size: 0.86rem; line-height: 1.5;
  color: var(--warn-ink); background: var(--warn-bg);
  border-left: 3px solid var(--warn-line); border-radius: 0 10px 10px 0;
}

/* ---- phenotype grid ---- */
.gx-grid { display: grid; gap: 8px; grid-template-columns: repeat(auto-fill, minmax(132px, 1fr)); }
.gx-chip {
  display: flex; align-items: center; gap: 9px; width: 100%;
  padding: 8px 10px; text-align: left; cursor: pointer;
  background: var(--cream); border: 1px solid var(--line); border-radius: 12px;
  color: var(--ink); font: inherit; font-size: 0.87rem; line-height: 1.25;
}
.gx-chip:hover { border-color: var(--moon-deep); }
.gx-chip.on { border-color: var(--accent); background: var(--band); box-shadow: inset 0 0 0 1px var(--accent); }
.gx-chip-t { min-width: 0; }
.gx-sw {
  width: 22px; height: 22px; border-radius: 50%; flex-shrink: 0;
  border: 1px solid rgba(0, 0, 0, 0.18); display: inline-block;
}

/* Body colour over flight colour, the two things that actually separate these birds
   by eye. Green to turquoise moves the body cool; cinnamon warms the flights; dilute
   lifts everything toward white; opaline pulls the flanks yellow. */
.gx-sw-normal            { background: linear-gradient(150deg, #4f8a3d 55%, #2f4a2a); }
.gx-sw-turquoise         { background: linear-gradient(150deg, #4e9390 55%, #2f4f5a); }
.gx-sw-yellowsided       { background: linear-gradient(150deg, #a8c24a 55%, #3d5230); }
.gx-sw-cinnamon          { background: linear-gradient(150deg, #6f9a4b 55%, #a5794a); }
.gx-sw-pineapple         { background: linear-gradient(150deg, #cbd45c 55%, #b08a55); }
.gx-sw-dilute            { background: linear-gradient(150deg, #9fbd7e 55%, #7f8f6a); }
.gx-sw-turquoisecinnamon { background: linear-gradient(150deg, #62a09a 55%, #a58a63); }
.gx-sw-turquoiseyellowsided { background: linear-gradient(150deg, #8fc3ad 55%, #3f6068); }
.gx-sw-turquoisepineapple   { background: linear-gradient(150deg, #b6d6b4 55%, #a89372); }
.gx-sw-mint              { background: linear-gradient(150deg, #b5d6c4 55%, #8fa6a8); }
.gx-sw-suncheek          { background: linear-gradient(150deg, #e8cf87 55%, #c2a173); }
.gx-sw-cinnamint         { background: linear-gradient(150deg, #cfe0cf 55%, #b3a389); }
.gx-sw-mooncheek         { background: linear-gradient(150deg, #e3e6dc 55%, #c3c7cd); }
/* The three combinations nomenclature never named. Deliberately neutral: a colour
   would imply a bird somebody could recognise, and the whole point is that nobody
   agreed on one. */
.gx-sw-unnamed { background: repeating-linear-gradient(135deg, var(--line) 0 4px, var(--card) 4px 8px); }

/* ---- segmented controls ---- */
.gx-seg { display: inline-flex; border: 1px solid var(--line); border-radius: 11px; overflow: hidden; }
.gx-seg-b {
  padding: 9px 14px; min-height: 40px; border: 0; cursor: pointer;
  background: var(--cream); color: var(--muted); font: inherit; font-size: 0.88rem; font-weight: 600;
}
.gx-seg-b + .gx-seg-b { border-left: 1px solid var(--line); }
.gx-seg-b:hover { color: var(--ink); }
.gx-seg-b.on { background: var(--accent); color: var(--accent-ink); }
.gx-seg-sm .gx-seg-b { padding: 7px 11px; min-height: 36px; font-size: 0.82rem; }

/* ---- splits accordion ---- */
.gx-splits { margin-top: 16px; border-top: 1px solid var(--line); padding-top: 12px; }
.gx-splits > summary {
  cursor: pointer; font-weight: 700; font-size: 0.9rem; color: var(--link); list-style: none;
  display: flex; align-items: center; gap: 8px; min-height: 34px;
}
.gx-splits > summary::-webkit-details-marker { display: none; }
.gx-splits > summary::before { content: "\25B8"; font-size: 0.85em; transition: transform .15s ease; }
.gx-splits[open] > summary::before { transform: rotate(90deg); }
.gx-splits-b { padding-top: 6px; }
.gx-split { display: flex; align-items: center; justify-content: space-between; gap: 12px; margin-top: 10px; flex-wrap: wrap; }
.gx-split-n { font-size: 0.9rem; }

/* ---- results ---- */
.gx-out { background: var(--card); border: 1px solid var(--line); border-radius: 18px; padding: 20px 20px 18px; }
.gx-out-h { margin: 0 0 14px; font-size: 1.25rem; }
.gx-toggles { display: flex; gap: 8px; flex-wrap: wrap; margin-bottom: 16px; }
.gx-toggle {
  padding: 7px 13px; min-height: 36px; cursor: pointer; font: inherit; font-size: 0.82rem; font-weight: 600;
  background: var(--cream); color: var(--muted); border: 1px solid var(--line); border-radius: 999px;
}
.gx-toggle:hover { color: var(--ink); border-color: var(--moon-deep); }
.gx-toggle.on { background: var(--accent); color: var(--accent-ink); border-color: var(--accent); }
/* The sentence in human language (11.5) leads, and it is the largest thing here. */
.gx-lead { margin: 0 0 6px; font-family: var(--font-display); font-size: clamp(1.15rem, 2.4vw, 1.5rem); line-height: 1.35; }
/* The most distinctive output on the page — what this pair CANNOT make — reuses the
   .callout surface rather than inventing a tinted one, so it is already on the
   tinted-surface list near the top of this file and its links darken correctly. */
.gx-cannot { margin: 12px 0 18px; }
.gx-cannot p { margin: 0; font-weight: 600; color: var(--warn-ink); }
.gx-refuse { margin: 0; padding: 14px 16px; border-radius: 12px; background: var(--warn-bg); color: var(--warn-ink); border: 1px solid var(--warn-line); }
.gx-scenario { margin: 0 0 8px; font-size: 0.78rem; font-weight: 800; letter-spacing: 0.1em; text-transform: uppercase; color: var(--muted); }
.gx-scenarios { display: grid; gap: 26px; grid-template-columns: 1fr 1fr; }
@media (max-width: 900px) { .gx-scenarios { grid-template-columns: 1fr; } }

/* A segmented bar, not a number table (11.5) — legible at a glance and it screenshots
   well, which is how a link gets into a forum thread in the first place. */
.gx-bar { display: flex; height: 14px; border-radius: 999px; overflow: hidden; margin: 4px 0 20px; border: 1px solid var(--line); }
.gx-bar-s { display: block; height: 100%; min-width: 2px; }

.gx-group + .gx-group { margin-top: 18px; }
.gx-group h4 { margin: 0 0 8px; font-size: 0.78rem; font-weight: 800; letter-spacing: 0.1em; text-transform: uppercase; color: var(--muted); }
.gx-rows { list-style: none; margin: 0; padding: 0; }
.gx-row { display: flex; align-items: flex-start; gap: 11px; padding: 9px 0; border-top: 1px solid var(--line); }
.gx-row:first-child { border-top: 0; }
.gx-row .gx-sw { margin-top: 2px; }
.gx-row-b { flex: 1; min-width: 0; display: flex; flex-direction: column; gap: 1px; }
.gx-row-t { font-weight: 700; }
.gx-row-d, .gx-row-sp { font-size: 0.84rem; color: var(--muted); }
.gx-row-g { font-family: ui-monospace, SFMono-Regular, Menlo, Consolas, monospace; font-size: 0.78rem; color: var(--link-tinted); margin-top: 2px; }
.gx-row-p { font-variant-numeric: tabular-nums; font-weight: 700; white-space: nowrap; }

.gx-maybe { margin-top: 18px; border-top: 1px solid var(--line); padding-top: 12px; }
.gx-maybe > summary { cursor: pointer; font-weight: 700; font-size: 0.9rem; color: var(--link); min-height: 34px; display: flex; align-items: center; }
.gx-maybe-b { padding-top: 8px; }
.gx-linkage { margin: 20px 0 0; padding-top: 14px; border-top: 1px solid var(--line); font-size: 0.85rem; color: var(--muted); }

/* ---- the static half ---- */
.gx-rules { list-style: none; margin: 0; padding: 0; display: grid; gap: 16px; grid-template-columns: repeat(auto-fit, minmax(260px, 1fr)); }
.gx-rules li { background: var(--card); border: 1px solid var(--line); border-radius: 16px; padding: 16px 18px; }
.gx-rules h3 { margin: 0 0 6px; font-size: 1.02rem; }
.gx-rules p { margin: 0; color: var(--muted); font-size: 0.93rem; }
.gx-sub-h { margin: 30px 0 8px; font-size: 1.05rem; }
/* Wide content scrolls inside its own box; the page body never scrolls sideways. */
.gx-tablewrap { overflow-x: auto; margin-top: 14px; }
.gx-table { border-collapse: collapse; width: 100%; min-width: 380px; }
.gx-table th, .gx-table td { text-align: left; padding: 9px 12px; border-top: 1px solid var(--line); font-size: 0.92rem; }
.gx-table thead th { border-top: 0; font-size: 0.74rem; letter-spacing: 0.1em; text-transform: uppercase; color: var(--muted); }
.gx-table tbody th { font-weight: 700; display: flex; align-items: center; gap: 9px; }
.gx-disc { margin: 20px 0 0; color: var(--muted); font-size: 0.93rem; }

/* 11.8 requires the caveats and the error channel to be VISIBLE beside the results,
   so they sit inside the results card rather than in a section below it. */
.gx-foot { margin-top: 20px; padding-top: 14px; border-top: 1px solid var(--line); }
.gx-foot .gx-linkage { margin: 0 0 8px; padding: 0; border: 0; }
.gx-foot-row { display: flex; align-items: center; gap: 12px; flex-wrap: wrap; margin-top: 12px; }
.gx-report { font-size: 0.85rem; color: var(--muted); }

/* ---- hierarchy pass, 2026-08-09 -----------------------------------------------------
   The first build put two tall pickers above the answer and the two display toggles above
   the answer inside the results card, so the most important thing on the page — the
   sentence that says what the chicks will be — was the third thing you reached. Nothing
   was removed to fix it; the order and the weight changed.

   From ~1060px the widget becomes two columns and the RESULTS STICK. Changing a chip on
   the left now moves the answer in place instead of somewhere off-screen. Below that
   width the single column keeps the natural order: describe the birds, then read the
   answer, with the answer immediately after the second card. */
@media (min-width: 1060px) {
  .gx-app { grid-template-columns: minmax(0, 1fr) minmax(0, 1.15fr); align-items: start; gap: 26px; }
  .gx-parents { grid-template-columns: 1fr; gap: 16px; }
 /* Sticky was the first idea and it was wrong: the results are often taller than the
     viewport (two scenarios, the possible list open), and a sticky panel that tall either
     grows its own scrollbar or hides its own bottom. Adjacent is where the value was —
     you can see the answer while changing a chip — so the column stays and the sticking
     goes. Seen in the browser, not reasoned about. */
  .gx-out { align-self: start; }

  /* When the answer is TWO answers — unknown sex, or a father whose hidden genes could be
     arranged either way — the results need the full width, and the pickers go back to
     sitting side by side above them. The class is set by the widget because CSS cannot
     ask whether a descendant exists. */
  .gx-app.gx-wide { grid-template-columns: 1fr; }
  .gx-app.gx-wide .gx-parents { grid-template-columns: 1fr 1fr; }
}

/* Two short labelled lists instead of one wall of thirteen. */
.gx-sublbl {
  margin: 12px 0 7px; font-size: 0.72rem; font-weight: 700; letter-spacing: 0.1em;
  text-transform: uppercase; color: var(--muted);
}
.gx-sublbl:first-child { margin-top: 0; }

/* The phase question, which only appears for a male hiding both sex-linked genes. It sits
   inside the hidden-genes panel because that is where it becomes relevant, and it gets a
   rule above it because it is a different question from the three yes/no/don't-know rows. */
.gx-phase { margin-top: 14px; padding-top: 12px; border-top: 1px dashed var(--line); }
.gx-phase .gx-lbl { margin-top: 0; }
.gx-phase .gx-hint { margin: 0 0 10px; }

/* Heading and the two display toggles share one row, so the toggles stop pushing the
   answer down the card. */
.gx-out-head {
  display: flex; align-items: baseline; justify-content: space-between;
  gap: 14px; flex-wrap: wrap; margin-bottom: 14px;
}
.gx-out-head .gx-out-h { margin: 0; }
.gx-out-head .gx-toggles { margin: 0; }
.gx-linkcall { margin-top: 24px; }
.gx-linkcall h3 { margin: 0 0 6px; font-size: 1.02rem; }
.gx-linkcall p { margin: 0; }
.gx-src { margin: 10px 0 0; font-size: 0.86rem; color: var(--muted); }
.gx-src a { font-weight: 600; }
