/* Hallmark · redesign (section-scope: steps, privacy-promise, get-the-app) · 2026-07-22
 * pre-emit critique: P4 H4 E4 S4 R5 V4 · theme: Mira brand system (locked, no Hallmark theme)
 * scope note: page-level macrostructure NOT Hallmark-picked - this stamp records a scoped
 * redesign only; diversification rules do not treat this page as a Hallmark build. */
/*
 * Mira Child Care website - shared stylesheet.
 * Tokens follow docs/design/009-design-system-reference.md (cool brand, light theme only).
 * Squarer shapes on purpose: buttons at 12px radius, cards at 16px, hero surfaces at 18px.
 * No pill buttons, no gradients, no dark theme. Cards ship flat at rest (no
 * static shadow) - the one exception is a hover-only lift/shadow on specific
 * card selectors, see the "Card hover effect" section (DESIGN_SPEC.md section 12).
 * Accessibility: the audience skews elderly and vision-impaired, so body text is 17px,
 * touch targets are at least 44px tall, and contrast stays strong throughout.
 */

@font-face {
  font-family: "Geist";
  src: url("../fonts/geist_regular.ttf") format("truetype");
  font-weight: 400;
  font-style: normal;
  font-display: swap;
}
@font-face {
  font-family: "Geist";
  src: url("../fonts/geist_medium.ttf") format("truetype");
  font-weight: 500;
  font-style: normal;
  font-display: swap;
}
@font-face {
  font-family: "Geist";
  src: url("../fonts/geist_semibold.ttf") format("truetype");
  font-weight: 600;
  font-style: normal;
  font-display: swap;
}
/* Instrument Serif @font-face blocks removed 2026-07-29 (owner ruling: the
   font is fully retired from the live site, no selector references it any
   more - see the h1/h2/h3 comment below for the full replacement history).
   The .ttf font files themselves stay on disk in fonts/ per the dispatch
   instruction (not deleted, just no longer loaded) - see fonts/LICENSE.txt. */
@font-face {
  font-family: "Josefin Sans";
  src: url("../fonts/josefin_sans_variable.ttf") format("truetype");
  font-weight: 100 700;
  font-style: normal;
  font-display: swap;
}

:root {
  --bg: #eef3f6;
  --surface: #ffffff;
  --sheet: #f6fafc;
  --fg: #163643;
  --fg2: #3a5662;
  --muted: #5f7a86; /* darkened from the app's #7c97a3 to hold 4.5:1 contrast on white for web body use */
  --line: rgba(22, 54, 67, 0.12);
  --accent: #2e8fd6;
  --accent-dark: #1d6eae; /* darker accent for text links, 4.5:1 on white */
  --accent-soft: #d8ecf9;
  --alert: #ef5f4a;
  --alert-dark: #c23a26; /* darker alert for text on white */
  --radius-hero: 18px;
  --radius-card: 16px;
  --radius-button: 12px;
  --radius-badge: 8px;

  /* v2 "Bright World" illustration tokens - docs/DESIGN_SPEC.md section 3.1.
     SKY / HILL / GRASS / BUSH / MEADOW-WASH values below were REPLACED
     2026-07-21 per docs/051-external-redesign-merge.md section 2's "OWNER
     RULING RECONCILIATION" paragraph: the owner's own dispatching instruction
     named "the tree colors, the ground colors, the sky colors" as an approved
     adoption item, so these scene-only families now carry the external
     designer fork's exact hex values. SEA/FOAM tokens are deliberately left
     UNCHANGED (the owner's list did not name the sea, and the footer's
     AA-large sea-band contrast pairings - DESIGN_SPEC.md section 6 - depend on
     the current --illus-sea-near value). UI-chrome tokens above this comment
     (bg, surface, fg, fg2, muted, line, the accent family, the alert family)
     are untouched - this is a scene-only port, not a re-theme. */
  --illus-sky-top: #b8d6ff;
  --illus-sky-mid: #d9e9ff;
  --illus-sky-horizon: #fdf3dc;
  --illus-sun-disc: #ffdf96;
  --illus-sun-glow: #ffe9b0;
  --illus-gold: #ffc94d;
  --illus-cloud: #ffffff;
  --illus-hill-far: #b8f0db;
  --illus-hill-mid: #8be5c3;
  --illus-grass-start: #2cbf8b;
  --illus-grass-end: #22a777;
  --illus-bush-far: #b8ec81;
  --illus-bush-mid: #82cc3c;
  --illus-sea-far: #2f8f8a;
  --illus-sea-mid: #4fb3a6;
  --illus-sea-near: #1f8f7a;
  --illus-foam: #eafff6;
  --illus-sand: #f2e2b8;
  --illus-rose: #f48fb1;
  --illus-lavender: #c9b8e8;
  --illus-sparkle: #f4f1ff;
  --illus-meadow-wash: #f4fefa;

  /* NEW in v1.1 (section 9 ruling 4, tree trunks/swing ropes) - not present in
     DESIGN_SPEC 3.1's palette table, which has no brown. The fork's own trunk
     values are identical to these, so no port was needed here. */
  --illus-trunk: #a9764f;
  --illus-trunk-dark: #8a5f3d;

  /* Badge-tint tokens (docs/051-external-redesign-merge.md section 3.1): four
     color pairs already used sitewide as hardcoded, section-scoped values
     (the #sharing/#privacy/#play,#faq,#contact section-label overrides
     further down this file), formalized here as reusable custom properties.
     Zero new colors - naming/reuse only. */
  --badge-blue-bg: var(--accent-soft);   /* #d8ecf9 */
  --badge-blue-fg: var(--accent-dark);   /* #1d6eae */
  --badge-sea-bg: #dff3f0;
  --badge-sea-fg: #0f6a5e;               /* 5.61:1 on --badge-sea-bg */
  --badge-grass-bg: #e3f3df;
  --badge-grass-fg: #1f6b3a;             /* 5.63:1 on --badge-grass-bg */
  --badge-gold-bg: #fff2d6;
  --badge-gold-fg: #8a5a00;              /* 5.35:1 on --badge-gold-bg */
  /* Demo/release announcement badge (066-demo-release-announcement-banner.md
     v1.1 - in-header badge beside the wordmark, replacing the retired v1.0
     full-bleed strip). Aliases the existing badge-gold pair rather than
     reusing it directly, so the badge can be recolored independently of the
     FAQ badge later if ever needed. */
  --announce-bg: var(--badge-gold-bg);
  --announce-fg: var(--badge-gold-fg);
  --announce-border: rgba(138, 90, 0, 0.30); /* derived from --badge-gold-fg's own RGB (138,90,0); alpha bumped 0.18->0.30 in v1.1 (section 6.1) so the border alone can separate the badge from the plain header surface */
  /* Neutral "no" mark tokens (brief 006/spec 063, comparison page): the "no" mark is
     a plain neutral dash, never the sitewide --alert red (which is reserved for true
     danger/destructive states, CLAUDE.md accent-discipline rule) and never a bare
     color-only signal (WCAG 1.4.1 - every mark also carries a word). */
  --badge-neutral-bg: #eef1f3;
  --badge-neutral-fg: var(--fg2);        /* computed 6.88:1 on --badge-neutral-bg */

  /* Theme-invariant tokens (DESIGN_SPEC.md section 16.2/16.4, dark-mode round-2
     refinements). Declared once here, NEVER redeclared in either dark block -
     the absence of a dark override IS what makes them fixed, same technique
     already used for --illus-rose/--illus-lavender/--illus-gold/--illus-sparkle.
     --illus-kite-shade: decouples kite shading from --illus-sun-disc, whose
     dark-mode override is still load-bearing for the 5 calm pages' moon
     substitute - kites must stay lit at their light-mode value in both themes.
     --ink-fixed/--phone-*: re-exposes existing light-mode --fg/--fg2/--surface/
     --badge-*-bg/--badge-*-fg values under fixed names, so the phone mockup's
     screen (and the store button's dark ink chip) always render the real,
     light-only MiraTheme app - zero new colors. */
  --illus-kite-shade: #ffdf96;
  --ink-fixed: #163643;
  --phone-bg: #ffffff;
  --phone-fg: #163643;
  --phone-fg2: #3a5662;
  --phone-icon-bg: #ffffff;
  --phone-card-blue-bg: #d8ecf9;
  --phone-card-blue-fg: #1d6eae;
  --phone-card-sea-bg: #dff3f0;
  --phone-card-sea-fg: #0f6a5e;
  --phone-card-grass-bg: #e3f3df;
  --phone-card-grass-fg: #1f6b3a;

  /* Designer round-2 (owner ruling 2, 2026-07-22): the hover "blue glow"
     shadow system, rebuilt on OUR locked brand hues (the fork derived these
     from its own replacement palette, which stays declined): accent glow from
     --accent #2e8fd6, teal/green glows from the badge-sea/grass text hues.
     Theme-invariant on purpose - a soft colored glow reads correctly on both
     light and dark surfaces. --footer-bg: the round-2 footer is no longer a
     dark sea band; it tracks the page background in both themes. */
  --glow-accent: rgba(46, 143, 214, 0.35);
  --glow-teal: rgba(15, 106, 94, 0.28);
  --glow-green: rgba(31, 107, 58, 0.28);
  --footer-bg: var(--bg);
}

/* DARK MODE (docs/051-external-redesign-merge.md section 6.4-6.7). Every value
   below is NEW; the light :root block above is completely unchanged. Two
   activation paths land on the identical dark value list:
     1. OS-level dark mode, picked up automatically unless the visitor has
        explicitly overridden the theme on this site (":not([data-theme=light])").
     2. The header sun/moon toggle (js/theme.js), which sets
        data-theme="dark"|"light" explicitly and wins regardless of OS
        preference.
   --badge-blue-bg/--badge-blue-fg are deliberately NOT redeclared here - they
   are defined in :root as var(--accent-soft)/var(--accent-dark), so they track
   the dark overrides of those two tokens automatically.
   --illus-rose/--illus-lavender/--illus-gold/--illus-sparkle/--illus-cloud are
   deliberately NOT redeclared - kites, flowers, sparkles, and the balloon's
   rose/lavender/gold accents read naturally as lanterns/fireflies/starlight at
   night without a re-theme (doc 051 section 6.6), and white clouds against a
   night sky read naturally as moonlit clouds for the same reason (this file's
   own extension of that reasoning - the doc's clause list did not name clouds
   either way). */
@media (prefers-color-scheme: dark) {
  :root:not([data-theme="light"]) {
    --bg: #0e232c;
    --surface: #163643;
    --sheet: #1c4250;
    --fg: #eaf3f7;
    --fg2: #b9d3dc;
    --muted: #93aab3;
    --line: rgba(234, 243, 247, 0.14);
    --accent: #4f97dd;
    --accent-dark: #8fc3f2;
    --accent-soft: #16324a;
    --alert: #ef5f4a;
    --alert-dark: #ffb3a3;
    --badge-sea-bg: #123b34;
    --badge-sea-fg: #7fe0c9;
    --badge-grass-bg: #1d3a15;
    --badge-grass-fg: #a9e08a;
    --badge-gold-bg: #3a2e10;
    --badge-gold-fg: #f0c875;
    --announce-border: rgba(240, 200, 117, 0.30); /* derived from dark --badge-gold-fg's RGB (240,200,117); alpha bumped 0.18->0.30 in v1.1 */
    --badge-neutral-bg: #24424d;
    --badge-neutral-fg: var(--fg2);       /* dark --fg2 #b9d3dc; computed 6.84:1 */
    --illus-sky-top: #0d1a33;
    --illus-sky-mid: #16264a;
    --illus-sky-horizon: #2c3a63;
    --illus-sun-disc: #f3efd9;
    --illus-sun-glow: #e6e2c9;
    --illus-hill-far: #1c3a30;
    --illus-hill-mid: #14302a;
    --illus-grass-start: #123d2c;
    --illus-grass-end: #0e2f22;
    --illus-bush-far: #1a4536;
    --illus-bush-mid: #123626;
    --illus-sea-far: #0e2b28;
    --illus-sea-mid: #123a34;
    --illus-sea-near: #0a2e28;
    --illus-foam: #cfe8e2;
    --illus-sand: #6b5a3a;
    --illus-trunk: #6b4a35;
    --illus-trunk-dark: #4a3325;
    --illus-meadow-wash: #0f1f19;
    /* New in DESIGN_SPEC.md section 16.2: the cratered moon (index.html/404.html
       only). Dark-mode-only tokens - no light-mode declaration needed since
       .scene-moon never renders in light mode. */
    --illus-moon-disc: #d9dde2;
    --illus-moon-crater: #b7bcc4;
    --illus-moon-crater-dark: #9aa0aa;
  }
  /* The sun becomes a bare, pale disc (reads as a moon) once recolored above;
     display: none (not opacity: 0) wins outright over the rays' own
     scene-sun-rays-pulse opacity animation. Same solid-disc shape, no new SVG. */
  :root:not([data-theme="light"]) .scene-sun-rays {
    display: none;
  }
  /* Star field (DESIGN_SPEC.md section 16.1) - hero + 404 only, per the
     .scene-star base rule further down. */
  :root:not([data-theme="light"]) .scene-star {
    display: block;
  }
  /* Moon upgrade (section 16.2): a second, independent SVG sibling toggled
     against the sun - scoped to index.html/404.html only via body class, so
     the 5 calm pages' existing recolored-sun moon substitute is untouched. */
  :root:not([data-theme="light"]) body.page-home .scene-sun:not(.scene-moon),
  :root:not([data-theme="light"]) body.page-404 .scene-sun:not(.scene-moon) {
    display: none;
  }
  :root:not([data-theme="light"]) .scene-moon {
    display: block;
  }
  /* Fireflies (section 16.3) - desktop gutters, index.html only. */
  :root:not([data-theme="light"]) .scene-firefly {
    display: block;
  }
  :root:not([data-theme="light"]) .scene-meadow-fireflies {
    display: block;
  }
  /* Phone mockup frame/glow (section 16.4) - the fixed --ink-fixed bezel loses
     edge definition against the dark hero sky, so dark mode gets a hairline
     border and a slightly warmer glow. */
  :root:not([data-theme="light"]) .phone-frame {
    border: 1px solid rgba(234, 243, 247, 0.2);
  }
  :root:not([data-theme="light"]) .phone-glow {
    background: radial-gradient(closest-side, rgba(79, 151, 221, 0.30), rgba(15, 143, 122, 0.18) 60%, transparent 80%);
  }
  /* Sample-report card shadow (open-followup #377): the light-mode ink shadow
     (rgba(22, 54, 67, ...)) is nearly the same hue as dark --bg (#0e232c), so
     it disappears; reuses .phone-glow's dark-mode accent-blue value above -
     same fix, same idiom, applied to a box-shadow instead of a background. */
  :root:not([data-theme="light"]) .sample-report-visual img {
    box-shadow: 0 20px 40px rgba(79, 151, 221, 0.30);
  }
}
:root[data-theme="dark"] {
  --bg: #0e232c;
  --surface: #163643;
  --sheet: #1c4250;
  --fg: #eaf3f7;
  --fg2: #b9d3dc;
  --muted: #93aab3;
  --line: rgba(234, 243, 247, 0.14);
  --accent: #4f97dd;
  --accent-dark: #8fc3f2;
  --accent-soft: #16324a;
  --alert: #ef5f4a;
  --alert-dark: #ffb3a3;
  --badge-sea-bg: #123b34;
  --badge-sea-fg: #7fe0c9;
  --badge-grass-bg: #1d3a15;
  --badge-grass-fg: #a9e08a;
  --badge-gold-bg: #3a2e10;
  --badge-gold-fg: #f0c875;
  --announce-border: rgba(240, 200, 117, 0.30); /* derived from dark --badge-gold-fg's RGB (240,200,117); alpha bumped 0.18->0.30 in v1.1 */
  --badge-neutral-bg: #24424d;
  --badge-neutral-fg: var(--fg2);         /* dark --fg2 #b9d3dc; computed 6.84:1 */
  --illus-sky-top: #0d1a33;
  --illus-sky-mid: #16264a;
  --illus-sky-horizon: #2c3a63;
  --illus-sun-disc: #f3efd9;
  --illus-sun-glow: #e6e2c9;
  --illus-hill-far: #1c3a30;
  --illus-hill-mid: #14302a;
  --illus-grass-start: #123d2c;
  --illus-grass-end: #0e2f22;
  --illus-bush-far: #1a4536;
  --illus-bush-mid: #123626;
  --illus-sea-far: #0e2b28;
  --illus-sea-mid: #123a34;
  --illus-sea-near: #0a2e28;
  --illus-foam: #cfe8e2;
  --illus-sand: #6b5a3a;
  --illus-trunk: #6b4a35;
  --illus-trunk-dark: #4a3325;
  --illus-meadow-wash: #0f1f19;
  --illus-moon-disc: #d9dde2;
  --illus-moon-crater: #b7bcc4;
  --illus-moon-crater-dark: #9aa0aa;
}
:root[data-theme="dark"] .scene-sun-rays {
  display: none;
}
:root[data-theme="dark"] .scene-star {
  display: block;
}
:root[data-theme="dark"] body.page-home .scene-sun:not(.scene-moon),
:root[data-theme="dark"] body.page-404 .scene-sun:not(.scene-moon) {
  display: none;
}
:root[data-theme="dark"] .scene-moon {
  display: block;
}
:root[data-theme="dark"] .scene-firefly {
  display: block;
}
:root[data-theme="dark"] .scene-meadow-fireflies {
  display: block;
}
:root[data-theme="dark"] .phone-frame {
  border: 1px solid rgba(234, 243, 247, 0.2);
}
:root[data-theme="dark"] .phone-glow {
  background: radial-gradient(closest-side, rgba(79, 151, 221, 0.30), rgba(15, 143, 122, 0.18) 60%, transparent 80%);
}
:root[data-theme="dark"] .sample-report-visual img {
  box-shadow: 0 20px 40px rgba(79, 151, 221, 0.30);
}
/* color-scheme: hints native UA chrome (form controls, scrollbars) to render
   in the matching scheme. Only set on the two explicit-toggle paths, exactly
   as doc 051 section 6.5 specifies - the OS-preference-only path (no explicit
   data-theme attribute) does not set this property, matching the spec text. */
:root[data-theme="dark"] {
  color-scheme: dark;
}
:root[data-theme="light"] {
  color-scheme: light;
}

* {
  box-sizing: border-box;
}

/* Fix (audit item 2): scroll-behavior is itself a motion effect (animates the
   scroll position on every in-page anchor jump), so it belongs behind the
   same reduced-motion gate as the rest of the scene's animation - a
   reduced-motion visitor gets instant jumps, not a smooth-scroll animation. */
@media (prefers-reduced-motion: no-preference) {
  html {
    scroll-behavior: smooth;
  }
}

body {
  margin: 0;
  background: var(--bg);
  color: var(--fg);
  font-family: "Geist", system-ui, -apple-system, "Segoe UI", Roboto, sans-serif;
  font-size: 17px;
  line-height: 1.6;
  -webkit-text-size-adjust: 100%;
  /* Sticky footer: on short pages (404, signed-out account, tall monitors on
     the legal pages), keep the footer pinned to the bottom of the viewport
     instead of the page background wash showing as a band below it. 100vh
     first as the fallback, 100dvh as the progressive enhancement (mobile
     browser chrome). main (below) is the one flex child that grows; header,
     decorative bands/strips, and the footer keep their natural size. */
  min-height: 100vh;
  min-height: 100dvh;
  display: flex;
  flex-direction: column;
}

/* Sticky-footer growth: every page has exactly one <main>, which absorbs the
   leftover vertical space so header/scene-bands/footer/scene-strips stay
   their natural size and the footer sits at the bottom of short pages. */
main {
  flex: 1 0 auto;
}

/* Skip link: hidden until focused, then clearly visible at the top of the page. */
.skip-link {
  position: absolute;
  left: -9999px;
  top: 0;
  background: var(--accent);
  color: #ffffff;
  font-weight: 600;
  padding: 12px 20px;
  border-radius: 0 0 var(--radius-button) 0;
  z-index: 100;
}
.skip-link:focus {
  left: 0;
}

h1, h2, h3 {
  /* Owner ruling 2026-07-28: heading typeface switched from Instrument Serif to
     Josefin Sans. Weight bumped 400 -> 600 - Josefin Sans at 400 has a low
     x-height and reads thin/low-contrast as a heading face, unlike the serif it
     replaces. Owner ruling 2026-07-28 (follow-up): the promise-lead statement
     ("No ads, ever.") also switched to Josefin Sans at 600 weight - it no
     longer stays serif. Owner ruling 2026-07-29: the remaining decorative
     Instrument Serif accents (hero stat values, step numbers, FAQ badge,
     testimonial quote marks, founder signature) are ALSO switched to Josefin
     Sans - the "decorative accents stay serif" exception above is REMOVED.
     Instrument Serif is fully retired from the live site; the @font-face
     blocks for it were removed from this file (no remaining usage) - the
     .ttf font files stay on disk, see fonts/LICENSE.txt. */
  font-family: "Josefin Sans", "Geist", system-ui, sans-serif;
  font-weight: 600;
  line-height: 1.2;
  color: var(--fg);
  margin: 0 0 0.5em 0;
}
h1 { font-size: 2.6rem; }
h2 { font-size: 1.9rem; }
h3 { font-size: 1.4rem; }

p, li {
  color: var(--fg2);
}

strong {
  color: var(--fg);
}

a {
  color: var(--accent-dark);
  text-decoration: underline;
  text-underline-offset: 3px;
}
a:hover, a:focus {
  color: var(--fg);
}
a:focus-visible,
button:focus-visible,
input:focus-visible,
summary:focus-visible,
.testimonial-track:focus-visible {
  outline: 3px solid var(--accent);
  outline-offset: 2px;
}

.container {
  /* Owner ruling 6 (2026-07-22): 880 -> 1080; second widening same day after
     live review ("still too dense"): 1080 -> 1280, sitewide. */
  max-width: 1280px;
  margin: 0 auto;
  padding: 0 24px;
}

.narrow {
  max-width: 720px;
}

/* Utility spacing/heading classes (followup #303): these replace inline style=""
   attributes on index.html and account.html so the site can run a Content-Security-Policy
   without style-src 'unsafe-inline'. Each was a one-off margin-top override or a compact
   Geist h2 (matching .danger-zone h2's weight/size but without the alert color and
   with margin-top reset to 0 for use inside a .card). */
.mt-0 { margin-top: 0; }
.mt-20 { margin-top: 20px; }
.mt-24 { margin-top: 24px; }
.mt-40 { margin-top: 40px; }
.h2-compact {
  font-family: "Geist", system-ui, sans-serif;
  font-weight: 600;
  font-size: 1.2rem;
  margin-top: 0;
}

/* Demo/release announcement badge (066-demo-release-announcement-banner.md
   v1.1) - RETIRED from every page's header (and mobile nav-menu-panel) by
   owner ruling 2026-07-29 (second follow-up, same day as the homepage-only
   move below): the owner did not want the badge appearing in any header
   next to the logo on any page (compare.html called out specifically). The
   v1.1 in-header component (.announce-badge/.announce-badge-dot/
   .announce-badge-text, the <=760px collapse-to-corner-dot rule, and the
   .nav-menu-panel instance) is fully removed - zero remaining HTML usages,
   confirmed by grep before deletion. The homepage's own .hero-copy
   .release-pill (see index.html + its own CSS further down this file) is
   now the ONLY place the demo/release note appears anywhere on the site.
   The --announce-bg/-fg/-border custom properties (:root, dark-mode blocks)
   are left in place, unused, rather than deleted - not part of this
   ruling's explicit scope and harmless to keep defined.
   Owner ruling 2026-07-29 (homepage only, EARLIER same-day ruling, still in
   effect): on mobile the in-header badge made the header stack read badly,
   so on index.html the badge had already moved out of .header-brand
   entirely and into .hero-copy, directly above the <h1>, before the second
   ruling above removed it from the other 10 pages altogether. */
.header-brand {
  display: flex;
  align-items: center;
  flex-wrap: nowrap; /* the brand group itself never breaks onto 2 lines - if
                         it doesn't fit, the WHOLE group wraps below nav via
                         the existing .site-header .container flex-wrap: wrap */
  gap: 10px;
  min-width: 0;
}

/* Header */
/* Owner ruling 2026-07-29: the header must follow the scroll ("the menu
   should always be usable") - sticky instead of static/relative, on every
   page (all pages share this one .site-header rule - verified by grep,
   no page has its own header override).
   z-index audit performed before picking 40 (site.css, all files, grep for
   "z-index" this same day): the highest value any in-page decorative/
   content layer uses is 2 (.hero.scene-hero .container, .scene-404-raft,
   .phone-notch - all local stacking contexts anchored to their own
   position:relative ancestor section, so they never actually compete with
   the header at the root stacking context, but 40 clears them with a wide
   margin regardless in case that ever changes). The only thing 40 is
   deliberately kept BELOW is .skip-link's z-index 100, so a keyboard user
   who focuses "Skip to main content" still sees it render on top of the
   now-also-fixed-to-the-viewport-top header, not underneath it.
   The mobile .nav-menu-panel dropdown (z-index: 90, see its own rule
   further down) is a DESCENDANT of .site-header. Giving .site-header its
   own explicit z-index makes it establish a stacking context of its own
   (it already had position:relative before this ruling, which alone does
   NOT create one without a z-index) - the panel's z-index: 90 now only
   needs to out-rank its own header siblings (trivial, nothing else in the
   header uses a z-index that high), and the whole header - panel included
   - paints as one unit above every other in-flow page element because of
   the header's own z-index: 40. No change to the panel's z-index value was
   needed; its own comment is updated separately to reflect the new,
   locally-scoped comparison instead of the old page-wide one. */
.site-header {
  background: var(--surface);
  border-bottom: 1px solid var(--line);
  position: sticky;
  top: 0;
  z-index: 40;
}
.site-header .container {
  display: flex;
  align-items: center;
  justify-content: space-between;
  flex-wrap: wrap;
  gap: 8px;
  min-height: 64px;
  padding-top: 8px;
  padding-bottom: 8px;
}
/* Owner ruling 2026-07-29 (companion to the sticky-header change above):
   with the header now permanently docked at the top of the viewport,
   in-page anchor jumps (nav links to #sharing/#privacy/#testimonials/#faq
   on index.html, "Get Mira on Google Play" to #play on the 3 for-*.html
   landing pages, and compare.html's own #compare-table/#compare-context/
   #compare-cta sections) would land their target flush against the header,
   with its top edge hidden underneath it. `section[id]` is a single,
   future-proof selector - every anchor-jumpable section on the site is a
   `<section id="...">` (verified by grep across every page), so this one
   rule covers all of them, present and future, without hand-listing ids.
   80px = the header's own ~65px natural height (64px min-height + 1px
   bottom border, both already border-box) plus a comfortable ~15px of
   breathing room so the heading does not sit pinned flush under the bar. */
section[id] {
  scroll-margin-top: 80px;
}
/* Designer round-2 (owner ruling 8, 2026-07-22): the header now carries the
   designer's FULL logo lockup (icon + MIRA + Child Care App, one inline SVG at
   its native 354.81x108.56 aspect) instead of icon + HTML text. The lockup's
   own letter blue (#3d90d7) is the logo asset's real color - logo/brand-name
   contrast exemption applies, same as DESIGN_SPEC 11.7. */
.logo-mark--full {
  display: block;
  width: 144px;
  height: 44px;
  flex-shrink: 0;
}
.wordmark {
  display: inline-flex;
  align-items: center;
  gap: 10px;
  min-height: 44px;
  /* font-family/font-size intentionally removed 2026-07-29 (Instrument Serif
     replacement pass): this selector no longer wraps any text node on any
     page - every page's wordmark link is now the .logo-mark--full SVG/img
     lockup (see the "Designer round-2" comment above), which bakes its own
     brand-blue outlined paths rather than rendering CSS-styled text. The
     rule was dead (no visible effect) before this pass too; left unset
     rather than pointed at Josefin Sans for a font that renders nothing. */
  color: var(--fg);
  text-decoration: none;
}
.wordmark:hover, .wordmark:focus {
  color: var(--accent-dark);
}
/* Bug fix (2026-07-28, owner report on /compare): this selector used to size
   the old icon-only header mark (DESIGN_SPEC.md section 11, 29x32px) before
   the "designer round-2" full lockup change above replaced it with
   .logo-mark--full (144x44px desktop). The two rules were left to coexist,
   and because ".wordmark img" (one class + one element = higher specificity)
   beats ".logo-mark--full" (one class alone), every <img class="logo-mark--full">
   on every page except index.html (which uses an inline <svg>, not an <img>,
   so this selector never matched it) was silently forced down to 29x32px at
   desktop widths - a real, visible "logo is too small to see" bug, not a
   design choice. .logo-mark--full already carries its own full sizing
   (including the two smaller mobile-breakpoint overrides shared below), so
   this stale rule is removed rather than reconciled. */
.header-nav {
  display: flex;
  gap: 6px;
  align-items: center;
  flex-wrap: wrap;
}
/* v1.1 section 9 ruling 8: quiet 12px-radius block buttons instead of plain
   underlined text links. No underline at rest; hover/focus fills accent-soft.
   fg2-on-white (7.7:1) and fg-on-accent-soft are both already-sanctioned v1
   pairings (spec section 6 / .auth-tab[aria-selected]), reused here as-is. */
.header-nav a {
  display: inline-flex;
  align-items: center;
  min-height: 44px;
  padding: 0 14px;
  border-radius: var(--radius-button);
  font-size: 1rem;
  font-weight: 500;
  color: var(--fg2);
  text-decoration: none;
}
.header-nav a:hover,
.header-nav a:focus {
  background: var(--accent-soft);
  color: var(--fg);
}
/* "Sign in" becomes the filled accent CTA. White-on-accent (3.5:1) is the
   existing sanctioned AA-large pairing (spec section 6); 17px + semibold is
   the explicit floor that pairing requires, used verbatim (not rem-derived,
   so there is no rounding ambiguity about clearing the floor). */
.header-nav a.nav-cta {
  background: var(--accent);
  color: #ffffff;
  font-size: 17px;
  font-weight: 600;
}
.header-nav a.nav-cta:hover,
.header-nav a.nav-cta:focus {
  background: var(--accent-dark);
  color: #ffffff;
}

/* Sections */
.section {
  padding: 56px 0;
}
.section + .section {
  border-top: 1px solid var(--line);
}
.section-label {
  display: inline-block;
  font-size: 0.8rem;
  font-weight: 600;
  letter-spacing: 0.08em;
  text-transform: uppercase;
  color: var(--accent-dark);
  background: var(--accent-soft);
  border-radius: var(--radius-badge);
  padding: 4px 10px;
  margin-bottom: 14px;
}

/* Hero */
.hero {
  padding: 72px 0 64px 0;
  text-align: center;
}
.hero h1 {
  max-width: 640px;
  margin-left: auto;
  margin-right: auto;
}
.hero .lede {
  font-size: 1.2rem;
  max-width: 620px;
  margin: 0 auto 12px auto;
}
/* Hero promise chips (067-hero-promise-chips-spec.md): replaces the single-
   line .hero-promise paragraph with 3 separate small colored cards, one per
   phrase. blue/sea/grass rotation (gold reserved for the demo/release badge,
   066). Informational, not clickable - no hover/focus/press treatment, no
   scroll-reveal entry (same precedent as .hero-stats li). */
.hero-promise-list {
  list-style: none;
  display: flex;
  flex-wrap: wrap;
  gap: 10px;
  margin: 0 0 28px 0;
  padding: 0;
  justify-content: center;
}
.hero-promise-list li {
  background: var(--badge-blue-bg);
  color: var(--badge-blue-fg);
  border-radius: var(--radius-card);
  padding: 10px 16px;
  font-family: "Geist", system-ui, sans-serif;
  font-weight: 600;
  font-size: 17px;
  line-height: 1.3;
  white-space: normal;
}
.hero-promise-list li:nth-child(2) {
  background: var(--badge-sea-bg);
  color: var(--badge-sea-fg);
}
.hero-promise-list li:nth-child(3) {
  background: var(--badge-grass-bg);
  color: var(--badge-grass-fg);
}
/* Homepage only: hero-copy is left-aligned at >=900px (body.page-home
   override above), so the chip row left-aligns to match. The 3 for-*.html
   landing pages never override hero-copy away from centered, so they stay
   centered at every width and need no override here. */
@media (min-width: 900px) {
  body.page-home .hero-promise-list {
    justify-content: flex-start;
  }
}

/* Hero phone mockup layout (docs/051-external-redesign-merge.md section 4.1):
   copy left / phone right at >=900px, stacked (copy above phone) below that.
   The shared .container stays at its existing 880px max-width - the phone is
   scaled down to fit inside it (see the phone-frame rules further down), not
   a sitewide layout change. */
.hero-grid {
  display: grid;
  grid-template-columns: 1fr;
  align-items: center;
  gap: 28px;
  text-align: center;
}
.hero-copy {
  text-align: center;
}
/* Owner ruling 2026-07-29 (homepage only, see the .release-pill comment
   below): the demo/release badge is the first child of .hero-copy on
   index.html only. inline-flex follows the parent's text-align naturally
   (centered below 900px, left below the body.page-home override above
   900px), so no alignment override is needed here - just breathing room
   under the badge before the <h1>. */
.hero-copy > .release-pill {
  margin-bottom: 14px;
}
/* Release pill (owner ruling 2026-07-29, restyle of the homepage hero
   badge only). Note (same day, second follow-up ruling): the other 10
   pages' header .announce-badge instance this comment used to reference as
   "untouched" is now fully RETIRED - see the "Demo/release announcement
   badge" comment block near .header-brand above for the removal. This
   homepage .release-pill is the only surviving instance of the demo/
   release note anywhere on the site. Owner specified exact
   fixed colors (light mint bg, dark-green text) rather than theme tokens,
   so - same pattern as the phone mockup's --phone-* fixed tokens further
   down this file (search "theme-invariant") - this chip is deliberately
   hardcoded to render identically in light and dark mode: a light pill
   on a dark hero is the intended look, not a bug. Contrast of #2E5E4E on
   #F3F8F5 computes to ~6.9:1, comfortably clearing AA-normal (4.5:1) at
   this 14px size.
   Height/wrap note (implementation judgment, not a spec deviation): the
   owner's copy ("Full Android Release . November 20, 2026") is long enough
   that a strict fixed 36px single-line pill would overflow the ~316px
   mobile content column at 360px viewports. 36px is honored as a MINIMUM
   (single line at desktop, where it fits comfortably under the 460px
   hero-copy column) rather than a fixed height, and the text is allowed to
   wrap to 2 lines at narrow widths, growing the pill slightly rather than
   causing horizontal overflow - the owner's 36px/no-wrap intent still holds
   everywhere it fits. */
.release-pill {
  display: inline-flex;
  align-items: center;
  gap: 6px;
  min-height: 36px;
  /* Owner gave a range (18-22px); 20px picked as the midpoint. */
  padding: 6px 20px;
  border-radius: 999px;
  background: #F3F8F5;
  color: #2E5E4E;
  box-shadow: 0 4px 16px rgba(30, 60, 50, 0.08);
}
.release-pill-icon {
  flex-shrink: 0;
}
.release-pill-text {
  font-family: "Geist", system-ui, sans-serif;
  font-weight: 500;
  font-size: 14px;
  line-height: 1.3;
  color: #2E5E4E;
}
@media (min-width: 900px) {
  /* Owner ruling 2026-07-28 (hero downscale, desktop only - mobile below
     900px is untouched): the previous 1fr/auto track let .hero-copy grow to
     fill whatever width was left over after the phone column, so the
     headline wrapped at an uncapped, near-full-container measure - the
     "type fills the screen" complaint. Both tracks are now sized to their
     own content (a capped copy column, an auto phone column) and the pair
     is centered as a unit within .container, so the composed text+phone
     group reads as one scene element with visible sky/scene around it on
     both sides, matching the target proportions - instead of stretching
     edge to edge. */
  /* .hero-grid only exists on index.html (the 3 for-*.html landing pages
     deliberately omit it, see the comment below), so this is already
     homepage-only with no extra scoping needed.
     Owner ruling 2026-07-29 (hero-width follow-up): superseded to a
     full-width layout - see the body.page-home override further down this
     file (search "hero-width follow-up") for the live values and the full
     reasoning; that override wins the cascade (same >=900px breakpoint,
     equal-or-higher specificity, later in source order), so these values
     are effectively a same-shape fallback rather than a second source of
     truth. Kept consistent with the override rather than left stale. */
  .hero-grid {
    grid-template-columns: minmax(0, 1fr) auto;
    justify-content: stretch;
    gap: 48px;
    text-align: left;
  }
  /* Scoped to body.page-home (index.html's own body class) rather than the
     bare .hero-copy/.hero.scene-hero selectors, which the 3 for-*.html
     landing pages (spec 062) also use for their own, already-correctly-sized
     hero copy (body.page-landing) - this downscale is homepage-only, per the
     owner's 2026-07-28 ruling scope. */
  body.page-home .hero-copy {
    text-align: left;
    max-width: 640px;
  }
  .hero h1, .hero .lede {
    margin-left: 0;
    margin-right: 0;
    max-width: none;
  }
  /* Headline/lede shrink to match: was inheriting the sitewide 3.2rem h1 and
     a 1.2rem lede, oversized once the phone mockup sat directly beside them.
     18px lede keeps the sitewide 17px body-text floor with a hair of margin. */
  body.page-home .hero.scene-hero h1 {
    font-size: 2.4rem;
  }
  body.page-home .hero.scene-hero .lede {
    font-size: 1.125rem;
  }
  body.page-home .hero.scene-hero .hero-stats {
    margin-top: 14px;
  }
  /* Condition/caregiver-role landing pages (website/docs/062-condition-
     landing-pages-spec.md section 3.2 finding, resolved per that section's
     own instruction): the rules above assume the two-column .hero-grid/
     .hero-visual layout is present to bound the copy column's width. These
     3 pages deliberately omit .hero-grid (no phone mockup, spec section
     5.1), so without this fix .hero-copy would render as a left-aligned,
     uncapped-width single column inside .container - which widened to
     1280px in a later session, making an unbounded hero heading/lede read
     as stretched and hard to scan at desktop widths. Restores the existing
     640px hero-copy cap the rest of the site already uses, without forcing
     centering (spec 062 explicitly does not ask for that). */
  .hero:not(:has(.hero-grid)) .hero-copy {
    max-width: 640px;
  }
}

/* Hero stat chips (docs/051-external-redesign-merge.md section 3.2). Three
   honest, evergreen facts under the hero CTA - deliberately excluded from the
   card-hover system (DESIGN_SPEC.md section 12.2): above-the-fold, not part
   of the mid-page scroll-hover language, and three tiny chips lifting on
   hover would read as fidgety next to a static hero. No hover CSS. */
.hero-stats {
  list-style: none;
  display: flex;
  flex-wrap: wrap;
  justify-content: center;
  gap: 10px;
  margin: 20px 0 0 0;
  padding: 0;
}
@media (min-width: 900px) {
  .hero-stats { justify-content: flex-start; }
}
.hero-stats li {
  background: var(--surface);
  border: 1px solid var(--line);
  border-radius: var(--radius-card);
  padding: 8px 14px;
  display: flex;
  flex-direction: column;
  min-width: 100px;
}
.hero-stats .hero-stat-value {
  /* Owner ruling 2026-07-29: Instrument Serif replaced with Josefin Sans
     sitewide. Weight 600 added (same "Josefin at 400 reads thin" reasoning
     as the h1/h2/h3 comment above) - font-size unchanged, 1.4rem is already
     large enough that the added weight alone keeps the same visual size. */
  font-family: "Josefin Sans", "Geist", system-ui, sans-serif;
  font-weight: 600;
  font-size: 1.4rem;
  color: var(--accent-dark);
  line-height: 1.1;
}
.hero-stats li:nth-child(2) .hero-stat-value { color: var(--badge-sea-fg); }
.hero-stats li:nth-child(3) .hero-stat-value { color: var(--badge-grass-fg); }
.hero-stats .hero-stat-label {
  font-size: 0.82rem;
  font-weight: 500;
  color: var(--fg2);
}

/* Hero phone mockup chrome (docs/051-external-redesign-merge.md section 4.2).
   aria-hidden decorative mockup - never real body content, so the sitewide
   17px/4.5:1 body-text floor does not govern the small on-screen labels here
   (same reasoning already used for the illustrated scene's other decorative
   text). Colors reused verbatim from already-sanctioned tokens (--fg on
   --surface, the badge-tint pairs from section 3.1) - no new contrast
   pairing. Scaled down from the fork's sizes to fit the existing 880px
   .container without crowding the copy column - see 4.1. */
.hero-visual {
  position: relative;
  display: flex;
  justify-content: center;
  margin-top: 4px;
}
@media (min-width: 900px) {
  .hero-visual {
    margin-top: 0;
  }
}
.phone-glow {
  position: absolute;
  width: 260px;
  height: 260px;
  left: 50%;
  top: 50%;
  transform: translate(-50%, -50%);
  background: radial-gradient(closest-side, rgba(46, 143, 214, 0.22), rgba(15, 143, 122, 0.12) 60%, transparent 80%);
  border-radius: 50%;
  z-index: 0;
}
.phone-frame {
  position: relative;
  z-index: 1;
  width: 260px;
  border-radius: 36px;
  /* DESIGN_SPEC.md section 16.4: --ink-fixed, not --fg - --fg flips value in
     dark mode, which previously left the bezel a pale near-white color. */
  background: var(--ink-fixed);
  padding: 12px;
  box-shadow: 0 16px 32px rgba(22, 54, 67, 0.22);
}
.phone-notch {
  position: absolute;
  top: 22px;
  left: 50%;
  transform: translateX(-50%);
  width: 76px;
  height: 18px;
  background: var(--ink-fixed);
  border-radius: 9px;
  z-index: 2;
}
.phone-screen {
  position: relative;
  /* DESIGN_SPEC.md section 16.4: --phone-bg, not --surface - the screen must
     always render the real, light-only MiraTheme app, in both site themes.
     Designer round-2 (owner rulings 7 + her taller silhouette): a real phone
     reads ~0.52 width:height; screenshots fill the screen EDGE TO EDGE (the
     reel-screen img below is absolutely inset to the screen box and clipped
     by this element's radius - no padding gap around the picture). */
  background: var(--phone-bg);
  border-radius: 26px;
  min-height: 470px;
  overflow: hidden;
}
/* .phone-greeting removed 2026-07-29 (Instrument Serif replacement pass):
   the hero reel's three "pages" were re-scoped to real app screenshots in
   an earlier session (see .reel-screen img below) and no HTML on the site
   references .phone-greeting any more - it was dead, unreachable CSS, not
   converted to Josefin Sans for the same reason .phone-greeting's own
   context comment already gives ("Real screenshots... fills the whole
   screen box"). */
/* The reel: three .reel-screen "pages" stacked on top of each other inside
   the phone. Default/reduced-motion state (this rule, ungated): only the
   first screen shows, at full opacity, no motion at all - a fully static
   phone permanently showing screen 1. The looping crossfade only turns on
   inside the prefers-reduced-motion: no-preference query further down
   (051 section 4.3) - "absence of a rule is the correct reduced-motion
   state" is the same pattern the scroll-reveal and button-press effects
   already use elsewhere in this file. */
.phone-reel {
  position: absolute;
  inset: 0;
}
.reel-screen {
  position: absolute;
  inset: 0;
  opacity: 0;
  overflow: hidden;
  border-radius: 26px;
}
/* Real screenshots (owner ruling 7, 2026-07-22): the picture fills the whole
   screen box, corner-rounded to the screen's own radius, top-anchored so the
   app's header stays visible and the capture's bottom nav falls out of frame.
   The drawn notch overlays the screenshot's status-bar strip. */
.reel-screen img {
  display: block;
  width: 100%;
  height: 100%;
  object-fit: cover;
  object-position: top;
  border-radius: inherit;
}
.reel-screen:first-child {
  opacity: 1;
}
@media (min-width: 900px) {
  /* Owner ruling 2026-07-28 (hero downscale, desktop only): the mockup was
     350px wide with a 640px-tall screen - roughly the same footprint as the
     headline block beside it, which is why the hero read as "phone fills
     the screen" rather than a small mockup sitting inside the illustrated
     scene. Scaled down to ~60% (210px frame) with every other dimension
     (notch, radii, glow, screen min-height) scaled by the same factor so the
     phone keeps its real proportions, just smaller.
     Owner ruling 2026-07-29 (live desktop/mobile review): that downscale
     went too far - at >=900px the 210px phone read visibly SMALLER than the
     540px copy column beside it (see the ROUND 2 hero-grid override below
     this block, further down the file), the opposite problem from the one
     the 2026-07-28 ruling fixed. This partially supersedes that ruling:
     frame width raised 210px -> 310px (bigger than even the pre-downscale
     mobile size) so the phone reads at least as tall as the copy column.
     Every other dimension is re-scaled from the base (<900px) rule above by
     the same ratio (310/260 = ~1.192) so the mockup keeps its real-phone
     proportions - glow given directly per the dispatch (~320px, close to
     the ratio's own 310); screen min-height uses the dispatch's own
     0.52-ratio math (310 * 470/260 = ~560px). Mobile (<900px) is completely
     untouched - it still uses the base 260px rule above. */
  .phone-glow {
    width: 320px;
    height: 320px;
  }
  .phone-frame {
    width: 310px;
    border-radius: 42px;
    padding: 14px;
  }
  .phone-notch {
    top: 26px;
    width: 90px;
    height: 22px;
  }
  .phone-screen {
    border-radius: 30px;
    min-height: 560px;
  }
  .reel-screen {
    border-radius: 30px;
  }
}
/* DESIGN_SPEC.md section 16.4: every .app-card/.app-card-icon color below
   is wired to the new --phone-* theme-invariant tokens (var-for-var swap of
   the same values, no other property changes) so the mockup's screen is
   pixel-identical in both site themes. */
.app-card {
  position: relative;
  display: flex;
  align-items: center;
  gap: 8px;
  background: var(--phone-card-blue-bg);
  border-radius: var(--radius-card);
  padding: 9px 11px;
  margin-bottom: 8px;
}
.app-card--sea {
  background: var(--phone-card-sea-bg);
}
.app-card--sea .app-card-icon-svg {
  stroke: var(--phone-card-sea-fg);
}
.app-card--grass {
  background: var(--phone-card-grass-bg);
}
.app-card--grass .app-card-icon-svg {
  stroke: var(--phone-card-grass-fg);
}
.app-card-icon {
  width: 22px;
  height: 22px;
  flex-shrink: 0;
  border-radius: 50%;
  background: var(--phone-icon-bg);
  display: flex;
  align-items: center;
  justify-content: center;
}
.app-card-icon svg {
  width: 12px;
  height: 12px;
}
.app-card-icon-svg {
  stroke: var(--phone-card-blue-fg);
}
.app-card-text .app-card-title {
  display: block;
  font-weight: 600;
  font-size: 0.78rem;
  color: var(--phone-fg);
}
.app-card-text .app-card-sub {
  display: block;
  font-size: 0.68rem;
  color: var(--phone-fg2);
}

/* Buttons */
.button {
  display: inline-block;
  background: var(--accent);
  color: #ffffff;
  font-family: inherit;
  font-size: 1.05rem;
  font-weight: 600;
  text-decoration: none;
  text-align: center;
  border: none;
  border-radius: var(--radius-button);
  padding: 14px 28px;
  min-height: 48px;
  cursor: pointer;
  /* Press/lift physics (DESIGN_SPEC.md section 14.3): the rest-state transition
     declaration covers hover-in/out, focus-in/out, and press/release consistently
     in both directions. Transform-only, no box-shadow (unlike the section 12 card
     hover effect) - buttons already read as solid, filled controls at rest. */
  transition: transform 180ms ease-out;
}
.button:hover, .button:focus, .button:active {
  background: #1d6eae;
  color: #ffffff;
}
.button.secondary {
  background: var(--surface);
  color: var(--fg);
  border: 1px solid var(--line);
}
.button.secondary:hover, .button.secondary:focus, .button.secondary:active {
  border-color: var(--fg2);
  color: var(--fg);
}
.button.danger {
  background: var(--alert);
}
.button.danger:hover, .button.danger:focus, .button.danger:active {
  background: var(--alert-dark);
}
.button:disabled {
  opacity: 0.5;
  cursor: not-allowed;
}
.button.full {
  display: block;
  width: 100%;
}

/* Official Google "G" mark on every Google button (docs/061-google-button-
   branding-and-carousel-drag.md Part 1). The chip fill is hardcoded #ffffff,
   never a theme token - see 1.7 for the computed proof the one asset is
   correct, unmodified, in both light and dark theme. */
.button--google {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  gap: 10px;
}
.button--google .icon-google-g {
  flex-shrink: 0;
}
/* .full sets display: block; this combined selector wins regardless of
   declaration order so the full-width Google buttons still center the
   icon+label as one flex group instead of reverting to block/text-align. */
.button.full.button--google {
  display: flex;
}

/* Button press/lift physics, transform layer (DESIGN_SPEC.md section 14.3).
   Guard structure:
   - :hover transform is mouse-only, gated (hover: hover) - no sticky-hover-on-tap.
   - :focus transform is NOT gated by (hover: hover) - a keyboard user on a
     touch-primary device still needs the visible lift (mirrors section 12.4's
     :focus-within carve-out).
   - :active transform is NOT gated by (hover: hover) either - :active is not a
     hover state and must fire on a touch press too.
   - All three stay behind prefers-reduced-motion: no-preference; under reduce,
     only the color-swap rules above apply (transform stays unset).
   Ordering below is load-bearing: .button:active is declared LAST among the
   three transform rules so it wins the specificity tie when a button is pressed
   while also hovered/focused - do not reorder these rules. */
@media (hover: hover) and (prefers-reduced-motion: no-preference) {
  .button:hover {
    transform: translateY(-2px);
  }
}
@media (prefers-reduced-motion: no-preference) {
  .button:focus {
    transform: translateY(-2px);
  }
  .button:active {
    transform: translateY(0) scale(0.97);
  }
}

/* Cards */
.card {
  background: var(--surface);
  border: 1px solid var(--line);
  border-radius: var(--radius-card);
  padding: 24px;
}
.card.hero-card {
  border-radius: var(--radius-hero);
  background: var(--accent-soft);
  border-color: transparent;
}

/* How-sharing-works steps */
/* Hallmark redesign 2026-07-22 (steps section): the 3-equal-card grid - a
   flagged AI tell - becomes an editorial numbered flow: oversized serif
   numerals, heading + body beside them, hairline rules between rows, and a
   progressive indent at desktop so the eye steps down the sequence. The
   .step class is retained so the existing hover-glow and scroll-reveal
   systems apply unchanged. Zero new tokens. */
.steps {
  display: grid;
  grid-template-columns: 1fr;
  gap: 0;
  margin-top: 24px;
  padding: 0;
  list-style: none;
}
.step {
  background: transparent;
  border: 1px solid transparent;
  border-top-color: var(--line);
  border-radius: var(--radius-card);
  padding: 28px 24px;
  display: grid;
  grid-template-columns: auto 1fr;
  column-gap: 24px;
  align-items: start;
  /* Card hover effect, Tier B (DESIGN_SPEC.md section 12.3): the transition
     declaration lives on the rest state (not just the :hover rule) so hover-in
     and hover-out share the same 180ms ease-out timing in both directions. The
     actual value changes only occur inside the guarded rules further down. */
  transition: transform 180ms ease-out, box-shadow 180ms ease-out, border-color 180ms ease-out;
}
.step:first-child {
  border-top-color: transparent;
}
.step h3,
.step p {
  grid-column: 2;
}
.step .step-number {
  grid-column: 1;
  grid-row: 1 / span 2;
}
@media (min-width: 900px) {
  .step:nth-child(2) { margin-left: 56px; }
  .step:nth-child(3) { margin-left: 112px; }
}
.step-number {
  /* Owner ruling 2026-07-29: Instrument Serif replaced with Josefin Sans
     sitewide. Weight 600 added; font-size unchanged - the clamp already
     renders these large (2.4-3.2rem), so the extra weight alone keeps the
     same visual size without needing a size bump. */
  font-family: "Josefin Sans", "Geist", system-ui, sans-serif;
  font-weight: 600;
  font-size: clamp(2.4rem, 4vw, 3.2rem);
  line-height: 1;
  color: var(--accent-dark);
  min-width: 44px;
  text-align: center;
  margin-bottom: 12px;
}
.step:nth-child(2) .step-number { color: var(--badge-sea-fg); }
.step:nth-child(3) .step-number { color: var(--badge-grass-fg); }
.step h3 {
  font-family: "Geist", system-ui, sans-serif;
  font-weight: 600;
  font-size: 1.15rem;
}
.step p {
  margin-bottom: 0;
}
.step-arrow {
  display: none;
}

/* Privacy promise list */
/* Hallmark redesign 2026-07-22 (privacy section): the strongest sentence on
   the site - "No ads, ever." - is elevated out of the six-equal-row list into
   an oversized serif statement; the remaining five promises tighten into a
   two-column grid at desktop. Copy verbatim; tokens reused. */
.promise-lead {
  margin-top: 28px;
}
.promise-lead-statement {
  font-family: "Josefin Sans", "Geist", system-ui, sans-serif;
  font-weight: 600;
  font-size: clamp(2.1rem, 4.5vw, 3.1rem);
  line-height: 1.1;
  color: var(--fg);
  margin: 0 0 10px 0;
}
.promise-lead-body {
  max-width: 640px;
  margin: 0;
}
.promise-list {
  list-style: none;
  padding: 0;
  margin: 28px 0 0 0;
  display: grid;
  gap: 12px;
}
@media (min-width: 900px) {
  .promise-list {
    grid-template-columns: 1fr 1fr;
    gap: 14px;
  }
}
.promise-list li {
  background: var(--surface);
  border: 1px solid var(--line);
  border-radius: var(--radius-card);
  padding: 16px 20px;
  display: flex;
  gap: 14px;
  /* Top-aligned (was: baseline) - the circular icon needs top alignment,
     not text baseline (docs/051-external-redesign-merge.md section 3.3). */
  align-items: flex-start;
  /* Card hover effect, Tier B - see the matching comment on .step above. */
  transition: transform 180ms ease-out, box-shadow 180ms ease-out, border-color 180ms ease-out;
}
/* Privacy-promise row icons (051 section 3.3): a circled, colored icon per
   row replacing the plain checkmark, rotating blue/sea/grass down the 6 rows
   via the markup's own promise-icon--{color} class. 40px (not the fork's
   44px) - a decorative icon badge, not itself a touch target, since the row
   has no click action. */
.promise-icon {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  width: 40px;
  height: 40px;
  border-radius: 50%;
  flex-shrink: 0;
}
.promise-icon svg { width: 20px; height: 20px; }
.promise-icon--blue  { background: var(--badge-blue-bg); }
.promise-icon--blue svg  { stroke: var(--badge-blue-fg); }
.promise-icon--sea   { background: var(--badge-sea-bg); }
.promise-icon--sea svg   { stroke: var(--badge-sea-fg); }
.promise-icon--grass { background: var(--badge-grass-bg); }
.promise-icon--grass svg { stroke: var(--badge-grass-fg); }

/* Condition and caregiver-role landing pages (website/docs/062-condition-
   landing-pages-spec.md) - shared components used by for-epilepsy.html,
   for-medically-complex-care.html, for-caregivers.html, and the homepage's
   own #situations row. */
.recognize-list {
  list-style: none;
  margin: 20px 0 0;
  padding: 0;
  display: flex;
  flex-direction: column;
  gap: 10px;
  max-width: 480px;
}
.recognize-list li {
  display: flex;
  align-items: flex-start;
  gap: 12px;
}
.recognize-icon {
  flex-shrink: 0;
  width: 24px;
  height: 24px;
  border-radius: 50%;
  background: var(--accent-dark);
  display: flex;
  align-items: center;
  justify-content: center;
}
.recognize-icon svg {
  width: 14px;
  height: 14px;
  stroke: #ffffff;
  stroke-width: 2.5;
  fill: none;
  stroke-linecap: round;
  stroke-linejoin: round;
}
.recognize-text {
  font-family: "Geist", system-ui, sans-serif;
  font-size: 1.0625rem;
  line-height: 1.4;
  font-weight: 600;
  color: var(--fg);
}

.disclaimer-callout {
  background: var(--badge-gold-bg);
  border: 1px solid var(--line);
  border-radius: 12px;
  padding: 20px 24px;
  max-width: 640px;
}
.disclaimer-callout p {
  color: var(--fg);
  font-size: 1.0625rem;
  line-height: 1.6;
  margin: 0;
}
.disclaimer-callout p + p { margin-top: 12px; }

.situation-links {
  display: grid;
  grid-template-columns: minmax(0, 1fr);
  gap: 16px;
  margin-top: 20px;
}
@media (min-width: 700px) {
  .situation-links { grid-template-columns: repeat(3, minmax(0, 1fr)); }
}
.situation-link {
  display: flex;
  flex-direction: column;
  gap: 6px;
  min-height: 96px;
  padding: 20px;
  border: 1px solid var(--line);
  border-radius: 12px;
  background: var(--surface);
  text-decoration: none;
}
.situation-link-title {
  font-weight: 700;
  font-size: 1.0625rem;
  color: var(--fg);
}
.situation-link-sub {
  font-size: 0.9375rem;
  line-height: 1.4;
  color: var(--fg2);
}
@media (hover: hover) and (prefers-reduced-motion: no-preference) {
  .situation-link:hover {
    transform: translateY(-3px);
    box-shadow: 0 8px 20px rgba(22, 54, 67, 0.16);
    border-color: var(--accent-dark);
  }
}
@media (hover: hover) and (prefers-reduced-motion: reduce) {
  .situation-link:hover { border-color: var(--accent-dark); }
}
.situation-link:focus-visible {
  border-color: var(--accent-dark);
  outline: 3px solid var(--accent);
  outline-offset: 2px;
}
@media (prefers-reduced-motion: no-preference) {
  .situation-link:focus-visible {
    transform: translateY(-3px);
    box-shadow: 0 8px 20px rgba(22, 54, 67, 0.16);
  }
}

/* Sample doctor-report section (brief 007 / website/docs/064-sample-doctor-
   report-spec.md sections 3.2-3.3). Two-column grid reversed from the hero
   (visual left / copy right at desktop) via grid-column/grid-row placement
   only - DOM order stays copy-first for screen-reader/keyboard reading order. */
.sample-report-grid {
  display: grid;
  grid-template-columns: 1fr;
  gap: 32px;
  align-items: start;
  margin-top: 24px;
}
@media (min-width: 900px) {
  .sample-report-grid {
    grid-template-columns: minmax(0, 0.9fr) minmax(0, 1.1fr);
    gap: 48px;
  }
  .sample-report-visual { grid-column: 1; grid-row: 1; }
  .sample-report-copy   { grid-column: 2; grid-row: 1; }
}
/* .sample-report-visual is a rendering of a physical printed artifact (the same
   category as .phone-glow), not a UI content card - deliberately excluded from
   the sitewide "no drop shadows on cards" rule and from the @media (hover:
   hover) card-hover-glow selector list above (spec 064 section 3.3). Do not
   add it to either. */
.sample-report-visual {
  margin: 0;
}
.sample-report-visual img {
  display: block;
  width: 100%;
  max-width: 420px;
  height: auto;
  border-radius: var(--radius-card);   /* 16px */
  border: 1px solid var(--line);
  box-shadow: 0 20px 40px rgba(22, 54, 67, 0.12);
}
.sample-report-badge {
  margin-top: 12px;
  display: inline-flex;
  align-items: center;
  gap: 6px;
  padding: 4px 10px;
  border-radius: var(--radius-badge);   /* 8px */
  background: var(--badge-gold-bg);
  color: var(--badge-gold-fg);
  font-size: 0.85rem;
  font-weight: 600;
}
/* .sample-report-note / .sample-report-caveat: no override needed - p, li
   {color: var(--fg2)} above already applies (spec 064 section 10 flags
   --muted-on-meadow-wash as a borderline ~4.21:1 pairing already in use by
   .testimonial-note; this new section's paragraphs simply never opt into
   --muted, rather than "fixing" that pre-existing component). */

/* FAQ */
.faq-item {
  background: var(--surface);
  border: 1px solid var(--line);
  border-radius: var(--radius-card);
  margin-top: 12px;
  /* Card hover effect, Tier A (DESIGN_SPEC.md section 12.3) - see the matching
     comment on .step above; same rest-state transition-declaration pattern. */
  transition: transform 180ms ease-out, box-shadow 180ms ease-out, border-color 180ms ease-out;
}
.faq-item summary {
  font-weight: 600;
  color: var(--fg);
  font-size: 1.05rem;
  padding: 18px 20px;
  cursor: pointer;
  /* display: flex below already overrides summary's native display: list-item,
     so list-style has no effect once flex is set - the ::-webkit-details-marker
     rule directly below is the one piece still needed for WebKit. */
  display: flex;
  align-items: center;
  /* Designer round-2 (owner ruling 3, 2026-07-22): left orientation - the old
     space-between floated the question mid-row; now the badge and question hug
     the left edge and only the chevron sits right (via .faq-question-text's
     flex: 1). */
  justify-content: flex-start;
  gap: 14px;
  text-align: left;
}
.faq-question-text {
  flex: 1;
  text-align: left;
}
/* Designer round-2: open questions tint their header row with the same badge
   color family the Q circle uses (accent / sea / grass rotation). */
.faq-item[open] summary {
  background: var(--accent-soft);
  border-bottom: 1px solid var(--line);
}
.faq-item:nth-of-type(3n+2)[open] summary { background: var(--badge-sea-bg); }
.faq-item:nth-of-type(3n)[open] summary { background: var(--badge-grass-bg); }
.faq-item summary::-webkit-details-marker {
  display: none;
}
/* FAQ circled-Q badge (docs/051-external-redesign-merge.md section 3.6): the
   fork's visual, our own existing chevron mechanism (unchanged, see the
   summary::after rules below). aria-hidden - the <summary>'s accessible name
   stays exactly the question text. Cycles blue/sea/grass down the 6 questions
   via :nth-of-type, matching the section-label color-rotation pattern already
   used elsewhere in this file. */
.faq-badge {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  width: 32px;
  height: 32px;
  border-radius: 50%;
  flex-shrink: 0;
  /* Owner ruling 2026-07-29: Instrument Serif replaced with Josefin Sans
     sitewide. This is the smallest/most legibility-sensitive of the
     replaced elements (a single glyph in a 32px circle), so - unlike the
     other elements in this pass - the size is bumped slightly (1.05rem ->
     1.15rem) alongside the weight bump, to keep the "Q" reading clearly at
     Josefin's smaller x-height. */
  font-family: "Josefin Sans", "Geist", system-ui, sans-serif;
  font-weight: 600;
  font-size: 1.15rem;
  background: var(--badge-blue-bg);
  color: var(--badge-blue-fg);
}
.faq-item:nth-of-type(3n+2) .faq-badge { background: var(--badge-sea-bg); color: var(--badge-sea-fg); }
.faq-item:nth-of-type(3n) .faq-badge   { background: var(--badge-grass-bg); color: var(--badge-grass-fg); }
/* FAQ disclosure chevron (DESIGN_SPEC.md section 14.4) - CSS-drawn, not an inline
   SVG or a Unicode glyph. content: "" is load-bearing: some browser/AT
   combinations expose non-empty generated content to the accessibility tree, so
   the shape is drawn entirely via border/width/height/transform with no text
   value anywhere. Color is currentColor, not a hardcoded hex, so the chevron
   inherits the exact color the summary text itself is using at every state
   (--fg at rest, --accent-dark on hover/focus via the (hover: hover)-guarded
   rule above and :focus-within from section 12) - zero new contrast pairing.
   Rotation itself is NOT gated by (hover: hover) or reduced-motion - it must be
   visible on every device, since it is the actual open/closed state indicator,
   not a decorative hover flourish. Only the *transition* is gated behind
   prefers-reduced-motion: no-preference; under reduce, the transform value still
   changes correctly between states, it just snaps instead of animating. */
.faq-item summary::after {
  content: "";
  flex-shrink: 0;
  width: 8px;
  height: 8px;
  border-right: 2px solid currentColor;
  border-bottom: 2px solid currentColor;
  transform: rotate(45deg);
}
.faq-item[open] summary::after {
  transform: rotate(225deg);
}
@media (prefers-reduced-motion: no-preference) {
  .faq-item summary::after {
    transition: transform 180ms ease-out;
  }
}
.faq-item .faq-answer {
  padding: 0 20px 18px 20px;
}
.faq-item .faq-answer p:last-child {
  margin-bottom: 0;
}

/* Contact section (docs/051-external-redesign-merge.md section 3.7): two-column
   layout, icon rows + a quick-actions card. Same existing copy/mailto actions
   as before - no fake contact form (static site, no backend). */
.contact-grid {
  display: grid;
  grid-template-columns: 1fr;
  gap: 24px;
  margin-top: 20px;
  align-items: stretch;
}
@media (min-width: 780px) {
  .contact-grid { grid-template-columns: 1.15fr 0.85fr; gap: 28px; }
}
.contact-rows {
  list-style: none;
  padding: 0;
  margin: 0;
  display: grid;
  gap: 14px;
}
.contact-row {
  background: var(--surface);
  border: 1px solid var(--line);
  border-radius: var(--radius-card);
  padding: 16px 18px;
  display: flex;
  align-items: center;
  gap: 14px;
  /* Card hover effect, Tier B - see the matching comment on .step above. */
  transition: transform 180ms ease-out, box-shadow 180ms ease-out, border-color 180ms ease-out;
}
.contact-row-icon {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  width: 40px;
  height: 40px;
  border-radius: 50%;
  background: var(--badge-blue-bg);
  flex-shrink: 0;
}
.contact-row-icon svg { width: 18px; height: 18px; stroke: var(--badge-blue-fg); }
.contact-row:nth-child(2) .contact-row-icon { background: var(--badge-sea-bg); }
.contact-row:nth-child(2) .contact-row-icon svg { stroke: var(--badge-sea-fg); }
/* Hallmark gates 50/51 (2026-07-23): min-width 0 lets the flex column shrink
   below its content width at 320px, and overflow-wrap lets the long email
   addresses break inside the word instead of pushing the page sideways. */
.contact-row-text { display: flex; flex-direction: column; gap: 2px; min-width: 0; overflow-wrap: anywhere; }
.contact-row-label { font-weight: 600; color: var(--fg); }
.contact-card {
  background: var(--surface);
  border: 1px solid var(--line);
  border-radius: var(--radius-hero);
  padding: 24px 20px;
  display: flex;
  flex-direction: column;
  justify-content: center;
  gap: 12px;
}

/* Testimonials carousel (docs/061-google-button-branding-and-carousel-drag.md
   Part 2): a real, bounded, native-scrollable (scrollLeft-driven) track - NOT
   a transform-animated marquee. A transform position has nothing to do with
   scrollLeft, so dragging the old marquee would have fought its own CSS
   animation on the same element; native touch panning and pointer-drag are
   both fundamentally scrollLeft-based, so the track has to be genuinely
   scrollable for either to work at all (see 2.3). Layout is IDENTICAL in both
   prefers-reduced-motion states - only the JS autoplay is motion-gated
   (js/testimonials.js). */
.testimonial-carousel { margin-top: 20px; }
.testimonial-carousel {
  position: relative;
  overflow: hidden;
  padding: 8px 4px 28px;
  /* Edge-fade mask: ungated (not motion-gated) now that the track overflows/
     scrolls in both motion states. Decorative "there is more here" cue. */
  mask-image: linear-gradient(90deg, transparent 0, #000 48px, #000 calc(100% - 48px), transparent 100%);
  -webkit-mask-image: linear-gradient(90deg, transparent 0, #000 48px, #000 calc(100% - 48px), transparent 100%);
}
.testimonial-track {
  display: flex;
  flex-wrap: nowrap;
  gap: 16px;                            /* owner ruling 2026-07-23: the visual
                                            gap read as ~90-110px in the
                                            deployed build - the real cause was
                                            not this gap value (was already 8px
                                            per doc 061) but .testimonial-card
                                            being a bare <blockquote> with no
                                            margin reset, so the UA stylesheet's
                                            default margin-inline: 40px on each
                                            side stacked with this gap (8 + 40 +
                                            40 = 88px). Fixed at the card rule
                                            below (margin: 0); this value is now
                                            the ENTIRE visible gap, tuned to the
                                            owner's explicit "small, 16px" ask. */
  overflow-x: auto;
  overflow-y: hidden;
  -webkit-overflow-scrolling: touch;   /* older iOS Safari momentum */
  scroll-behavior: auto;               /* never "smooth" - autoplay/drag both
                                           write scrollLeft directly, per-frame */
  touch-action: pan-x;                 /* native horizontal touch panning here;
                                           vertical touch gestures still pass
                                           through to page scroll */
  scroll-snap-type: x proximity;       /* not mandatory - never fights an
                                           in-progress drag/autoplay gesture */
  scroll-padding-inline: 24px;         /* matches .container's own side
                                           padding, so the first/last card can
                                           snap flush with the same gutter */
  padding-inline: 24px;
}
.testimonial-track::-webkit-scrollbar { display: none; }
/* Mouse/pen drag affordance only - touch gets no cursor regardless, and
   pointer: fine additionally excludes hover-capable-but-coarse pointers. */
@media (hover: hover) and (pointer: fine) {
  .testimonial-track { cursor: grab; }
  .testimonial-track.is-dragging { cursor: grabbing; }
}
.testimonial-track.is-dragging {
  user-select: none;   /* only while actively dragging past the 6px threshold */
}
.testimonial-card {
  flex: 0 0 auto;
  width: 260px;
  margin: 0;                            /* owner ruling 2026-07-23: root-cause
                                            fix for the oversized card gap -
                                            this is a <blockquote> element, and
                                            the browser's UA stylesheet gives
                                            it a default margin-inline: 40px on
                                            each side (plus a block margin) with
                                            no explicit reset anywhere in this
                                            file. That default margin, not the
                                            track's own gap, was the real
                                            source of the ~90-110px visual gap
                                            reported live. */
  background: var(--surface);
  border: 1px solid var(--line);
  border-radius: var(--radius-card);
  padding: 24px;
  display: flex;
  flex-direction: column;
  gap: 14px;
  scroll-snap-align: start;
  /* Card hover effect, Tier B - see the matching comment on .step above. */
  transition: transform 180ms ease-out, box-shadow 180ms ease-out, border-color 180ms ease-out;
}
/* DESIGN_SPEC.md section 16.5: 320px -> 272px, so ~3 cards fit inside the
   836px content column at true desktop widths (>=924px): 3 x 272px + 2 x 8px
   gap = 832px, 4px to spare. (Pre-existing stale note, unaffected by this
   pass: the 272px width narrowing never actually landed - width stayed
   320px, per the 2026-07-21 decision log's own finding - not touched here,
   out of scope for the owner's 2026-07-23 gap-only ruling. Gap value above
   this rule is now 16px, not the 8px this old note still describes.) */
@media (min-width: 700px) { .testimonial-card { width: 320px; } }
/* In-carousel hover: shadow reduced from the sitewide 14px/32px glow so the
   clipping carousel's padding can fully contain it (designer's own math). */
@media (hover: hover) {
  .testimonial-carousel .testimonial-card:hover {
    box-shadow: 0 10px 24px var(--glow-accent);
  }
}
.testimonial-quote-mark {
  /* Owner ruling 2026-07-29: Instrument Serif replaced with Josefin Sans
     sitewide. Weight 600 added; font-size unchanged - at 2.6rem this
     decorative quote glyph is already large, so the extra weight alone
     keeps the same visual size/presence. */
  font-family: "Josefin Sans", "Geist", system-ui, sans-serif;
  font-weight: 600;
  font-size: 2.6rem;
  line-height: 0.6;
  color: var(--accent);
}
/* DESIGN_SPEC.md section 16.5: rotation extended from 2 overrides to a full
   3-color cycle across all 10 cards, matching the FAQ Q-badge's own
   nth-of-type(3n+2)/nth-of-type(3n) pattern (doc 051 section 3.6). */
.testimonial-card:nth-of-type(3n+2) .testimonial-quote-mark { color: var(--badge-sea-fg); }
.testimonial-card:nth-of-type(3n) .testimonial-quote-mark { color: var(--badge-grass-fg); }
.testimonial-card p { color: var(--fg); font-size: 1.05rem; margin: 0; }
.testimonial-card footer { display: flex; flex-direction: column; margin-top: auto; }
.testimonial-name { font-weight: 600; color: var(--fg); }
.testimonial-role { color: var(--muted); font-size: 0.9rem; }

/* Honesty label over the illustrative story cards (owner ruling 2026-07-23):
   quiet but readable - it must not undercut the section's warmth, and it must
   not be hidden. Removed when real family quotes replace the samples (#317). */
.testimonial-note {
  color: var(--muted);
  font-size: 0.95rem;
  max-width: 56ch;
  margin-top: -6px;
  margin-bottom: 22px;
}

/* "Why Mira exists" founder letter (#why). Owner ruling 2026-07-28, round 2:
   this section now gets the same content-raft card treatment as the compare
   page's hero (surface/border/18px radius/generous padding - see the shared
   .prose-card rule above) and reads WIDER than its old plain-text measure -
   .founder-note-inner's own 740px cap is retired in favor of .prose-card's
   900/980px width (the card IS the widening; no extra rule needed here).
   .founder-note p's 62ch cap is kept deliberately: a comfortable ~62-character
   reading line is still worth holding even inside a wider card (the card adds
   breathing-room padding around the text, not a wider text column) - matches
   this file's own line-length reasoning elsewhere (DESIGN_SPEC.md 4.5's
   ~80-character guideline; 62ch stays comfortably under it). The signature
   keeps its own short accent-rule treatment (not a nested card) so it reads
   as a letter's sign-off inside the card, not another boxed component. */
.founder-note p { max-width: 62ch; overflow-wrap: anywhere; }
.founder-sig {
  margin-top: 34px;
  padding-top: 20px;
  border-top: 3px solid var(--accent-soft);
  max-width: 260px;
  display: flex;
  flex-direction: column;
  gap: 2px;
}
.founder-sig-name {
  /* Owner ruling 2026-07-29: Instrument Serif replaced with Josefin Sans
     sitewide. Weight 600 added; font-size unchanged - 1.7rem stays legible
     as a signature-style name once weighted, same reasoning as the other
     elements in this pass. */
  font-family: "Josefin Sans", "Geist", system-ui, sans-serif;
  font-weight: 600;
  font-size: 1.7rem;
  line-height: 1.1;
  color: var(--fg);
}
.founder-sig-role { color: var(--muted); font-size: 0.95rem; }

/* Theme toggle (docs/051-external-redesign-merge.md section 6.3) - a 44px
   round icon-button shape. Joins .header-nav as its last flex child on all
   7 pages. (The testimonials carousel's own former round pause button used
   this same shape; that button was removed per
   docs/061-google-button-branding-and-carousel-drag.md Part 2.) */
.theme-toggle {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  width: 44px;
  height: 44px;
  border-radius: 50%;
  border: 1px solid var(--line);
  background: var(--surface);
  cursor: pointer;
  padding: 0;
  flex-shrink: 0;
  /* Owner 2026-07-22: breathing room - the toggle was squeezed against the
     Sign in CTA. */
  margin-left: 14px;
}
/* Mobile header (docs/060-mobile-nav-and-firefly-motion.md Part A) - supersedes
   the 2026-07-23 two-row wrap pattern (commit c6bf11e/7b8f253) with a
   one-line row (wordmark - toggle - Sign in - hamburger) plus a dropdown
   panel for the plain nav links; see A.2-A.4 for the breakpoint/compression
   math this reproduces. */
@media (max-width: 760px) {
  .site-header .container {
    flex-wrap: nowrap;
  }
  /* Direct-child combinator (>) is load-bearing here, not a style choice:
     the mobile panel's own links (.nav-menu-panel a) are also descendants of
     .header-nav (A.5's markup nests <details class="nav-menu"> as the last
     child inside .header-nav) but sit two levels deeper, so a bare
     descendant selector would hide them too and make every panel link
     unreachable/unfocusable - caught live via CDP during verification. */
  .header-nav > a:not(.nav-cta) {
    display: none; /* plain links hide; they live in the panel now (A.5) */
  }
  .header-nav a.nav-cta {
    order: 2;
    padding: 0 12px;
  }
  .theme-toggle {
    order: 1;
    position: static;
    margin-left: 0;
  }
  .nav-menu {
    order: 3;
  }
  /* Gap-fill (A.4's own worked budget assumes this "standard tier" logo size
     but A.7's code sample never states the rule) - standard tier 32px height
     (~105px width at the locked 354.81:108.56 aspect), matching section
     11.4's icon+text convention height. Without this the unscoped base
     144x44 lockup overflows A.4's own fixed-content budget by ~10px at
     375px. */
  .wordmark img,
  .logo-mark--full {
    height: 32px;
    width: auto;
  }
}
@media (max-width: 359px) {
  .header-nav {
    gap: 4px;
  }
  .header-nav a.nav-cta {
    padding: 0 10px;
  }
  .wordmark img,
  .logo-mark--full {
    height: 24px;
    width: auto;
  }
}

/* Mobile hamburger menu (docs/060-mobile-nav-and-firefly-motion.md A.5-A.10):
   a native <details>/<summary> dropdown panel anchored under the header,
   last child inside the existing .header-nav landmark. Fully absent on
   desktop (not just visually hidden), so there is never a second "Site"
   landmark or duplicated links exposed to the accessibility tree. */
.nav-menu {
  display: none;
}
@media (max-width: 760px) {
  .nav-menu {
    display: block;
  }
}
/* The 44px round button - reuses .theme-toggle's own shape verbatim so the
   two buttons read as one family. */
.nav-menu > summary {
  list-style: none;
  display: inline-flex;
  align-items: center;
  justify-content: center;
  width: 44px;
  height: 44px;
  border-radius: 50%;
  border: 1px solid var(--line);
  background: var(--surface);
  cursor: pointer;
}
.nav-menu > summary::-webkit-details-marker {
  display: none;
}
.nav-menu > summary::marker {
  content: "";
}
.nav-menu > summary svg {
  width: 22px;
  height: 22px;
  stroke: var(--fg2);
  fill: none;
}
.nav-menu > summary:hover,
.nav-menu > summary:focus-visible {
  background: var(--accent-soft);
  border-color: var(--accent);
}
/* Open/close icon behavior: swap, not morph (A.6) - reuses the exact
   .theme-toggle .icon-sun/.icon-moon mechanism (display driven by state
   selector). Satisfies "reduced-motion = instant swap" for free: a display
   swap was never a continuous animation, so nothing needs gating. */
.nav-menu > summary .icon-menu-close {
  display: none;
}
.nav-menu[open] > summary .icon-menu-lines {
  display: none;
}
.nav-menu[open] > summary .icon-menu-close {
  display: block;
}
/* The disclosure content - edge-to-edge header chrome, not an inset content
   raft (A.7): continues the header's own straight-edged white plate down one
   level rather than nesting a rounded card directly under a straight header
   edge. */
.nav-menu-panel {
  position: absolute;
  top: 100%;
  left: 0;
  right: 0;
  z-index: 90; /* Updated 2026-07-29: .site-header is now position:sticky
                   with its own z-index (40), so it establishes a stacking
                   context - this value now only has to out-rank its OWN
                   siblings inside .site-header (trivial), not compare
                   against page content directly any more. The whole header,
                   panel included, rides above page content as a unit via
                   the header's own z-index. Left at 90 (well clear of any
                   plausible sibling) rather than lowered, since nothing
                   forces a change and a smaller, more "normal" value has no
                   benefit here. */
  background: var(--surface);
  border-top: 1px solid var(--line);
  border-bottom: 1px solid var(--line);
  box-shadow: 0 8px 20px rgba(22, 54, 67, 0.16); /* reuses .faq-item's Tier-A hover bloom (section 12.3) */
  padding: 8px 24px 16px;
  display: flex;
  flex-direction: column;
}
.nav-menu-panel a {
  display: flex;
  align-items: center;
  min-height: 48px; /* >44px floor, generous for a stacked link list */
  font-size: 1.05rem;
  font-weight: 500;
  color: var(--fg2);
  text-decoration: none;
  border-bottom: 1px solid var(--line);
}
.nav-menu-panel a:last-child {
  border-bottom: none;
}
.nav-menu-panel a:hover,
.nav-menu-panel a:focus-visible {
  color: var(--fg);
  background: var(--accent-soft);
}
/* Motion (A.9): panel entrance only - closing native <details> cannot be
   transitioned without extra machinery, matching the site's own existing
   FAQ precedent (chevron animates, box collapse does not). */
@keyframes nav-panel-in {
  from { opacity: 0; translate: 0 -6px; }
  to { opacity: 1; translate: 0 0; }
}
@keyframes nav-panel-in-reduced {
  from { opacity: 0; }
  to { opacity: 1; }
}
@media (prefers-reduced-motion: no-preference) {
  .nav-menu[open] > .nav-menu-panel {
    animation: nav-panel-in 200ms ease-out;
  }
  .nav-menu > summary:active {
    transform: scale(0.97);
  }
}
@media (prefers-reduced-motion: reduce) {
  .nav-menu[open] > .nav-menu-panel {
    animation: nav-panel-in-reduced 120ms ease-out;
  }
}

.theme-toggle:hover, .theme-toggle:focus {
  background: var(--accent-soft);
  border-color: var(--accent);
}
.theme-toggle svg { width: 21px; height: 21px; }
.theme-toggle svg[hidden] { display: none; }
/* Owner 2026-07-23: icon visibility is driven by CSS from the ACTIVE theme
   (state convention - day wears the sun, night wears the moon), using the
   same dual activation paths as the palette itself. This works even if
   theme.js never runs (blocked/stale script), so the icon can never
   disagree with the sky again. These rules outrank the [hidden] attribute
   on purpose. */
.theme-toggle .icon-sun { display: block; }
.theme-toggle .icon-moon { display: none; }
:root[data-theme="dark"] .theme-toggle .icon-sun { display: none; }
:root[data-theme="dark"] .theme-toggle .icon-moon { display: block; }
@media (prefers-color-scheme: dark) {
  :root:not([data-theme="light"]) .theme-toggle .icon-sun { display: none; }
  :root:not([data-theme="light"]) .theme-toggle .icon-moon { display: block; }
}
/* Owner 2026-07-22: expressive theme icons - a vivid gold sun for switching
   to day, a soft pale crescent for switching to night. Colored via CSS (not
   svg attributes) because presentation attributes cannot carry var() and the
   CSP forbids inline style attributes. */
.theme-toggle .icon-sun { stroke: var(--illus-gold); fill: none; }
.theme-toggle .icon-sun circle { fill: var(--illus-gold); stroke: none; }
.theme-toggle .icon-moon path { fill: var(--illus-sparkle); stroke: var(--fg2); stroke-width: 0.6; }

/* Card hover effect (DESIGN_SPEC.md section 12) - mouse-only, dual-tier response.
   Tier A (.faq-item, collapsed state only): the fuller cue - lift, shadow bloom,
   accent-colored border - because it's a real <details>/<summary> control that
   does something on click. Tier B (.step, .promise-list li): a softer, neutral
   "alive" response with no accent color and no pointer cursor, because these are
   informational cards with nothing to activate; giving them the same cue as a
   real control would falsely imply they're clickable.
   Guard structure (section 12.4):
   - Bundled fix: the pre-existing .faq-item summary:hover text-color rule had no
     hover-capability guard (could stick after a mobile tap); it now lives inside
     the same (hover: hover) guard as the rest of this section.
   - :hover rules for both tiers require (hover: hover), so touch devices receive
     none of this - not a weaker version, none of it.
   - :focus-within on .faq-item is NOT gated by (hover: hover) - a keyboard user
     on a touch-primary device still needs the visible focus cue. .step and
     .promise-list li have no focusable children, so no :focus-within is needed.
   - Both hover and focus-within are further split by prefers-reduced-motion:
     the "no-preference" branch gets transform + box-shadow + border-color; the
     "reduce" branch gets the border-color swap only (the sanctioned non-moving
     fallback), never transform/box-shadow. */
@media (hover: hover) {
  .faq-item summary:hover {
    color: var(--accent-dark);
  }
}

/* Designer round-2 (owner ruling 2, 2026-07-22): the dual-tier lift replaced by
   the designer's unified "blue glow" elevation - border dissolves, a soft
   accent-tinted shadow blooms under the card; the sharing steps glow green and
   the privacy rows glow teal, matching their section identities. Shadow-only
   (no transform), so the reduced-motion split is no longer needed: a static
   shadow is not motion. The (hover: hover) guard stays - touch devices get no
   sticky hover; :focus-within keeps keyboard parity ungated. Supersedes the
   section-12 tier values (structure retained, values replaced). */
@media (hover: hover) {
  .card:hover,
  .step:hover,
  .promise-list li:hover,
  .faq-item:hover,
  .testimonial-card:hover,
  .contact-row:hover,
  .contact-card:hover {
    border-color: transparent;
    box-shadow: 0 14px 32px var(--glow-accent);
  }
  #sharing .step:hover {
    box-shadow: 0 14px 32px var(--glow-green);
  }
  #privacy .promise-list li:hover {
    box-shadow: 0 14px 32px var(--glow-teal);
  }
}

.faq-item:not([open]):focus-within {
  border-color: transparent;
  box-shadow: 0 14px 32px var(--glow-accent);
}

/* Scroll reveal effect ("soft reveal", DESIGN_SPEC.md section 13) - homepage only.
   Base state: every revealing element already renders at its normal opacity and
   position via ordinary CSS above (this file adds no unguarded opacity/translate
   rule anywhere) - the "hidden" from-state below only ever exists inside both
   guards. Guard order is load-bearing (section 13.4): @supports wraps everything
   so unsupported browsers see zero altered CSS, and it sits OUTSIDE the
   reduced-motion query so a reduced-motion visitor on a supporting browser still
   gets nothing, rather than falling outside both guards and getting the
   animation by default. No separate prefers-reduced-motion: reduce branch is
   needed - under reduced motion, "no reveal" IS the ordinary unguarded base
   state, so its absence here is correct, not a gap.
   Property choice: this animates the standalone `translate` longhand, never
   `transform` - section 12 above already owns `transform` for the hover lift on
   these same three card selectors (.step, .promise-list li, .faq-item). Because
   translate and transform are different CSS properties, both apply
   simultaneously with no cascade conflict (see section 13.6): a settled,
   revealed card (translate: 0 0) that is then hovered simply gets section 12's
   transform: translateY(...) added on top, unchanged. */
@supports (animation-timeline: view()) {
  @media (prefers-reduced-motion: no-preference) {

    body.page-home .section:not(#contact) > .container > .section-label,
    body.page-home .section:not(#contact) > .container > h2,
    body.page-landing .section > .container > .section-label,
    body.page-landing .section > .container > h2,
    body.page-home .step,
    body.page-landing .step,
    body.page-home .promise-list li,
    body.page-landing .promise-list li,
    body.page-home .faq-item,
    body.page-home .situation-link,
    body.page-landing .situation-link {
      animation-name: soft-reveal;
      animation-duration: 1s;
      animation-timing-function: ease-out;
      animation-fill-mode: both;
      animation-timeline: view();
      animation-range: entry 0% entry 40%;
    }

    /* Stagger - .step only (section 13.5): a vertically stacked list already
       staggers for free because each element's animation-timeline: view()
       progress is tied to its own scroll position, but .step becomes a 3-column
       grid at >=700px, where all three cards would otherwise enter the viewport
       at the same scroll position and reveal simultaneously.
       v1.7: per-card animation-range overrides, NOT animation-delay - the
       original animation-delay: 8%/16% approach proved unsupported in current
       Chrome (CSS.supports('animation-delay', '8%') returns false even though
       animation-timeline: view() and percentage animation-range both work), so
       all three cards revealed in unison instead of rippling. Shifting both ends
       of the shared 40-point animation-range window later by the same amount
       produces the identical visual stagger from a property already confirmed
       supported. See DESIGN_SPEC.md 13.4/13.5.
       body.page-landing gets the same stagger (website/docs/062-condition-
       landing-pages-spec.md section 12.3) - only for-caregivers.html uses
       .step, but the rule is harmless on pages that do not. The
       body.page-home .situation-link pairing above closes a gap in spec 062's
       own literal CSS: its section 14 prose promises the homepage's
       #situations row joins this reveal automatically, but section 12.3's
       literal block only paired body.page-landing .situation-link - fixed
       here per that section's own explicit instruction. */
    body.page-home .step:nth-child(2) { animation-range: entry 8% entry 48%; }
    body.page-home .step:nth-child(3) { animation-range: entry 16% entry 56%; }
    body.page-landing .step:nth-child(2) { animation-range: entry 8% entry 48%; }
    body.page-landing .step:nth-child(3) { animation-range: entry 16% entry 56%; }

  }
}

/* v1.7 (13.1.3/13.2): the Contact section header pair (#contact .section-label,
   #contact h2) is excluded from the reveal above via :not(#contact) on the two
   header selectors. Opening an earlier FAQ item reflows the page and pushes the
   Contact header - the very next thing after the FAQ section - toward or past
   the bottom viewport edge; its view() timeline then recomputes and the header
   can re-hide and replay its fade-and-rise a second time. Excluding these two
   elements is cheaper and safer than narrowing animation-range sitewide to chase
   one element's edge case. See DESIGN_SPEC.md 13.7. */

/* Store button (docs/051-external-redesign-merge.md section 3.5), replacing
   the previous .play-placeholder dashed box. Single "coming soon" Google Play
   chip only - no App Store button (Mira is Android-only) and no "live/Get it
   now" styling (the app has not launched). No hover response ships until a
   real href/<a> element replaces this placeholder - see 051 section 3.5. */
/* Owner 2026-07-22: the get-app screenshot figure was removed as "extra" -
   the section stays a centered closing band with the wider #play padding. */
.store-buttons {
  display: flex;
  flex-wrap: wrap;
  gap: 14px;
  justify-content: center;
  margin-top: 20px;
}
/* Owner 2026-07-22 (mobile screenshot): stacked store buttons rendered at
   different content-driven widths. On narrow screens both take one uniform
   width; the "Coming soon" chips right-align to the same edge. */
@media (max-width: 699px) {
  .store-buttons {
    flex-direction: column;
    align-items: center;
  }
  .store-button {
    width: 100%;
    max-width: 320px;
  }
}
/* Designer round-2 (owner ruling 4, 2026-07-22): BOTH store silhouettes. The
   Play button wears our accent (not the fork's replacement blue); the App
   Store button keeps the dark ink chip. HONESTY NET: both carry the "Coming
   soon" chip until real store listings exist - the fork's "Get it now" live
   chip on Play is deliberately NOT ported yet (flip it at launch). */
.store-button--primary {
  background: var(--accent);
}
.store-button--primary .store-button-eyebrow {
  color: rgba(255, 255, 255, 0.92);
}
.store-button {
  display: inline-flex;
  align-items: center;
  gap: 12px;
  /* Owner 2026-07-23: equal widths across the pair so the chips align. */
  min-width: 272px;
  /* Bug fix (DESIGN_SPEC.md section 16.4): this was var(--fg), which the
     comment claimed was "fixed" but is not - --fg flips to a pale cream in
     dark mode, producing white-on-near-white text (~1.1:1). --ink-fixed is
     the genuinely theme-invariant token. */
  background: var(--ink-fixed);
  color: #ffffff;
  border-radius: var(--radius-button);
  padding: 8px 18px;
  min-height: 56px;
}
.store-button-icon { width: 26px; height: 26px; flex-shrink: 0; }
.store-button-text { display: flex; flex-direction: column; text-align: left; }
.store-button-eyebrow {
  font-size: 0.68rem;
  text-transform: uppercase;
  letter-spacing: 0.06em;
  color: rgba(255, 255, 255, 0.75);
}
.store-button-name {
  font-size: 1.02rem;
  font-weight: 600;
  font-family: "Geist", system-ui, sans-serif;
}
.store-button-soon {
  /* Owner 2026-07-23: auto margin, not fixed - the chips right-align to the
     button edge so both "Coming soon" chips line up. Equal button min-widths
     below make them align across the pair on desktop too. */
  margin-left: auto;
  font-size: 0.72rem;
  font-weight: 600;
  /* Bug fix (DESIGN_SPEC.md section 16.4): --ink-fixed, not --fg - same root
     cause and fix as .store-button above. */
  color: var(--ink-fixed);
  background: var(--illus-gold);
  padding: 4px 9px;
  border-radius: var(--radius-badge);
  white-space: nowrap;
}

/* Footer */
.site-footer {
  background: var(--surface);
  border-top: 1px solid var(--line);
  padding: 36px 0;
  margin-top: 56px;
}
.footer-links {
  display: flex;
  flex-wrap: wrap;
  gap: 20px;
  padding: 0;
  margin: 0 0 16px 0;
  list-style: none;
}
.footer-links a {
  padding: 10px 0;
  display: inline-block;
  /* Owner ruling 2026-07-29: footer links inherit the sitewide "a { text-
     decoration: underline }" rule and read as underlined by default - drop
     it here, keep it back on hover/focus for the usual link affordance. */
  text-decoration: none;
}
.footer-links a:hover,
.footer-links a:focus {
  text-decoration: underline;
}
.footer-note {
  color: var(--muted);
  font-size: 0.95rem;
  margin: 0;
}

/* Legal pages */
/* v2: the "plain white reading column" content raft (DESIGN_SPEC 4.2/4.3) that
   floats over the header-band / meadow-wash / meadow-strip scene added below. */
.legal-page {
  background: var(--surface);
  border-radius: var(--radius-card);
  padding: 8px 24px 40px;
  margin-top: 32px;
  margin-bottom: 40px;
}
.legal-page h1 {
  margin-top: 40px;
}
.legal-page h2 {
  margin-top: 40px;
  font-size: 1.5rem;
}
.legal-page .updated {
  color: var(--muted);
}
.draft-banner {
  background: #fdf1ee;
  border: 1px solid var(--alert);
  border-radius: var(--radius-card);
  padding: 16px 20px;
  color: var(--alert-dark);
  font-weight: 500;
  margin: 24px 0;
}

/* Forms (account app) */
.form-field {
  margin-bottom: 18px;
}
.form-field label {
  display: block;
  font-weight: 600;
  color: var(--fg);
  margin-bottom: 6px;
}
.form-field input {
  width: 100%;
  font-family: inherit;
  font-size: 1.05rem;
  color: var(--fg);
  background: var(--surface);
  border: 1px solid var(--line);
  border-radius: var(--radius-button);
  padding: 12px 14px;
  min-height: 48px;
}
.form-field input:focus {
  border-color: var(--accent);
}
.form-hint {
  color: var(--muted);
  font-size: 0.95rem;
  margin-top: 6px;
}

.auth-tabs {
  display: flex;
  gap: 8px;
  margin-bottom: 24px;
  flex-wrap: wrap;
}
.auth-tab {
  flex: 1;
  min-width: 120px;
  font-family: inherit;
  font-size: 1rem;
  font-weight: 600;
  color: var(--fg2);
  background: var(--surface);
  border: 1px solid var(--line);
  border-radius: var(--radius-button);
  padding: 12px 10px;
  min-height: 48px;
  cursor: pointer;
}
.auth-tab[aria-selected="true"] {
  background: var(--accent-soft);
  color: var(--fg);
  border-color: var(--accent);
}

.divider {
  display: flex;
  align-items: center;
  gap: 12px;
  color: var(--muted);
  margin: 24px 0;
  font-size: 0.95rem;
}
.divider::before, .divider::after {
  content: "";
  flex: 1;
  border-top: 1px solid var(--line);
}

/* Status and error messages (aria-live regions) */
.status-message {
  border-radius: var(--radius-button);
  padding: 14px 16px;
  margin: 16px 0;
  font-weight: 500;
}
.status-message.error {
  background: #fdf1ee;
  border: 1px solid var(--alert);
  color: var(--alert-dark);
}
.status-message.success {
  background: #ecf6ee;
  border: 1px solid #3e8e57;
  color: #2c6b41;
}
.status-message.info {
  background: var(--accent-soft);
  border: 1px solid var(--accent);
  color: var(--fg);
}
.status-message:empty {
  display: none;
}

/* Signed-in account rows */
.account-row {
  display: flex;
  justify-content: space-between;
  align-items: center;
  gap: 12px;
  padding: 16px 0;
  border-bottom: 1px solid var(--line);
  flex-wrap: wrap;
}
.account-row:last-child {
  border-bottom: none;
}
.account-row .row-label {
  font-weight: 600;
  color: var(--fg);
}
.account-row .row-value {
  color: var(--fg2);
  overflow-wrap: anywhere;
}

.danger-zone {
  border: 1px solid var(--alert);
  border-radius: var(--radius-card);
  padding: 24px;
  margin-top: 32px;
  background: var(--surface);
}
.danger-zone h2 {
  color: var(--alert-dark);
  font-family: "Geist", system-ui, sans-serif;
  font-weight: 600;
  font-size: 1.2rem;
  margin-top: 0;
}

/* Modal dialog (type-to-confirm deletion) */
dialog {
  border: 1px solid var(--line);
  border-radius: var(--radius-card);
  padding: 28px;
  max-width: 480px;
  width: calc(100% - 44px);
  background: var(--surface);
  color: var(--fg);
}
dialog::backdrop {
  background: rgba(22, 54, 67, 0.5);
}
.dialog-actions {
  display: flex;
  gap: 12px;
  margin-top: 20px;
  flex-wrap: wrap;
}
.dialog-actions .button {
  flex: 1;
  min-width: 140px;
}

.hidden {
  display: none !important;
}

/* Progress list shown during deletion */
.progress-list {
  list-style: none;
  padding: 0;
  margin: 16px 0 0 0;
}
.progress-list li {
  padding: 8px 0;
  border-bottom: 1px solid var(--line);
}
.progress-list li:last-child {
  border-bottom: none;
}

@media (min-width: 700px) {
  h1 { font-size: 3.2rem; }
  .section {
    padding: 72px 0;
  }
  /* Hallmark redesign: varied section rhythm - the closing get-the-app band
     breathes wider; the dense privacy block sits tighter. */
  #play {
    padding: 104px 0;
  }
  #privacy {
    padding: 56px 0 64px;
  }
}

/* =========================================================================
   SCENE - v2 "Bright World" illustrated backgrounds (DESIGN_SPEC.md).
   Inline SVG + CSS only. All decorative SVG is aria-hidden + pointer-events:
   none. All motion is transform/opacity only and lives inside the
   prefers-reduced-motion: no-preference query at the bottom of this section,
   so a reduced-motion visitor gets a fully static (and fully finished) scene.
   ========================================================================= */

/* ---- Page-level scene backgrounds -------------------------------------- */
body.page-home {
  background: var(--illus-meadow-wash);
}
body.page-account {
  background: var(--illus-meadow-wash);
}
body.page-404 {
  background: linear-gradient(180deg, var(--illus-sky-top) 0%, var(--illus-sky-mid) 60%, var(--illus-sky-horizon) 100%);
  /* Fix (audit item 4): .scene-404-main .scene-sky breaks out of its narrow
     720px container to full-bleed with the standard left:50%/width:100vw/
     translateX(-50%) trick (see that rule's own comment). `100vw` measures the
     viewport INCLUDING the scrollbar's width, while the page's actual visible
     width is the viewport MINUS the scrollbar - so that box is a few px wider
     than the page itself and pokes past the right edge, adding a horizontal
     scrollbar of its own (only on this page: index.html's hero never needs
     the vw hack, since its sky sits inside a bare full-bleed <section>, not a
     narrow container - it already matches the page's real width and never
     overflows). Clipping here, on body (the box that actually needs the extra
     few px hidden), fixes the scrollbar without touching the sky element's
     intentional full-bleed width or its children's positioning. `overflow-x`
     on `<body>` propagates to the viewport per the CSS overflow-propagation
     rule (there is no explicit `overflow` on `<html>` to take precedence). */
  overflow-x: hidden;
}

/* ---- Reusable scene components: sun, clouds, kites ----------------------- */
.scene-sky {
  position: absolute;
  top: 0;
  left: 0;
  right: 0;
  overflow: hidden;
  pointer-events: none;
  z-index: 1;
}
/* v1.1 section 9 ruling 1: solid disc + rays only - the outer glow halo ring
   (.scene-sun-glow, previously here) is REMOVED. Sun is ~2x the v1.0 desktop
   diameter (108px -> 220px). Rays may pulse; the disc itself stays hard/flat. */
.scene-sun {
  position: absolute;
  top: 0;
  right: 6%;
  width: 120px;
  height: 120px;
}
/* Bug fix: rays previously carried transform-box/transform-origin CSS on the
   same <rect> that also has the SVG `transform="rotate(...)"` attribute -
   mixing those two transform systems on one element is unreliable across
   browsers and was corrupting the rotation math (most rays landed outside
   the viewBox). Fixed by removing the CSS transform properties here (they
   were never needed - the pulse animation targets the .scene-sun-rays
   *group*'s opacity, not any per-ray transform) and, in the markup, moving
   each ray's rotate() onto its own plain <g> wrapper that carries no CSS. */
.scene-sun-rays rect {
  fill: var(--illus-gold);
  opacity: 0.85;
}
.scene-sun-disc {
  fill: var(--illus-sun-disc);
}
/* Moon upgrade (DESIGN_SPEC.md section 16.2) - a second, independent SVG
   sibling sharing the .scene-sun position/size class, toggled via display
   against the sun (dark-mode display rules live with the other dark-mode
   toggles, near the :root dark blocks above). Two flat crater shapes layered
   (not a gradient) give the largest crater a subtle rim-shadow. index.html
   (hero) and 404.html only - the 5 calm pages keep their existing recolored-
   sun moon substitute unchanged. */
.scene-moon {
  display: none;
}
.scene-moon-disc {
  fill: var(--illus-moon-disc);
}
.scene-moon-crater {
  fill: var(--illus-moon-crater);
}
.scene-moon-crater-dark {
  fill: var(--illus-moon-crater-dark);
}
/* Star field (DESIGN_SPEC.md section 16.1) - hero (index.html) and 404.html
   .scene-sky only, dark mode only (display toggle lives with the other
   dark-mode toggles near the :root dark blocks above). Plain flat circles,
   zero new color (reuses --illus-sparkle). Position is percentage left/top
   so one rule works at both breakpoints without a duplicate @media override -
   the band scales proportionally. 404.html reuses classes --1 through --10
   verbatim (fewer markup instances, not fewer CSS rules). */
.scene-star {
  display: none;
  position: absolute;
  pointer-events: none;
}
.scene-star circle {
  fill: var(--illus-sparkle);
}
.scene-star--tiny { width: 2px; height: 2px; }
.scene-star--small { width: 3px; height: 3px; }
.scene-star--medium { width: 4px; height: 4px; }
/* Hero-wide starfield (owner 2026-07-22): stars no longer stop at the sky
   band - a second field covers the whole hero down to the treetops. Sits at
   z-index 0 beneath the content (.container is z-index 2); the stars inside
   self-gate to dark mode via the .scene-star base rules above. */
.hero-starfield {
  position: absolute;
  inset: 0;
  pointer-events: none;
  z-index: 0;
}
.hstar-1 { left: 4%; top: 38%; }
.hstar-2 { left: 9%; top: 66%; }
.hstar-3 { left: 16%; top: 84%; }
.hstar-4 { left: 22%; top: 52%; }
.hstar-5 { left: 30%; top: 74%; }
.hstar-6 { left: 38%; top: 88%; }
.hstar-7 { left: 46%; top: 62%; }
.hstar-8 { left: 55%; top: 79%; }
.hstar-9 { left: 63%; top: 90%; }
.hstar-10 { left: 70%; top: 55%; }
.hstar-11 { left: 78%; top: 72%; }
.hstar-12 { left: 86%; top: 84%; }
.hstar-13 { left: 93%; top: 50%; }
.hstar-14 { left: 97%; top: 70%; }
.scene-star--1 { left: 3%; top: 20%; }
.scene-star--2 { left: 8%; top: 60%; }
.scene-star--3 { left: 14%; top: 35%; }
.scene-star--4 { left: 19%; top: 78%; }
.scene-star--5 { left: 25%; top: 12%; }
.scene-star--6 { left: 31%; top: 55%; }
.scene-star--7 { left: 37%; top: 30%; }
.scene-star--8 { left: 44%; top: 70%; }
.scene-star--9 { left: 50%; top: 10%; }
.scene-star--10 { left: 56%; top: 45%; }
.scene-star--11 { left: 62%; top: 72%; }
.scene-star--12 { left: 68%; top: 25%; }
.scene-star--13 { left: 6%; top: 82%; }
.scene-star--14 { left: 40%; top: 88%; }
.scene-star--15 { left: 96%; top: 20%; }
.scene-star--16 { left: 97%; top: 62%; }
.scene-star--17 { left: 22%; top: 92%; }
.scene-star--18 { left: 55%; top: 85%; }
.scene-cloud {
  position: absolute;
  height: auto;
  fill: var(--illus-cloud);
}
/* Ruling 3: 5 clouds now (was 3), built from 3 distinct silhouettes (see the
   3-ellipse "puff", 4-ellipse "wide", and 2-ellipse "small" shapes in the
   markup), varied sizes/speeds/depths via width + animation-duration. Opacity
   is no longer a static per-cloud value here - it is now fully driven by each
   cloud's own dedicated keyframe (scene-cloud-drift-1..5, in the motion block
   below), which fades the cloud in only once it is entirely clear of the
   viewport edge and fades it out before it starts crossing the other edge -
   see that block for the hand-verified per-viewport math. Each keyframe's
   "visible" opacity IS that cloud's depth value (0.95/0.85/0.75/0.7/0.65). */
.scene-cloud--1 {
  top: 8px;
  left: -140px;
  width: 100px;
}
.scene-cloud--2 {
  top: 46px;
  left: -190px;
  width: 90px;
}
.scene-cloud--3 {
  top: 96px;
  left: -160px;
  width: 60px;
  display: none;
}
.scene-cloud--4 {
  top: 26px;
  left: -170px;
  width: 68px;
  display: none;
}
.scene-cloud--5 {
  top: 130px;
  left: -200px;
  width: 82px;
  display: none;
}
/* Base kite (used solo on 404's runaway kite). The hero's 3-kite set
   (.scene-kite--1/2/3, ruling 2a) overrides position/size/color below. */
.scene-kite {
  position: absolute;
  top: 10px;
  left: 58%;
  width: 44px;
  height: 76px;
  transform-box: fill-box;
  transform-origin: 50% 58%;
}
.scene-kite-body {
  fill: var(--illus-gold);
}
.scene-kite-body-shade {
  /* Bug fix (DESIGN_SPEC.md section 16.2): --illus-kite-shade, not
     --illus-sun-disc - the sun-disc token still needs its dark-mode override
     for the 5 calm pages' moon substitute, so leaving kite shading wired to
     it would silently shift color in dark mode. --illus-kite-shade is the
     same light value (#ffdf96), theme-invariant by construction. */
  fill: var(--illus-kite-shade);
  opacity: 0.55;
}
.scene-kite-string {
  fill: none;
  stroke: var(--illus-lavender);
  stroke-width: 2;
}
.scene-kite-tail path {
  fill: var(--illus-lavender);
}
/* Kite 1 = gold body / lavender tail (matches the .scene-kite defaults, no
   override needed). Kite 2 = rose body / gold tail. Kite 3 = lavender body /
   rose tail (desktop only, hidden at phone width per ruling 2a). */
.scene-kite--1 {
  left: 8%;
  top: 34px;
  width: 36px;
  height: 62px;
}
.scene-kite--2 {
  left: 40%;
  top: 66px;
  width: 32px;
  height: 56px;
}
.scene-kite--2 .scene-kite-body {
  fill: var(--illus-rose);
}
.scene-kite--2 .scene-kite-body-shade {
  fill: #ffffff;
  opacity: 0.4;
}
.scene-kite--2 .scene-kite-string {
  stroke: var(--illus-gold);
}
.scene-kite--2 .scene-kite-tail path {
  fill: var(--illus-gold);
}
.scene-kite--3 {
  left: 74%;
  top: 12px;
  width: 40px;
  height: 70px;
  display: none;
}
.scene-kite--3 .scene-kite-body {
  fill: var(--illus-lavender);
}
.scene-kite--3 .scene-kite-body-shade {
  fill: #ffffff;
  opacity: 0.4;
}
.scene-kite--3 .scene-kite-string {
  stroke: var(--illus-rose);
}
.scene-kite--3 .scene-kite-tail path {
  fill: var(--illus-rose);
}
@media (min-width: 700px) {
  .scene-sun {
    width: 220px;
    height: 220px;
    top: 4px;
    right: 8%;
  }
  .scene-cloud--1 { top: 18px; width: 150px; }
  .scene-cloud--2 { top: 80px; width: 140px; }
  .scene-cloud--3 { top: 150px; width: 100px; display: block; }
  .scene-cloud--4 { top: 48px; width: 96px; display: block; }
  .scene-cloud--5 { top: 210px; width: 120px; display: block; }
  .scene-kite { width: 56px; height: 96px; top: 16px; left: 60%; }
  .scene-kite--1 { width: 60px; height: 104px; top: 46px; left: 8%; }
  .scene-kite--2 { width: 52px; height: 92px; top: 100px; left: 40%; }
  .scene-kite--3 { width: 56px; height: 98px; top: 24px; left: 74%; display: block; }
}

/* ---- index.html: sky hero ------------------------------------------------ */
/* Reserved sky-band height grew (96/160px -> 170/300px) to comfortably fit
   the ~2x sun, the 3-kite float+sway range, and 5 clouds with margin to spare
   - .scene-sky's own height + overflow:hidden is a hard geometric guarantee
   that no decoration ever reaches into the headline/lede/CTA text below it,
   regardless of animation state, at any width 360-1440px. */
.hero.scene-hero {
  position: relative;
  overflow: hidden;
  text-align: center;
  padding: 170px 0 0 0;
  background: linear-gradient(180deg, var(--illus-sky-top) 0%, var(--illus-sky-mid) 55%, var(--illus-sky-horizon) 100%);
}
.hero.scene-hero .scene-sky {
  height: 170px;
}
.hero.scene-hero .container {
  position: relative;
  z-index: 2;
  padding-bottom: 48px;
}
.scene-hills {
  display: block;
  width: 100%;
  height: auto;
  margin-top: 24px;
}
.scene-hill-far {
  fill: var(--illus-hill-far);
}
.scene-hill-mid {
  fill: var(--illus-grass-start);
}
.scene-bushes .bush-far {
  fill: var(--illus-bush-far);
}
.scene-bushes .bush-mid {
  fill: var(--illus-bush-mid);
}
.scene-flowers circle {
  fill: var(--illus-rose);
}
/* Ruling 4: grass tufts on the soil, a tree at each side (never behind text -
   both sit inside the hills-divider SVG, at the hero/meadow seam, well below
   the reserved sky band and well above any section's text), and a swing on
   the left tree with a gentle pendulum sway. --illus-trunk(-dark) are new
   tokens (brown), not in DESIGN_SPEC 3.1 - flagged in the report. */
.scene-grass-tufts path {
  fill: none;
  stroke: var(--illus-bush-far);
  stroke-width: 3;
  stroke-linecap: round;
}
.scene-tree-trunk {
  fill: var(--illus-trunk);
}
.scene-tree-foliage {
  fill: var(--illus-bush-mid);
}
.scene-tree--right .scene-tree-foliage {
  fill: var(--illus-bush-far);
}
/* Round-4 fix: the drawn side-branch stub is gone entirely (owner ruled it
   "weird"). The swing now reads as hanging from a branch HIDDEN inside the
   leaves: it is drawn in the markup BEFORE the crown's foliage circles, so
   the foliage (painted after/on top) visually swallows the rope tops, and
   only the portion of each rope below the crown's underside is visible - no
   drawn branch needed. transform-origin stays at the group's own top-center,
   which is exactly the ropes' attachment point (see markup), so the sway
   pivots from there, looking anchored per ruling 5. */
.scene-swing {
  transform-box: fill-box;
  transform-origin: 50% 0%;
}
.scene-swing-rope {
  fill: none;
  stroke: var(--illus-trunk-dark);
  stroke-width: 2;
}
.scene-swing-seat {
  fill: var(--illus-gold);
}
@media (min-width: 700px) {
  .hero.scene-hero { padding-top: 300px; }
  .hero.scene-hero .scene-sky { height: 300px; }
}
/* Owner ruling 2026-07-28 (hero downscale, desktop only, homepage only - the
   3 for-*.html landing pages share .hero.scene-hero .container but are not
   part of this ruling's scope, hence the body.page-home prefix): tighter
   bottom breathing room under the now-smaller headline/phone pair, so the
   hero as a whole (sky + content + hills) reads as more compact. body.page-
   home raises this selector's specificity above the base .hero.scene-hero
   .container rule above regardless of source order, so it reliably wins at
   >=900px. */
@media (min-width: 900px) {
  body.page-home .hero.scene-hero .container {
    padding-bottom: 28px;
  }
}

/* Owner ruling 2026-07-28, ROUND 2 (hero vertical compression, desktop only,
   homepage only): round 1 shrank the phone/headline pair but left the
   reserved sky band (.scene-sky / .hero.scene-hero padding-top) at its
   original 300px, so at 100% zoom on a ~1900x950 viewport the meadow/trees
   never entered the first screen - all sky, per the owner's "we have to take
   out the chunk amount of the sky from the middle" ruling. This block cuts
   the reserved sky band roughly 40% (300px -> 180px) and scales every
   decorative anchor inside it (sun, 3 kites, 5 clouds) by the same ~0.6
   factor from their existing >=700px values, so nothing clips against the
   shorter .scene-sky overflow:hidden box and the composition's proportions
   (margin between each shape and the band's edge) stay the same as before -
   only the absolute scale changes. Also trims the hero-copy stack's own
   vertical rhythm (headline size, lede line-height, promise/stats margins)
   and widens the copy column (460px -> 540px, still comfortably inside the
   1232px content width alongside the phone mockup) so the lede wraps in
   fewer lines - both cut real height, not just visual density. Every
   selector is body.page-home-scoped: 404.html's full-page sky scene and the
   3 for-*.html landing pages' hero (spec 062, no .hero-grid) both reuse the
   bare .scene-sun/.scene-kite/.scene-cloud/.scene-hills classes this block
   would otherwise also resize, and this ruling is homepage-only. Mobile
   (<900px) is completely untouched - the original >=700px sun/kite/cloud/sky
   rules above still govern 700-899px, same scoping precedent as round 1. */
@media (min-width: 900px) {
  body.page-home .hero.scene-hero {
    padding-top: 180px;
  }
  body.page-home .hero.scene-hero .scene-sky {
    height: 180px;
  }
  body.page-home .scene-sun {
    width: 140px;
    height: 140px;
    top: 2px;
    right: 8%;
  }
  body.page-home .scene-kite--1 {
    width: 38px;
    height: 66px;
    top: 30px;
  }
  body.page-home .scene-kite--2 {
    width: 33px;
    height: 59px;
    top: 64px;
  }
  body.page-home .scene-kite--3 {
    width: 36px;
    height: 62px;
    top: 15px;
  }
  body.page-home .scene-cloud--1 { top: 12px; width: 96px; }
  body.page-home .scene-cloud--2 { top: 51px; width: 90px; }
  body.page-home .scene-cloud--3 { top: 96px; width: 64px; }
  body.page-home .scene-cloud--4 { top: 31px; width: 61px; }
  body.page-home .scene-cloud--5 { top: 126px; width: 70px; }
  /* Owner ruling 2026-07-29 (hero-width follow-up - "I still don't like
     narrow hero section"): supersedes the "widens to 540px, still centered
     as a composed unit" reasoning of the comment this replaced. The owner
     reviewed the composed-unit hero live and still found it too narrow -
     large empty side margins with the copy+phone group floating in the
     middle of the container, instead of using the page's own width like
     the rest of the site.
     This block now spreads the hero across the FULL container width:
     - .hero-grid's first track becomes minmax(0, 1fr) (was a fixed
       minmax(0, 540px)) so the copy column grows to fill whatever the
       phone column doesn't use, with justify-content: stretch (was
       center) - the "composed unit floating in the middle" centering is
       retired, copy sits at the container's left edge, phone at its right
       edge.
     - .hero-copy's cap raises 540px -> 640px so the now much-wider column
       doesn't stretch the lede to an unreadably long measure - it grows to
       fill available space up to that cap, same pattern the sitewide
       .hero-copy cap already uses elsewhere (see the base >=900px rule
       above, kept in sync with this value).
     - the h1 grows via clamp() instead of one fixed size, scaling between
       the 900px and 1280px breakpoints (2.15rem -> 2.6rem, worked out by
       linear interpolation: slope (2.6-2.15)rem / 380px = ~1.9vw, base
       offset ~1.1rem) so it reads proportionally at in-between widths
       instead of jumping at a single breakpoint.
     Verified no collision at 900-1000px, the width the dispatch flagged as
     tight (the phone mockup is 310px wide in this range - see the
     >=900px phone-frame rule earlier in this file): content width at
     exactly 900px is 900 - 48px (.container's own 24px side padding x2) =
     852px; the phone column's intrinsic width is its 310px .phone-frame
     (.phone-glow is absolutely positioned, so it does not add to the flex
     row's own intrinsic width); 852 - 310 - 48px gap = 494px left for the
     1fr copy column - comfortably positive and under the 640px cap, so the
     copy column simply narrows to fit rather than overflowing or
     colliding with the phone. */
  body.page-home .hero-copy {
    max-width: 640px;
  }
  body.page-home .hero-grid {
    grid-template-columns: minmax(0, 1fr) auto;
    justify-content: stretch;
  }
  body.page-home .hero.scene-hero h1 {
    font-size: clamp(2.15rem, 1.1rem + 1.9vw, 2.6rem);
    margin-bottom: 0.3em;
  }
  body.page-home .hero.scene-hero .lede {
    line-height: 1.5;
    margin-bottom: 8px;
  }
  body.page-home .hero.scene-hero .hero-stats {
    margin-top: 8px;
  }
  body.page-home .hero.scene-hero .container {
    padding-bottom: 16px;
  }
  body.page-home .scene-hills {
    margin-top: 14px;
  }
}

/* ---- index.html: mid-section scene colors and gutter texture ------------ */
/* Step numbers, checkmarks and section labels pick up scene colors instead
   of the monochrome accent, per section theme (family sharing = grass,
   privacy = sea teal, get-the-app/FAQ/contact = gold). Every pairing below
   is computed against its own badge/chip background, not the page wash. */
/* Refactored to the badge-tint custom properties (051 section 3.1) - pure
   equivalence, same computed colors as before. */
#sharing .section-label,
#sharing .step-number {
  background: var(--badge-grass-bg);
  color: var(--badge-grass-fg);
}
#privacy .section-label {
  background: var(--badge-sea-bg);
  color: var(--badge-sea-fg);
}
#play .section-label,
#faq .section-label,
#contact .section-label {
  background: var(--badge-gold-bg);
  color: var(--badge-gold-fg);
}
/* Comparison page (brief 006/spec 063): same section-label badge-family pattern,
   applied to the new page's three mid-page sections. */
#compare-table .section-label {
  background: var(--badge-gold-bg);
  color: var(--badge-gold-fg);
}
#compare-context .section-label {
  background: var(--badge-sea-bg);
  color: var(--badge-sea-fg);
}
#compare-cta .section-label {
  background: var(--badge-grass-bg);
  color: var(--badge-grass-fg);
}

.section--scene {
  position: relative;
}
/* Round-3 rework (spec 9.6 amended): fillers move INWARD, hugging the
   content raft's outer edge (a fixed 20px gap from .container's own edge)
   instead of sitting out at the far viewport edge. .container maxes out at
   880px and is centered (margin:0 auto), so its right edge is always at
   50vw+440px and its left edge at 50vw-440px, REGARDLESS of viewport width -
   that is what makes a viewport-relative calc() anchor safe here (unlike a
   fixed px value, it tracks the raft edge exactly at any width). The 20px
   gap keeps the decoration visually "accompanying" the text without ever
   touching the raft border.
   Gate: at exactly 1140px, the gutter is (1140-880)/2 = 130px wide; minus
   the 20px gap that leaves 110px for the decoration itself - a 26px margin
   past the biggest filler (the 84px balloon). Below 1140px that margin goes
   negative before the gutter disappears, so - per instruction - these stay
   DESKTOP-ONLY (>=1140px) rather than showing partially cut off on narrower
   screens. (Verified at 1140/1200/1440px; margin only grows from there.) */
.scene-gutter {
  position: absolute;
  top: 40px;
  width: 96px;
  pointer-events: none;
  display: none;
}
/* With the 1280px container (owner's second widening), gutters sit at a 660px
   half-offset - clear of the 640px content half-width - and only render from
   1560px up (scrollbar-inclusive), where 660 + 96px fits the viewport half. */
.scene-gutter--right {
  left: calc(50vw + 660px);
}
.scene-gutter--left {
  right: calc(50vw + 660px);
  top: 110px;
}
/* Generic stacking for any decorative SVG dropped into a gutter. */
.scene-gutter > svg {
  display: block;
  margin-bottom: 18px;
}
.scene-gutter > svg:last-child {
  margin-bottom: 0;
}
.scene-sparkle {
  width: 26px;
  height: 26px;
  fill: var(--illus-sparkle);
}
.scene-sparkle--lavender {
  fill: var(--illus-lavender);
}
.scene-sparkle--gold {
  fill: var(--illus-gold);
}
.scene-cloud-tiny {
  width: 84px;
  height: auto;
  fill: var(--illus-cloud);
  opacity: 0.85;
}
/* Ruling 6 (round-3 amended) scroll-gap fillers: bigger, many more instances,
   varied colors across the full scene palette AND varied shapes, moved into
   the mid-gutters near the content rafts. Color modifier classes below cover
   rose (default), lavender, gold, sea-teal, and grass-green. */
.scene-butterfly {
  width: 52px;
  height: auto;
}
.scene-butterfly .wing {
  fill: var(--illus-rose);
}
.scene-butterfly--lavender .wing {
  fill: var(--illus-lavender);
}
.scene-butterfly--gold .wing {
  fill: var(--illus-gold);
}
.scene-butterfly--sea .wing {
  fill: var(--illus-sea-mid);
}
.scene-butterfly--grass .wing {
  fill: var(--illus-bush-mid);
}
.scene-butterfly-body {
  fill: none;
  stroke: var(--fg2);
  stroke-width: 2;
  opacity: 0.45;
}
.scene-butterfly-wings {
  transform-box: fill-box;
  transform-origin: 50% 50%;
}
.scene-bird {
  width: 46px;
  height: auto;
  fill: none;
  stroke: var(--fg2);
  stroke-width: 2.5;
  stroke-linecap: round;
  opacity: 0.65;
}
.scene-bird--sea {
  stroke: var(--illus-sea-mid);
  opacity: 0.75;
}
.scene-balloon {
  width: 84px;
  height: auto;
}
.scene-balloon-envelope {
  fill: var(--illus-gold);
}
.scene-balloon-stripe {
  fill: var(--illus-rose);
  opacity: 0.85;
}
.scene-balloon-rope {
  fill: none;
  stroke: var(--fg2);
  stroke-width: 1.5;
  opacity: 0.5;
}
.scene-balloon-basket {
  fill: var(--illus-sand);
}
.scene-flower-cluster {
  width: 60px;
  height: auto;
}
/* Each cluster mixes colors directly via per-dot classes, so any two-tone
   combination can be composed in the markup without extra CSS variants. */
.scene-flower-cluster .dot-rose {
  fill: var(--illus-rose);
}
.scene-flower-cluster .dot-gold {
  fill: var(--illus-gold);
}
.scene-flower-cluster .dot-lavender {
  fill: var(--illus-lavender);
}
.scene-flower-cluster .dot-sea {
  fill: var(--illus-sea-mid);
}
.scene-flower-cluster .dot-grass {
  fill: var(--illus-bush-mid);
}
/* Fireflies (DESIGN_SPEC.md section 16.3) - one per mid-page section, dropped
   into the desktop gutters as an additional nocturnal element that COEXISTS
   with the existing butterflies/birds/balloon/sparkles (confirmed, not
   hidden/dimmed - same "lanterns/fireflies at night" reasoning already
   governing this dark-mode texture). Dark mode only (display toggle lives
   with the other dark-mode toggles near the :root dark blocks above). Two
   flat concentric circles, zero new color (reuses --illus-gold). Core radius
   varies 1-2px per instance directly via the markup's `r` attribute, matching
   how clouds/butterflies already vary size per instance. */
/* Specificity note: the generic `.scene-gutter > svg { display: block }` layout
   rule (0,1,1) would beat a bare `.scene-firefly` (0,1,0) hide - so the hide
   rule matches the same compound form to win in light mode, while both dark
   activation selectors (0,3,0) still comfortably override it at night. */
.scene-gutter > svg.scene-firefly {
  display: none;
}
.scene-firefly {
  width: 20px;
  height: 20px;
}
.scene-firefly-halo {
  fill: var(--illus-gold);
  opacity: 0.3;
}
.scene-firefly-core {
  fill: var(--illus-gold);
  opacity: 0.95;
}
/* Meadow fireflies (owner request 2026-07-22): same visual family as the gutter
   fireflies, but living INSIDE the .scene-hills SVG (on the grass, under the
   trees) rather than in the side gutters - so they get their own group-level
   dark-mode gate and reuse the halo/core circle classes for fills. */
.scene-meadow-fireflies {
  display: none;
}
/* 1560px: media queries measure the window INCLUDING the vertical scrollbar
   (~15px) while layout width excludes it - the threshold keeps the gutter's
   outer edge (50vw + 660px + 96px) inside the usable width with the
   scrollbar's width spent. */
@media (min-width: 1560px) {
  .scene-gutter {
    display: block;
  }
}

/* ---- index.html: sea finale above the footer ----------------------------- */
.scene-sea {
  line-height: 0;
  pointer-events: none;
}
.scene-sea-svg {
  display: block;
  width: 100%;
  height: auto;
}
.scene-sand {
  fill: var(--illus-sand);
}
.scene-sea-far {
  fill: var(--illus-sea-far);
}
.scene-sea-mid {
  fill: var(--illus-sea-mid);
}
.scene-sea-near {
  fill: var(--illus-sea-near);
}
/* Ruling 7: bigger, clearly-readable sailboat (hull + 2 sails + pennant),
   redrawn ~2x from v1.0's single-triangle boat, still on the horizon. */
.scene-hull {
  fill: #ffffff;
}
.scene-mast {
  fill: none;
  stroke: #ffffff;
  stroke-width: 2;
}
.scene-sail {
  fill: #ffffff;
}
.scene-sail-jib {
  opacity: 0.85;
}
.scene-pennant {
  fill: var(--illus-gold);
}
.scene-sailboat {
  transform-box: fill-box;
  transform-origin: 50% 100%;
}
.scene-foam {
  fill: var(--illus-foam);
}
/* Designer round-2 (owner ruling 1, 2026-07-22): the index footer is no longer
   a dark sea band - it sits on the page background (tracking both themes via
   --footer-bg) beneath the sea illustration, laid out as a three-column grid:
   full logo lockup | quick links | contact, with a closing note row. */
.scene-sea-footer {
  background: var(--footer-bg);
  border-top: none;
  margin-top: 0;
}
.footer-grid {
  display: grid;
  grid-template-columns: 1fr;
  gap: 36px;
  padding-top: 4px;
}
@media (min-width: 780px) {
  .footer-grid {
    grid-template-columns: 1.3fr 1fr 1fr;
    gap: 32px;
  }
}
.footer-logo-full {
  display: block;
  width: 150px;
  height: 46px;
  margin-bottom: 16px;
}
.footer-heading {
  font-size: 0.78rem;
  font-weight: 600;
  letter-spacing: 0.08em;
  text-transform: uppercase;
  color: var(--muted);
  margin: 0 0 16px 0;
}
.footer-grid .footer-links {
  flex-direction: column;
  gap: 10px;
  margin: 0;
}
.footer-contact-list {
  list-style: none;
  padding: 0;
  margin: 0;
  display: flex;
  flex-direction: column;
  gap: 10px;
}
.footer-contact-list a {
  padding: 10px 0;
  display: inline-block;
  /* Owner ruling 2026-07-29: same underline removal as .footer-links a
     above - kept back on hover/focus for affordance. */
  text-decoration: none;
}
.footer-contact-list a:hover,
.footer-contact-list a:focus {
  text-decoration: underline;
}
.footer-bottom {
  display: flex;
  flex-wrap: wrap;
  gap: 8px 24px;
  align-items: center;
  justify-content: space-between;
  margin-top: 40px;
  padding-top: 22px;
  border-top: 1px solid var(--line);
}
/* Text sized/weighted to clear WCAG AA-large (>=18.66px bold, 3:1) rather than
   AA-normal: white-on-sea-near computes to ~4.0:1 and foam-on-sea-near to
   ~3.8:1, both under the 4.5:1 normal-text floor but comfortably over 3:1 for
   large text. Flagged to web-designer: DESIGN_SPEC 6 lists these as 4.6:1 /
   4.5:1+, which this implementation could not reproduce - see report. */
/* Round-2: the old white-on-sea-band text overrides are gone with the band
   itself - footer text now uses the page's normal tokens in both themes. */
/* The site-wide accent focus outline computes to ~1.1:1 against sea-near
   (both are similarly dark/saturated) - practically invisible. Scoped to
   white here only for this dark band; the global focus-outline token and
   behavior elsewhere on the site is untouched. */

/* ---- account.html / delete-account.html: calm sky header band ----------- */
.scene-header-band {
  position: relative;
  height: 72px;
  overflow: hidden;
  background: linear-gradient(180deg, var(--illus-sky-top) 0%, var(--illus-sky-mid) 100%);
}
.scene-header-band .scene-sun {
  top: -14px;
  right: 6%;
  width: 68px;
  height: 68px;
}
.scene-header-band--sober {
  height: 40px;
}
@media (min-width: 700px) {
  .scene-header-band { height: 104px; }
  .scene-header-band .scene-sun { top: -18px; width: 100px; height: 100px; }
  .scene-header-band--sober { height: 56px; }
}

/* ---- Thin meadow footer strip (account/delete-account/privacy/terms) ---- */
.scene-meadow-strip {
  line-height: 0;
}
.scene-meadow-strip svg {
  display: block;
  width: 100%;
  height: 40px;
}
.scene-strip-far {
  fill: var(--illus-hill-far);
}
.scene-strip-near {
  fill: var(--illus-grass-start);
}

/* ---- 404.html: FULL-PAGE sky, runaway kite (v1.1 ruling 5) --------------- */
/* The squeezed boxed-card look is gone: main carries no background/radius of
   its own any more, so body.page-404's full-viewport sky gradient (see
   "Page-level scene backgrounds" above, and the sticky-footer min-height:100vh
   body rule) shows through uninterrupted behind the header and all the way
   to the footer. Only the reserved top padding remains, for the same
   geometric never-behind-text guarantee as the hero (matches its 170/300px
   band so the bigger sun/clouds/kite have identical clearance here). The text
   itself now lives in .scene-404-raft, a small light card that floats on the
   scene instead of the scene living inside a card. */
/* Bug fix: .scene-404-main carries .container.narrow, so it (and therefore
   any absolutely-positioned child using left:0/right:0) is capped at 720px
   wide and centered - unlike the hero, which is a bare full-bleed <section>.
   The shared cloud-drift keyframe travels the FULL VIEWPORT (100vw); inside
   the narrower 720px box that made a cloud clip mid-drift against the box's
   own edge, reading as a "half-cut" silhouette. Fix: break .scene-sky out to
   true 100vw (the standard centered-parent full-bleed trick - valid here
   because .container's margin: 0 auto keeps .scene-404-main's own center
   aligned with the viewport's center) so the decorative band's actual width
   matches the 100vw the animation assumes, on both index and 404 alike.
   .scene-404-main no longer needs its own overflow:hidden for this: .scene-sky
   still self-clips (see the base .scene-sky rule) to its own height, which is
   what enforces the never-behind-text guarantee, not the parent box. */
.scene-404-main {
  position: relative;
  padding: 170px 22px 56px;
}
.scene-404-main .scene-sky {
  left: 50%;
  right: auto;
  width: 100vw;
  transform: translateX(-50%);
  height: 170px;
}
.scene-404-main .scene-kite {
  left: 14%;
}
.scene-404-raft {
  position: relative;
  z-index: 2;
  background: var(--surface);
  border-radius: var(--radius-card);
  padding: 28px 24px;
  margin: 0 auto;
}
@media (min-width: 700px) {
  .scene-404-main { padding-top: 300px; }
  .scene-404-main .scene-sky { height: 300px; }
}

/* Fix (audit item 1): the base .scene-cloud--1..5 rules above (index.html's
   hero has 1-5, 404.html's runaway-kite sky reuses 1-3) each set `left` to a
   negative, off-screen value (-140px..-200px) inside .scene-sky's
   overflow:hidden clip - that is deliberate, it is the START point the
   no-preference drift animation below reads from (see the hand-verified
   entry/exit math on the scene-cloud-drift-1..5 keyframes further down). A
   reduced-motion visitor never runs that animation, so without an override
   every cloud stays clipped off-screen forever, i.e. no clouds at all. This
   block gives each cloud a simple on-screen resting spot instead, whenever
   motion is off. Percentage `left` (not a static px transform) is used so
   one rule holds at every breakpoint without a separate desktop override -
   `top`/`width`/`display` are untouched (already breakpoint-specific, set
   above) and this media feature can never be active at the same time as
   `prefers-reduced-motion: no-preference` below, so the animated path's
   off-screen start/end math is completely unaffected. */
@media (prefers-reduced-motion: reduce) {
  .scene-cloud--1 { left: 4%; }
  .scene-cloud--2 { left: 22%; }
  .scene-cloud--3 { left: 40%; }
  .scene-cloud--4 { left: 56%; }
  .scene-cloud--5 { left: 70%; }
}

/* ---- Motion: opt-in only, transform/opacity, matches DESIGN_SPEC table 5 - */
@media (prefers-reduced-motion: no-preference) {
  /* Fix (audit item 3): will-change lives here, not on the base .scene-cloud
     rule, so a reduced-motion visitor - whose clouds never animate - never
     pays the (small but real) compositor memory cost of a promoted layer. */
  .scene-cloud {
    will-change: transform;
  }
  /* Sun: rays pulse gently (ruling 1); the disc never animates. */
  .scene-sun-rays {
    animation: scene-sun-rays-pulse 8s ease-in-out infinite;
  }
  /* Each cloud uses ITS OWN keyframe (not a shared one) because the opacity
     fade-in/out points are computed from that cloud's specific width/start
     position - see the keyframe definitions for the verified math. */
  .scene-cloud--1 {
    animation: scene-cloud-drift-1 65s linear infinite;
  }
  .scene-cloud--2 {
    animation: scene-cloud-drift-2 85s linear infinite;
    animation-delay: -20s;
  }
  .scene-cloud--3 {
    animation: scene-cloud-drift-3 105s linear infinite;
    animation-delay: -40s;
  }
  .scene-cloud--4 {
    animation: scene-cloud-drift-4 120s linear infinite;
    animation-delay: -10s;
  }
  .scene-cloud--5 {
    animation: scene-cloud-drift-5 95s linear infinite;
    animation-delay: -55s;
  }
  /* Base kite (404's solo runaway kite): simple sway, as v1.0. */
  .scene-kite {
    animation: scene-kite-sway 7s ease-in-out infinite;
  }
  .scene-kite--runaway {
    animation-name: scene-kite-runaway-sway;
  }
  /* Hero's 3-kite set (ruling 2a): livelier combined rotate+translate float
     paths, each >= 8s so the cycle stays slow/gentle even though the arc is
     bigger. These rules are declared after the base .scene-kite rule above,
     so their animation shorthand fully overrides it for these 3 kites. */
  .scene-kite--1 {
    animation: scene-kite-float-a 9s ease-in-out infinite;
  }
  .scene-kite--2 {
    animation: scene-kite-float-b 11s ease-in-out infinite;
    animation-delay: -3s;
  }
  .scene-kite--3 {
    animation: scene-kite-float-a 8.5s ease-in-out infinite reverse;
    animation-delay: -1.5s;
  }
  .scene-swing {
    animation: scene-swing-sway 6.5s ease-in-out infinite;
  }
  .scene-sparkle {
    animation: scene-sparkle-twinkle 5s ease-in-out infinite;
  }
  .scene-sparkle--slow {
    animation-duration: 7s;
    animation-delay: 1.2s;
  }
  /* Star twinkle (DESIGN_SPEC.md section 16.1) - reuses the existing sparkle
     keyframe, zero new keyframe. Only 6 of the 18 stars get a twinkle class
     (variant A/B, matching the .scene-sparkle/.scene-sparkle--slow pattern
     exactly); the other 12 render at a fixed, fully-opaque brightness
     permanently (no animation rule at all - the correct reduced-motion state
     for them too, by the same "absence of a rule" pattern). */
  .scene-star--twinkle-a {
    animation: scene-sparkle-twinkle 5s ease-in-out infinite;
  }
  .scene-star--twinkle-b {
    animation: scene-sparkle-twinkle 7s ease-in-out infinite;
    animation-delay: 1.2s;
  }
  .scene-butterfly {
    animation: scene-butterfly-bob 7s ease-in-out infinite;
  }
  /* Stagger timing is a separate modifier from color, so any color variant
     can pair with any timing variant (more instances now share these). */
  .scene-butterfly--t2 {
    animation-duration: 7.8s;
    animation-delay: -2s;
  }
  .scene-butterfly--t3 {
    animation-duration: 8.6s;
    animation-delay: -4s;
  }
  .scene-butterfly-wings {
    animation: scene-butterfly-wing-pulse 6s ease-in-out infinite;
  }
  .scene-bird {
    animation: scene-bird-flit 8s ease-in-out infinite;
  }
  .scene-bird--t2 {
    animation-duration: 9.5s;
    animation-delay: -2.5s;
  }
  .scene-bird--t3 {
    animation-duration: 10.5s;
    animation-delay: -5s;
  }
  .scene-balloon {
    animation: scene-balloon-drift 10s ease-in-out infinite;
  }
  .scene-foam {
    animation: scene-foam-bob 9s ease-in-out infinite;
  }
  .scene-foam:nth-child(even) {
    animation-delay: -3s;
  }
  .scene-foam:nth-child(3n) {
    animation-delay: -6s;
  }
  .scene-sailboat {
    animation: scene-sailboat-sway 10s ease-in-out infinite;
  }
  /* Fireflies (docs/060-mobile-nav-and-firefly-motion.md Part B baseline;
     AMENDED by owner ruling 2026-07-23 for calmer flicker + a bigger wander
     area; FURTHER AMENDED by owner ruling 2026-07-23 (same day, second pass,
     commit ce8fd10 follow-up): the a/b/c-shared-by-three-instances scheme
     below made several fireflies visibly repeat each other's motion (three
     meadow fireflies per shared path, plus the gutter --t1 instances on
     "sharing" and "why" both silently matching the bare `.scene-firefly`
     selector and so sharing path+duration+delay outright). Every one of the
     15 instances (9 meadow + 6 gutter) now gets its OWN keyframe, duration,
     and delay - no two instances share a wander path or a flicker phase.
     Wander (transform, on the parent) and flicker (opacity, on the core/halo
     children) stay two independent animations per firefly, as before. */

  /* Wander - parent element (.scene-firefly svg / .meadow-ff g), transform
     only. Each instance is selected uniquely and without new markup: the 6
     gutter fireflies each live in exactly one `.scene-gutter` div inside one
     `<section id="...">`, so `#id .scene-gutter--side .scene-firefly` is
     already a unique selector per instance (the pre-existing --t1/--t2/--t3
     modifier classes are no longer load-bearing for animation selection but
     are left in the markup, unused, rather than triggering an avoidable HTML
     edit). The 9 meadow fireflies are all direct `<g>` children of one
     `.scene-meadow-fireflies` group in a fixed document order, so
     `:nth-child(n)` addresses each one individually. Per-instance safety
     ranges (meadow: derived from each instance's actual cx/cy so it stays
     inside the 0-1440 x 0-220 hills viewBox, at or below the y=80 crest, and
     off the left/right SVG edges; gutter: outward <=10px / inward <=18px /
     vertical within +-50px, per the existing budget) are noted per keyframe,
     below. Durations spread 26-48s, no two instances equal; delays likewise
     unique so no two fireflies are ever at the same point in their loop. */
  #sharing .scene-gutter--right .scene-firefly {
    animation: firefly-wander-g1 26s ease-in-out infinite;
  }
  #privacy .scene-gutter--right .scene-firefly {
    animation: firefly-wander-g2 29s ease-in-out infinite;
    animation-delay: -9s;
  }
  #why .scene-gutter--right .scene-firefly {
    animation: firefly-wander-g3 32s ease-in-out infinite;
    animation-delay: -14s;
  }
  #play .scene-gutter--right .scene-firefly {
    animation: firefly-wander-g4 36s ease-in-out infinite;
    animation-delay: -21s;
  }
  #faq .scene-gutter--left .scene-firefly {
    animation: firefly-wander-g5 41s ease-in-out infinite;
    animation-delay: -27s;
  }
  #contact .scene-gutter--left .scene-firefly {
    animation: firefly-wander-g6 46s ease-in-out infinite;
    animation-delay: -33s;
  }
  .scene-meadow-fireflies > g:nth-child(1) {
    animation: firefly-wander-m1 27s ease-in-out infinite;
    animation-delay: -6s;
  }
  .scene-meadow-fireflies > g:nth-child(2) {
    animation: firefly-wander-m2 30s ease-in-out infinite;
    animation-delay: -11s;
  }
  .scene-meadow-fireflies > g:nth-child(3) {
    animation: firefly-wander-m3 33s ease-in-out infinite;
    animation-delay: -16s;
  }
  .scene-meadow-fireflies > g:nth-child(4) {
    animation: firefly-wander-m4 34s ease-in-out infinite;
    animation-delay: -19s;
  }
  .scene-meadow-fireflies > g:nth-child(5) {
    animation: firefly-wander-m5 37s ease-in-out infinite;
    animation-delay: -24s;
  }
  .scene-meadow-fireflies > g:nth-child(6) {
    animation: firefly-wander-m6 39s ease-in-out infinite;
    animation-delay: -28s;
  }
  .scene-meadow-fireflies > g:nth-child(7) {
    animation: firefly-wander-m7 42s ease-in-out infinite;
    animation-delay: -31s;
  }
  .scene-meadow-fireflies > g:nth-child(8) {
    animation: firefly-wander-m8 44s ease-in-out infinite;
    animation-delay: -35s;
  }
  .scene-meadow-fireflies > g:nth-child(9) {
    animation: firefly-wander-m9 48s ease-in-out infinite;
    animation-delay: -40s;
  }

  /* Flicker - core and halo circles, opacity only. The two shared keyframes
     (firefly-flicker-core/-halo, unchanged below) already stagger the
     halo's dip a few points behind the core's WITHIN one instance; what
     previously caused lockstep blinking was every instance in a --t1/--t2/
     --t3 tier sharing the exact same duration+delay as every other instance
     in that tier. Each of the 15 instances now gets its own duration/delay
     pair (loosely grouped into three tiers inherited from the prior 7s/9s/
     11s families, but no two values repeat), so nearby fireflies never
     blink in phase. */
  #sharing .scene-firefly-core,
  #sharing .scene-firefly-halo {
    animation-duration: 7s;
    animation-delay: 0s;
  }
  #privacy .scene-firefly-core,
  #privacy .scene-firefly-halo {
    animation-duration: 7.4s;
    animation-delay: -2.1s;
  }
  #why .scene-firefly-core,
  #why .scene-firefly-halo {
    animation-duration: 7.8s;
    animation-delay: -4.3s;
  }
  #play .scene-firefly-core,
  #play .scene-firefly-halo {
    animation-duration: 8.2s;
    animation-delay: -1.5s;
  }
  #faq .scene-firefly-core,
  #faq .scene-firefly-halo {
    animation-duration: 8.6s;
    animation-delay: -3.8s;
  }
  #contact .scene-firefly-core,
  #contact .scene-firefly-halo {
    animation-duration: 9s;
    animation-delay: -5.6s;
  }
  .scene-firefly-core,
  .scene-firefly-halo {
    animation-name: firefly-flicker-core;
    animation-timing-function: ease-in-out;
    animation-iteration-count: infinite;
  }
  .scene-firefly-halo {
    animation-name: firefly-flicker-halo;
  }
  .scene-meadow-fireflies > g:nth-child(1) .scene-firefly-core,
  .scene-meadow-fireflies > g:nth-child(1) .scene-firefly-halo {
    animation-duration: 9.4s;
    animation-delay: -0.9s;
  }
  .scene-meadow-fireflies > g:nth-child(2) .scene-firefly-core,
  .scene-meadow-fireflies > g:nth-child(2) .scene-firefly-halo {
    animation-duration: 9.8s;
    animation-delay: -2.7s;
  }
  .scene-meadow-fireflies > g:nth-child(3) .scene-firefly-core,
  .scene-meadow-fireflies > g:nth-child(3) .scene-firefly-halo {
    animation-duration: 10.2s;
    animation-delay: -4.5s;
  }
  .scene-meadow-fireflies > g:nth-child(4) .scene-firefly-core,
  .scene-meadow-fireflies > g:nth-child(4) .scene-firefly-halo {
    animation-duration: 10.6s;
    animation-delay: -6.3s;
  }
  .scene-meadow-fireflies > g:nth-child(5) .scene-firefly-core,
  .scene-meadow-fireflies > g:nth-child(5) .scene-firefly-halo {
    animation-duration: 11s;
    animation-delay: -1.2s;
  }
  .scene-meadow-fireflies > g:nth-child(6) .scene-firefly-core,
  .scene-meadow-fireflies > g:nth-child(6) .scene-firefly-halo {
    animation-duration: 7.2s;
    animation-delay: -3.4s;
  }
  .scene-meadow-fireflies > g:nth-child(7) .scene-firefly-core,
  .scene-meadow-fireflies > g:nth-child(7) .scene-firefly-halo {
    animation-duration: 8.4s;
    animation-delay: -5.1s;
  }
  .scene-meadow-fireflies > g:nth-child(8) .scene-firefly-core,
  .scene-meadow-fireflies > g:nth-child(8) .scene-firefly-halo {
    animation-duration: 9.6s;
    animation-delay: -2.4s;
  }
  .scene-meadow-fireflies > g:nth-child(9) .scene-firefly-core,
  .scene-meadow-fireflies > g:nth-child(9) .scene-firefly-halo {
    animation-duration: 10.8s;
    animation-delay: -0.6s;
  }
  /* Hero phone reel crossfade (DESIGN_SPEC.md section 5 "Exception 4";
     docs/051-external-redesign-merge.md section 4.3): three .reel-screen
     "pages" share one keyframe, staggered by negative animation-delay so
     exactly one is ever visible at a time. transform: scale() + opacity
     only. The reduced-motion state is the ungated base rule above
     (.reel-screen:first-child { opacity: 1; }) - nothing further to gate. */
  .reel-screen {
    /* Owner 2026-07-22: twice slower (6s -> 12s cycle). */
    animation: reel-cycle 12s ease-in-out infinite;
  }
  .reel-screen:nth-child(2) {
    animation-delay: -4s;
  }
  .reel-screen:nth-child(3) {
    animation-delay: -8s;
  }
}

@keyframes scene-sun-rays-pulse {
  0%, 100% { opacity: 0.7; }
  50% { opacity: 1; }
}
/* Round-3 fix: clouds drift the full viewport width, so as they cross x=0 or
   x=100vw their SHAPE would otherwise be visibly cut by the screen edge for
   part of the cycle ("cutting itself while going out of frame"). Each cloud
   below fades in via opacity ONLY once its own left edge has fully cleared
   x=0 (no more clipping possible) and fades out BEFORE its right edge starts
   crossing x=100vw - so the only things ever visible are a complete cloud or
   nothing, never a partial silhouette. transform keeps drifting continuously
   underneath (only specified at 0%/100%, linearly interpolated throughout;
   independent of the opacity keyframes).
   Percentages are computed per-cloud from its fixed left/width against the
   WORST-CASE viewport in its active display range (360px for clouds 1-2,
   which show from 360px up; 700px for clouds 3-5, display:none below 700px)
   - since a narrower viewport gives a smaller total travel distance, the
   danger zone is a LARGER percentage of the cycle there, so a margin that is
   safe at the narrowest active viewport stays safe (with more margin) at
   every wider viewport up to 1440px. All entry/exit margins below are
   >=2.3 percentage points past the exact computed unsafe threshold. */
@keyframes scene-cloud-drift-1 {
  /* left:-140px, width 100(mobile)/150(desktop). Worst case entry: VW=360,
     unsafe until 23.33%. Worst case exit: VW=360, unsafe from 66.67%. */
  0% { transform: translateX(0); opacity: 0; }
  26% { opacity: 0; }
  30% { opacity: 0.95; }
  64% { opacity: 0.95; }
  68% { opacity: 0; }
  100% { transform: translateX(calc(100vw + 240px)); opacity: 0; }
}
@keyframes scene-cloud-drift-2 {
  /* left:-190px, width 90(mobile)/140(desktop). Worst case entry: VW=360,
     unsafe until 31.67%. Worst case exit: VW=360, unsafe from 76.67%. */
  0% { transform: translateX(0); opacity: 0; }
  34% { opacity: 0; }
  38% { opacity: 0.85; }
  74% { opacity: 0.85; }
  78% { opacity: 0; }
  100% { transform: translateX(calc(100vw + 240px)); opacity: 0; }
}
@keyframes scene-cloud-drift-3 {
  /* left:-160px, width 100 (desktop-only, display:none below 700px). Worst
     case entry: VW=700, unsafe until 17.02%. Worst case exit: VW=700,
     unsafe from 80.85%. */
  0% { transform: translateX(0); opacity: 0; }
  20% { opacity: 0; }
  24% { opacity: 0.75; }
  78% { opacity: 0.75; }
  82% { opacity: 0; }
  100% { transform: translateX(calc(100vw + 240px)); opacity: 0; }
}
@keyframes scene-cloud-drift-4 {
  /* left:-170px, width 96 (desktop-only). Worst case entry: VW=700, unsafe
     until 18.09%. Worst case exit: VW=700, unsafe from 82.34%. */
  0% { transform: translateX(0); opacity: 0; }
  21% { opacity: 0; }
  25% { opacity: 0.7; }
  80% { opacity: 0.7; }
  84% { opacity: 0; }
  100% { transform: translateX(calc(100vw + 240px)); opacity: 0; }
}
@keyframes scene-cloud-drift-5 {
  /* left:-200px, width 120 (desktop-only). Worst case entry: VW=700, unsafe
     until 21.28%. Worst case exit: VW=700, unsafe from 82.98%. */
  0% { transform: translateX(0); opacity: 0; }
  24% { opacity: 0; }
  28% { opacity: 0.65; }
  80% { opacity: 0.65; }
  84% { opacity: 0; }
  100% { transform: translateX(calc(100vw + 240px)); opacity: 0; }
}
@keyframes scene-kite-sway {
  0%, 100% { transform: rotate(-4deg); }
  50% { transform: rotate(4deg); }
}
@keyframes scene-kite-runaway-sway {
  0%, 100% { transform: rotate(-11deg); }
  50% { transform: rotate(-19deg); }
}
/* Combined rotate + translate "float" paths for the hero's 3-kite set
   (ruling 2a: "bigger sway arcs plus slow floating drift"). transform-only,
   each stop just a different translate()+rotate() pair. */
@keyframes scene-kite-float-a {
  0%   { transform: translate(0, 0) rotate(-6deg); }
  25%  { transform: translate(6px, -12px) rotate(3deg); }
  50%  { transform: translate(-5px, -20px) rotate(9deg); }
  75%  { transform: translate(-9px, -6px) rotate(-2deg); }
  100% { transform: translate(0, 0) rotate(-6deg); }
}
@keyframes scene-kite-float-b {
  0%   { transform: translate(0, 0) rotate(7deg); }
  30%  { transform: translate(-9px, -16px) rotate(-4deg); }
  60%  { transform: translate(7px, -22px) rotate(-10deg); }
  100% { transform: translate(0, 0) rotate(7deg); }
}
@keyframes scene-swing-sway {
  0%, 100% { transform: rotate(-5deg); }
  50% { transform: rotate(5deg); }
}
@keyframes scene-sparkle-twinkle {
  0%, 100% { opacity: 0.25; }
  50% { opacity: 0.9; }
}
/* Fireflies (DESIGN_SPEC.md section 16.3): opacity + transform: translate()
   only. */
/* Firefly wander - per-instance unique paths (owner ruling 2026-07-23,
   second pass, supersedes the shared a/b/c paths introduced in commit
   ce8fd10): each of the 6 gutter and 9 meadow fireflies now has its own
   keyframe with its own waypoint geometry (different shape, different
   number of stops), not a scaled or mirrored copy of another instance's
   path. Gutter paths still respect the existing budget: outward (away from
   the content raft) <=10px, inward (toward the raft) <=18px, vertical
   within +-50px - .scene-gutter/.section have no overflow:hidden, so extra
   vertical travel only ever crosses into the page's own continuous
   background, never off-page. Left-gutter instances mirror the right
   gutter's inward/outward sign convention (left gutter's "inward," toward
   the raft, is positive x) but are drawn as their own distinct shapes, not
   a mechanical sign-flip of a right-gutter path. Meadow paths are SVG user
   units (uniform-scaling viewBox 0-1440 x 0-220, no per-breakpoint tuning
   needed); each path's horizontal/vertical amplitude is computed from that
   ONE instance's actual cx/cy (not a shared worst-case across three
   instances, since every instance now has its own path) so it cannot leave
   the viewBox or rise above the hill-mid crest (~y=80) - the per-instance
   safe window and actual extremes are noted in each keyframe's comment. */
@keyframes firefly-wander-g1 { /* #sharing, right gutter, 26s. Caps: inward<=18, outward<=10, vertical<=50 */
  0%   { transform: translate(0, 0); }
  15%  { transform: translate(-10px, -22px); }
  32%  { transform: translate(-18px, 6px); }
  50%  { transform: translate(-6px, 28px); }
  68%  { transform: translate(7px, -12px); }
  85%  { transform: translate(9px, -30px); }
  100% { transform: translate(0, 0); }
}
@keyframes firefly-wander-g2 { /* #privacy, right gutter, 29s */
  0%   { transform: translate(0, 0); }
  14%  { transform: translate(6px, -30px); }
  29%  { transform: translate(-12px, -42px); }
  43%  { transform: translate(-17px, -5px); }
  57%  { transform: translate(-8px, 20px); }
  71%  { transform: translate(4px, 38px); }
  86%  { transform: translate(10px, 10px); }
  100% { transform: translate(0, 0); }
}
@keyframes firefly-wander-g3 { /* #why, right gutter, 32s */
  0%   { transform: translate(0, 0); }
  20%  { transform: translate(-14px, 18px); }
  45%  { transform: translate(-6px, -35px); }
  70%  { transform: translate(8px, -15px); }
  90%  { transform: translate(9px, 32px); }
  100% { transform: translate(0, 0); }
}
@keyframes firefly-wander-g4 { /* #play, right gutter, 36s */
  0%   { transform: translate(0, 0); }
  12%  { transform: translate(4px, -16px); }
  25%  { transform: translate(-9px, -32px); }
  38%  { transform: translate(-18px, -8px); }
  50%  { transform: translate(-14px, 20px); }
  63%  { transform: translate(-2px, 42px); }
  75%  { transform: translate(7px, 25px); }
  88%  { transform: translate(10px, -6px); }
  100% { transform: translate(0, 0); }
}
@keyframes firefly-wander-g5 { /* #faq, left gutter, 41s - inward is +x here */
  0%   { transform: translate(0, 0); }
  16%  { transform: translate(8px, 24px); }
  33%  { transform: translate(17px, -4px); }
  50%  { transform: translate(5px, -28px); }
  67%  { transform: translate(-6px, 14px); }
  84%  { transform: translate(-9px, 30px); }
  100% { transform: translate(0, 0); }
}
@keyframes firefly-wander-g6 { /* #contact, left gutter, 46s - inward is +x here */
  0%   { transform: translate(0, 0); }
  14%  { transform: translate(-5px, 32px); }
  29%  { transform: translate(11px, 40px); }
  43%  { transform: translate(18px, 4px); }
  57%  { transform: translate(9px, -22px); }
  71%  { transform: translate(-3px, -40px); }
  86%  { transform: translate(-8px, -14px); }
  100% { transform: translate(0, 0); }
}
/* Meadow m1: cx180/cy120, 27s. Safe window horizontal [-160,+1240], vertical
   [-40 (y>=80 crest), +90 (y<=210 SVG floor)]. Actual extremes -70/+72, -38/+45. */
@keyframes firefly-wander-m1 {
  0%   { transform: translate(0, 0); }
  17%  { transform: translate(-38px, -30px); }
  34%  { transform: translate(-70px, 5px); }
  50%  { transform: translate(-22px, 45px); }
  66%  { transform: translate(35px, 38px); }
  83%  { transform: translate(72px, -38px); }
  100% { transform: translate(0, 0); }
}
/* Meadow m2: cx290/cy140, 30s. Safe window horizontal [-270,+1130], vertical
   [-60,+70]. Actual extremes -108/+112, -55/+58. */
@keyframes firefly-wander-m2 {
  0%   { transform: translate(0, 0); }
  13%  { transform: translate(45px, -55px); }
  27%  { transform: translate(112px, -18px); }
  40%  { transform: translate(90px, 40px); }
  55%  { transform: translate(10px, 58px); }
  70%  { transform: translate(-70px, 28px); }
  85%  { transform: translate(-108px, -25px); }
  100% { transform: translate(0, 0); }
}
/* Meadow m3: cx245/cy165, 33s. Safe window horizontal [-225,+1175], vertical
   [-85 (y>=80), +45 (y<=210)]. Actual extremes -118/+95, -55/+40. */
@keyframes firefly-wander-m3 {
  0%   { transform: translate(0, 0); }
  18%  { transform: translate(-60px, -32px); }
  36%  { transform: translate(-118px, 10px); }
  54%  { transform: translate(-45px, 40px); }
  72%  { transform: translate(60px, 25px); }
  90%  { transform: translate(95px, -55px); }
  100% { transform: translate(0, 0); }
}
/* Meadow m4: cx560/cy150, 34s. Safe window horizontal [-540,+860], vertical
   [-70,+60]. Actual extremes -132/+128, -58/+50, an 8-point "flower" loop. */
@keyframes firefly-wander-m4 {
  0%   { transform: translate(0, 0); }
  12%  { transform: translate(35px, -40px); }
  25%  { transform: translate(-30px, -58px); }
  37%  { transform: translate(-98px, -18px); }
  50%  { transform: translate(-132px, 30px); }
  62%  { transform: translate(-55px, 50px); }
  75%  { transform: translate(50px, 38px); }
  87%  { transform: translate(128px, -8px); }
  100% { transform: translate(0, 0); }
}
/* Meadow m5: cx750/cy115, 37s. Safe window horizontal [-730,+670], vertical
   [-35 (y>=80), +95]. Actual extremes -82/+80, -30/+62, a simple triangle-ish loop. */
@keyframes firefly-wander-m5 {
  0%   { transform: translate(0, 0); }
  22%  { transform: translate(80px, 40px); }
  48%  { transform: translate(20px, 62px); }
  74%  { transform: translate(-82px, 20px); }
  100% { transform: translate(0, 0); }
}
/* Meadow m6: cx900/cy160, 39s. Safe window horizontal [-880,+520], vertical
   [-80,+50]. Actual extremes -112/+95, -62/+42, a 7-point zigzag. */
@keyframes firefly-wander-m6 {
  0%   { transform: translate(0, 0); }
  14%  { transform: translate(-45px, -40px); }
  29%  { transform: translate(-112px, -8px); }
  43%  { transform: translate(-58px, 42px); }
  57%  { transform: translate(30px, 30px); }
  71%  { transform: translate(95px, -20px); }
  86%  { transform: translate(48px, -62px); }
  100% { transform: translate(0, 0); }
}
/* Meadow m7: cx1130/cy130, 42s. Safe window horizontal [-1110,+290], vertical
   [-50,+80]. Actual extremes -105/+78, -45/+65. */
@keyframes firefly-wander-m7 {
  0%   { transform: translate(0, 0); }
  16%  { transform: translate(-52px, 35px); }
  33%  { transform: translate(-105px, -12px); }
  50%  { transform: translate(-48px, -45px); }
  67%  { transform: translate(38px, -22px); }
  84%  { transform: translate(78px, 65px); }
  100% { transform: translate(0, 0); }
}
/* Meadow m8: cx1260/cy150, 44s. Safe window horizontal [-1240,+160], vertical
   [-70,+60] - right headroom is tight (only 160px), so this path is
   deliberately asymmetric, almost all leftward, mirroring the reasoning the
   old shared meadow-c path used. Actual extremes -128/+55, -58/+48. */
@keyframes firefly-wander-m8 {
  0%   { transform: translate(0, 0); }
  15%  { transform: translate(30px, -30px); }
  32%  { transform: translate(-25px, -58px); }
  50%  { transform: translate(-90px, -18px); }
  68%  { transform: translate(-128px, 30px); }
  85%  { transform: translate(-40px, 48px); }
  100% { transform: translate(0, 0); }
}
/* Meadow m9: cx1360/cy115, 48s. Safe window horizontal [-1340,+60], vertical
   [-35 (y>=80), +95] - right headroom is only 60px (near the SVG's right
   edge), so this path stays strongly leftward, the largest and slowest of
   the nine. Actual extremes -145/+55, -30/+70. */
@keyframes firefly-wander-m9 {
  0%   { transform: translate(0, 0); }
  13%  { transform: translate(38px, -22px); }
  27%  { transform: translate(55px, 22px); }
  42%  { transform: translate(-20px, 70px); }
  58%  { transform: translate(-95px, 40px); }
  73%  { transform: translate(-145px, -12px); }
  88%  { transform: translate(-62px, -30px); }
  100% { transform: translate(0, 0); }
}
/* Firefly flicker - AMENDED by owner ruling 2026-07-23 (supersedes doc 060
   B.4's original figures): the fast (3.2-4s), three-dips-per-cycle pattern
   read as too fast/flickery. Replaced with a calmer 7-11s rhythm - a soft
   fade down, at most one quick dip, then a soft fade back up - applied
   separately to the core and halo circles. Base rest values (0.95 core /
   0.3 halo) are unchanged from the pre-060 shipped static values - the
   keyframes dip below and return to those same numbers, so the
   reduced-motion static fallback still needs no new figures. The halo's dip
   trails the core's by a few percentage points so the glow bloom lingers a
   beat after the point source dims. */
@keyframes firefly-flicker-core {
  0%        { opacity: 0.95; }
  30%       { opacity: 0.78; }
  46%       { opacity: 0.22; }
  52%       { opacity: 0.85; }
  75%       { opacity: 0.9; }
  100%      { opacity: 0.95; }
}
@keyframes firefly-flicker-halo {
  0%        { opacity: 0.3; }
  30%       { opacity: 0.24; }
  49%       { opacity: 0.1; }
  55%       { opacity: 0.27; }
  78%       { opacity: 0.3; }
  100%      { opacity: 0.3; }
}
@keyframes scene-butterfly-bob {
  0%, 100% { transform: translate(0, 0); }
  50% { transform: translate(-6px, -10px); }
}
@keyframes scene-butterfly-wing-pulse {
  0%, 100% { transform: scaleX(1); }
  50% { transform: scaleX(0.5); }
}
@keyframes scene-bird-flit {
  0%, 100% { transform: translate(0, 0); }
  50% { transform: translate(10px, -8px); }
}
@keyframes scene-balloon-drift {
  0%, 100% { transform: translateY(0) rotate(-2deg); }
  50% { transform: translateY(-16px) rotate(2deg); }
}
@keyframes scene-foam-bob {
  0%, 100% { transform: translateY(0); }
  50% { transform: translateY(-3px); }
}
@keyframes scene-sailboat-sway {
  0%, 100% { transform: rotate(-2deg); }
  50% { transform: rotate(2deg); }
}
/* Hero phone reel crossfade (docs/051-external-redesign-merge.md section 4.3).
   elapsed(t) = (t + delay) mod 6s: screen 1 "on" 0-1.6s, screen 2 2-3.6s,
   screen 3 4-6s, then repeats - true crossfade: the incoming screen fades in
   over the same 0.36s window the outgoing one fades out (no all-invisible dead
   zone; the original 5%/22%/27% timing left the phone empty for a third of
   every cycle and was hotfixed by the main session). transform: scale() +
   opacity only. */
/* Sky parallax (owner-approved package, 2026-07-22): the hero's decor band
   drifts down slightly slower than the page scrolls, giving the sky depth.
   Applied to the BAND (which owns no other animation - its children keep
   their drift/twinkle untouched); the top gap it opens reveals the hero's
   own gradient, so the effect is seamless. Guards mirror the scroll-reveal:
   no-support and reduced-motion both see the ordinary static sky. Uses the
   `translate` longhand - transform stays free for future use. */
@supports (animation-timeline: scroll()) {
  @media (prefers-reduced-motion: no-preference) {
    .hero .scene-sky,
    .hero .hero-starfield {
      /* The 1s duration is a required formality: omitting it means 0s, and a
         0s animation "finishes" instantly instead of tracking the timeline
         (this shipped broken - the sky sat at its end offset). Progress-based
         timelines ignore the actual duration value. */
      animation: sky-parallax 1s linear both;
      animation-timeline: scroll(root);
      animation-range: 0px 640px;
    }
  }
}
@keyframes sky-parallax {
  from { translate: 0 0; }
  to { translate: 0 44px; }
}

/* Draggable kites (owner request, 2026-07-22 - js/playful.js): the hero
   kites accept the pointer on mouse devices only; grabbed, they follow the
   cursor (translate longhand, composing with their own sway transform);
   released, playful.js glides them home. They stay clipped to their sky
   band - sky creatures cannot leave the sky. */
@media (hover: hover) and (pointer: fine) {
  .hero .scene-kite {
    pointer-events: auto;
    cursor: grab;
    touch-action: none;
  }
  /* Unscoped from .hero: a grabbed kite is re-hosted at document.body
     (playful.js, 2026-07-23) so it truly rides above everything. */
  .scene-kite.is-dragged {
    cursor: grabbing;
    pointer-events: auto;
  }
}

/* Waves greet you (same package): the sea finale performs ONE gentle swell
   as it enters the viewport - a rise-and-settle arrival, not a loop. Base
   state is fully visible/static (reduced-motion + no-support correct by
   construction); children's foam-bob/sailboat animations are untouched
   (the container owns no other animation). */
@supports (animation-timeline: view()) {
  @media (prefers-reduced-motion: no-preference) {
    .scene-sea {
      /* 1s duration formality - see the sky-parallax note above. */
      animation: sea-greet 1s ease-out both;
      animation-timeline: view();
      animation-range: entry 0% entry 55%;
    }
  }
}
@keyframes sea-greet {
  from { translate: 0 26px; }
  to { translate: 0 0; }
}
@keyframes reel-cycle {
  0% { opacity: 0; transform: scale(0.96); }
  6% { opacity: 1; transform: scale(1); }
  33.3% { opacity: 1; transform: scale(1); }
  39.3% { opacity: 0; transform: scale(0.96); }
  100% { opacity: 0; transform: scale(0.96); }
}

/* Scroll reveal effect (DESIGN_SPEC.md section 13.3) - this keyframes rule is
   inert/ungated at file scope on purpose: it only takes effect once something
   sets animation-name: soft-reveal, which happens only inside the @supports +
   prefers-reduced-motion guard above (see the "Scroll reveal effect" block near
   the section 12 card-hover CSS). */
@keyframes soft-reveal {
  from { opacity: 0; translate: 0 12px; }
  to   { opacity: 1; translate: 0 0; }
}

/* ==========================================================================
   Comparison page (brief 006 / spec 063: website/docs/063-comparison-page-spec.md)
   /compare - honest feature comparison against a binder/spreadsheet and a generic
   family calendar app (owner ruling 2026-07-28: the competitor is not named).
   Page opts OUT of the scroll-reveal system (DESIGN_SPEC.md section 13, scoped
   to body.page-home already) and the card-hover tiers (section 12, scoped to
   .step/.promise-list li/.faq-item, none of which this page uses) - no extra
   CSS is needed to exclude either, both are already selector-scoped elsewhere.
   ========================================================================== */

/* Hero: plain, unbadged, no scene decoration (spec 063 section 5.1) - the page's
   own <main class="compare-main"> carries no .container class of its own (unlike
   the legal-page pattern), matching the homepage's own <main>, which never double-
   applies .container - each section here supplies its own .container div instead,
   including this one, to avoid doubling the sitewide 24px side gutter. */
/* Owner ruling 2026-07-28: the hero now carries the site's illustrated sky
   gradient instead of sitting on the flat page background - "so the sky
   gradient shows around the card" (see .compare-hero-card below).
   Deliberately still no sun/kite/motion: spec 063's "sober, serious first
   impression" reasoning for this claims-heavy page holds for the DECORATION
   layer, just not for the background wash itself, which the owner explicitly
   asked to bring back. Continues (does not restart) the gradient the
   .scene-header-band--sober strip directly above this section already ends
   on (that band runs --illus-sky-top -> --illus-sky-mid) - this rule picks
   up at --illus-sky-mid and carries on to --illus-sky-horizon, so the two
   bands read as one continuous sky rather than a visible color seam at the
   join. */
.compare-hero {
  padding: 56px 0 64px 0;
  text-align: center;
  background: linear-gradient(180deg, var(--illus-sky-mid) 0%, var(--illus-sky-horizon) 100%);
}
/* The content-raft card (owner ruling 2026-07-28, items 2-3): reuses the
   site's existing card idiom verbatim (.card's own surface/border/padding
   pattern) at the "hero surface" 18px radius DESIGN_SPEC.md section 7
   reserves for hero-level cards (--radius-hero), rather than inventing a new
   shape. Sized off .container (1280px) as the guide - wide and generous, not
   cramped - while still leaving the sky gradient visible at the sides on
   wider screens. Mobile gets edge-to-edge-feeling, tighter padding; padding
   and max-width both scale up at the existing 700px breakpoint.

   Promoted to a shared component (owner ruling 2026-07-28, round 2): the
   homepage's "Why Mira exists" founder-letter section asked for this exact
   same surface/border/radius/padding/width treatment, so rather than copy
   the declarations onto a second selector, .compare-hero-card is generalized
   into a shared class, .prose-card, and .compare-hero-card becomes an alias
   of it (same rule, comma-joined selector) - zero duplicated CSS, and
   compare.html's existing markup/class name is untouched. .prose-card is the
   new class the founder-note section adopts (see the "Why Mira exists"
   comment further up this file, near .founder-note p, for why this also
   satisfies the owner's separate "make it wider" ruling for that section
   without any extra sizing rule of its own - adopting this shared card's
   900/980px measure IS the widening, since the section's old cap was a
   narrower 740px). */
.compare-hero-card,
.prose-card {
  background: var(--surface);
  border: 1px solid var(--line);
  border-radius: var(--radius-hero);
  padding: 32px 24px;
  max-width: 900px;
  margin: 0 auto;
}
@media (min-width: 700px) {
  .compare-hero-card,
  .prose-card {
    padding: 56px 64px;
    max-width: 980px;
  }
}
/* .lede has no bare/global rule in this file (only .hero .lede, scoped to the
   homepage's own .hero, which this page does not use) - .compare-hero-lede
   carries the equivalent properties directly rather than relying on a rule
   that would not otherwise apply here. Measure widened (owner ruling
   2026-07-28, item 2) from a narrow 640px column to a much wider one, now
   bounded by the card's own generous width rather than its own tight cap. */
.compare-hero-lede {
  font-size: 1.2rem;
  max-width: 820px;
  margin: 0 auto;
  color: var(--fg2);
}

/* Comparison table section (spec 063 section 5.2) */
.compare-verified {
  font-size: 0.95rem;
  color: var(--muted);
  margin-top: -8px;
}
.compare-concession {
  background: var(--badge-neutral-bg);
  color: var(--fg2);
  border-radius: var(--radius-card);
  padding: 20px 24px;
  margin-top: 24px;
}
.compare-trademark-note {
  font-size: 0.85rem;
  color: var(--muted);
  margin-top: 16px;
}

/* The comparison table component itself (spec 063 section 5.3/5.5). Desktop
   table >=700px (matching the site's existing scene-header-band breakpoint,
   not a new value); stacked, bordered row-cards below 700px. Explicit ARIA
   table roles are baked into the HTML (role="table"/"rowgroup"/"row"/
   "columnheader"/"rowheader"/"cell") so real table semantics survive the
   <700px display:block reflow - a documented browser gotcha where implicit
   ARIA table roles are dropped the moment display leaves table/table-row/
   table-cell, in every major browser. */
/* Owner ruling 2026-07-29: the table did not correspond to the overall page
   width - the wrap was capped at 960px while .container (the rest of the
   page's own rhythm) is 1280px, and the resulting narrow second/third/fourth
   columns forced too many line breaks in the answer cells. The 960px cap is
   removed entirely (not raised) so the wrap simply fills its parent
   .container at whatever width that is - matching the page like every other
   full-width section already does. Nothing else on this page shared that
   960px value (checked: intro copy, cards, and the closing CTA each have
   their own, smaller, independent max-widths), so removing it here is
   scoped to the table alone, per the ruling's own instruction. */
.compare-table-wrap {
  margin: 24px 0 0;
  overflow: hidden; /* clips the table's own border-radius corners */
  border-radius: var(--radius-card);
  border: 1px solid var(--line);
}
.compare-table {
  width: 100%;
  border-collapse: collapse;
  background: var(--surface);
}
.compare-table caption { position: absolute; width: 1px; height: 1px; overflow: hidden; clip: rect(0 0 0 0); white-space: nowrap; }
.compare-table th, .compare-table td { padding: 16px; text-align: left; border-bottom: 1px solid var(--line); vertical-align: top; }
.compare-table tbody tr:last-child th, .compare-table tbody tr:last-child td { border-bottom: none; }
.compare-table thead th { font-family: "Geist", system-ui, sans-serif; font-weight: 600; font-size: 1.05rem; color: var(--fg); }
/* Column-width follow-up to the same ruling: once the wrap could grow past
   960px, the first ("Care task") column - the only one with no width rule
   of its own - grew disproportionately at wide desktop widths, taking space
   from the 3 answer columns the owner specifically said read too cramped.
   Constrained to ~28% (within the owner's suggested 26-30% range) on both
   the header cell and the row-header cells (the two `th` roles that make up
   this column - `scope="col"` in <thead>, `scope="row"` in each <tbody>
   row); the 3 answer columns are left with no explicit width so the
   browser's normal table auto-layout splits the remaining ~72% between
   them based on content, same mechanism as before this change. */
@media (min-width: 700px) {
  .compare-table thead .compare-col-dimension,
  .compare-table tbody th[scope="row"] {
    width: 28%;
  }
}
.compare-col-mira { background: var(--accent-soft); }
.compare-table thead .compare-col-mira { color: var(--accent-dark); }
.compare-mark { display: inline-flex; align-items: center; gap: 6px; padding: 6px 10px; border-radius: var(--radius-badge); font-weight: 600; font-size: 0.95rem; }
.compare-mark svg { width: 16px; height: 16px; flex-shrink: 0; }
.compare-mark--yes { background: var(--badge-grass-bg); color: var(--badge-grass-fg); }
.compare-mark--yes svg { stroke: var(--badge-grass-fg); }
.compare-mark--partial { background: var(--badge-gold-bg); color: var(--badge-gold-fg); }
.compare-mark--partial svg { stroke: var(--badge-gold-fg); fill: var(--badge-gold-fg); }
.compare-mark--no { background: var(--badge-neutral-bg); color: var(--badge-neutral-fg); }
.compare-mark--no svg { stroke: var(--badge-neutral-fg); }
.compare-mark-note { display: block; margin-top: 6px; font-size: 0.9rem; color: var(--fg2); line-height: 1.5; }

/* >=700px: ordinary table layout (explicit, defensive - browsers already default
   to this, stated for clarity alongside the override below) */
@media (min-width: 700px) {
  .compare-table, .compare-table thead, .compare-table tbody, .compare-table tr,
  .compare-table th, .compare-table td { display: revert; }
}

/* <700px: reflow to stacked row-cards. ARIA roles in the HTML keep real table
   semantics for screen readers regardless of this display change. */
@media (max-width: 699px) {
  .compare-table-wrap { border: none; border-radius: 0; overflow: visible; }
  .compare-table, .compare-table tbody, .compare-table tr, .compare-table th, .compare-table td { display: block; width: 100%; }
  .compare-table thead { position: absolute; width: 1px; height: 1px; overflow: hidden; clip: rect(0 0 0 0); white-space: nowrap; }
  .compare-table tbody tr { border: 1px solid var(--line); border-radius: var(--radius-card); margin-bottom: 16px; overflow: hidden; }
  .compare-table tbody tr th[scope="row"] { background: var(--bg); font-weight: 600; border-bottom: 1px solid var(--line); padding: 14px 16px; }
  .compare-table tbody td { border-bottom: 1px solid var(--line); }
  .compare-table tbody td:last-child { border-bottom: none; }
  .compare-table tbody td::before { content: attr(data-label); display: block; font-weight: 600; font-size: 0.85rem; color: var(--fg2); margin-bottom: 6px; }
}

/* Closing CTA (spec 063 section 5.7): a centered, narrow closing block. */
.compare-cta-inner {
  max-width: 640px;
  text-align: center;
}
