/* ============================================================================
   Polylingo B2C visual system  —  Fluent 2 design language (tokens-only)

   Loaded by app/templates/polytxt/base_polytxt.html and inherited across every
   B2C page. See docs/adr/0021-b2c-visual-system-fluent-2.md for scope and
   rationale.

   Variable names mirror Fluent v9 token names where reasonable
   (--colorBrandBackground, --spacingHorizontalM, --shadow8, etc.) so a
   future ADR introducing @fluentui/react-components can adopt this CSS
   without renaming.

   Structure:
     0. Self-hosted Noto Sans family    (Task #G Phase 2 / ADR 0047)
     1. Global tokens   (full brand and neutral ramps, spacing, radius,
                        shadow, motion, stroke, type)
     2. Light alias     (default :root)
     [Section 3 — Dark theme — REMOVED 2026-05-30. Dark mode deferred
      pending the proper design-system pass. See ADR 0021 editorial
      amendment 2026-05-30.]
     4. Reset
     5. Base typography
     6. Page chrome     (.page, .page-header, .page-title)
     7. Header / nav    (.app-header)
     8. Buttons         (.btn variants)
     9. Form fields     (.form-field, validation, errors)
    10. Cards           (.card, .surface)
    11. Badges          (.badge variants)
    12. Alerts / messages
    13. Tables          (.data-table)
    14. Utilities       (sparingly: spacing, text colour)
    15. Focus + reduced motion accessibility
   ============================================================================ */

/* ============================================================================
   0. Self-hosted Noto Sans family — Task #G Phase 2 / ADR 0047
   --------------------------------------------------------------------------
   We declare one @font-face block per family / subset / weight. Browsers
   only DOWNLOAD a file when text rendering touches its `unicode-range`
   block — so a UK English visitor pays only the Latin Noto cost (~30 KB
   per weight), an Arabic visitor pays the Latin + Arabic cost, and a
   Chinese visitor pays the Latin + Simplified-Chinese cost (~1 MB per
   weight). Per-language scripts (Arabic, Hebrew, Devanagari, Bengali,
   Tamil, Gurmukhi, Gujarati, Sinhala, Ethiopic) each ship a single
   subset.

   Weights: 400 (regular) + 600 (semibold) + 700 (bold) — covers Fluent 2's
   typography ramp without weight-set explosion.

   Source: fontsource.org pre-built woff2 (SIL OFL). Self-hosted (NOT
   Google Fonts CDN) per ADR 0047 + ICO guidance on third-party font
   loading. Files live under polytxt/fonts/noto/.

   CJK caveat: the SC, TC, JP and KR faces all ship (kana and Hangul
   render), but they are still FOLDED under the shared ``Noto Sans``
   family, so the last-declared face wins the shared Han unicode-range
   for everyone — ja/ko pages can paint some ideographs with
   wrong-region glyphs (Han unification). Splitting them into distinct
   families with bare ``:lang()`` overrides is the open ADR 0080 §4
   (UF) work; do not record it as done.

   font-display: swap — page renders immediately with the system
   fallback (the next font in the stack), then swaps to Noto when the
   woff2 arrives. No invisible-text flash.
   ============================================================================ */

/* — Noto Sans (Latin) — */
@font-face {
    font-family: 'Noto Sans';
    font-style: normal;
    font-weight: 400;
    font-display: swap;
    src: url('fonts/noto/sans/latin-400-normal.woff2') format('woff2');
    unicode-range: U+0000-00FF, U+0131, U+0152-0153, U+02BB-02BC, U+02C6, U+02DA, U+02DC, U+0304, U+0308, U+0329, U+2000-206F, U+2074, U+20AC, U+2122, U+2191, U+2193, U+2212, U+2215, U+FEFF, U+FFFD;
}
@font-face {
    font-family: 'Noto Sans';
    font-style: normal;
    font-weight: 600;
    font-display: swap;
    src: url('fonts/noto/sans/latin-600-normal.woff2') format('woff2');
    unicode-range: U+0000-00FF, U+0131, U+0152-0153, U+02BB-02BC, U+02C6, U+02DA, U+02DC, U+0304, U+0308, U+0329, U+2000-206F, U+2074, U+20AC, U+2122, U+2191, U+2193, U+2212, U+2215, U+FEFF, U+FFFD;
}
@font-face {
    font-family: 'Noto Sans';
    font-style: normal;
    font-weight: 700;
    font-display: swap;
    src: url('fonts/noto/sans/latin-700-normal.woff2') format('woff2');
    unicode-range: U+0000-00FF, U+0131, U+0152-0153, U+02BB-02BC, U+02C6, U+02DA, U+02DC, U+0304, U+0308, U+0329, U+2000-206F, U+2074, U+20AC, U+2122, U+2191, U+2193, U+2212, U+2215, U+FEFF, U+FFFD;
}

/* — Noto Sans (Cyrillic) — */
@font-face {
    font-family: 'Noto Sans';
    font-style: normal;
    font-weight: 400;
    font-display: swap;
    src: url('fonts/noto/sans/cyrillic-400-normal.woff2') format('woff2');
    unicode-range: U+0301, U+0400-045F, U+0490-0491, U+04B0-04B1, U+2116;
}
@font-face {
    font-family: 'Noto Sans';
    font-style: normal;
    font-weight: 600;
    font-display: swap;
    src: url('fonts/noto/sans/cyrillic-600-normal.woff2') format('woff2');
    unicode-range: U+0301, U+0400-045F, U+0490-0491, U+04B0-04B1, U+2116;
}
@font-face {
    font-family: 'Noto Sans';
    font-style: normal;
    font-weight: 700;
    font-display: swap;
    src: url('fonts/noto/sans/cyrillic-700-normal.woff2') format('woff2');
    unicode-range: U+0301, U+0400-045F, U+0490-0491, U+04B0-04B1, U+2116;
}

/* — Noto Sans (Greek) — */
@font-face {
    font-family: 'Noto Sans';
    font-style: normal;
    font-weight: 400;
    font-display: swap;
    src: url('fonts/noto/sans/greek-400-normal.woff2') format('woff2');
    unicode-range: U+0370-03FF;
}
@font-face {
    font-family: 'Noto Sans';
    font-style: normal;
    font-weight: 600;
    font-display: swap;
    src: url('fonts/noto/sans/greek-600-normal.woff2') format('woff2');
    unicode-range: U+0370-03FF;
}
@font-face {
    font-family: 'Noto Sans';
    font-style: normal;
    font-weight: 700;
    font-display: swap;
    src: url('fonts/noto/sans/greek-700-normal.woff2') format('woff2');
    unicode-range: U+0370-03FF;
}

/* — Noto Sans (Latin Extended) — European diacritics for the 42-language
   UI expansion (Polish ą ć ę ł ż, Romanian ă î ş ţ, Turkish ğ ş, Czech
   ě ř ů, etc.). Shares the 'Noto Sans' family so it cascades under the
   first entry of --fontFamilyBase — no token change needed. Task i18n
   42-langs / ADR 0047. */
@font-face {
    font-family: 'Noto Sans';
    font-style: normal;
    font-weight: 400;
    font-display: swap;
    src: url('fonts/noto/sans/latin-ext-400-normal.woff2') format('woff2');
    unicode-range: U+0100-02BA, U+02BD-02C5, U+02C7-02CC, U+02CE-02D7, U+02DD-02FF, U+0304, U+0308, U+0329, U+1D00-1DBF, U+1E00-1E9F, U+1EF2-1EFF, U+2020, U+20A0-20AB, U+20AD-20C0, U+2113, U+2C60-2C7F, U+A720-A7FF;
}
@font-face {
    font-family: 'Noto Sans';
    font-style: normal;
    font-weight: 600;
    font-display: swap;
    src: url('fonts/noto/sans/latin-ext-600-normal.woff2') format('woff2');
    unicode-range: U+0100-02BA, U+02BD-02C5, U+02C7-02CC, U+02CE-02D7, U+02DD-02FF, U+0304, U+0308, U+0329, U+1D00-1DBF, U+1E00-1E9F, U+1EF2-1EFF, U+2020, U+20A0-20AB, U+20AD-20C0, U+2113, U+2C60-2C7F, U+A720-A7FF;
}
@font-face {
    font-family: 'Noto Sans';
    font-style: normal;
    font-weight: 700;
    font-display: swap;
    src: url('fonts/noto/sans/latin-ext-700-normal.woff2') format('woff2');
    unicode-range: U+0100-02BA, U+02BD-02C5, U+02C7-02CC, U+02CE-02D7, U+02DD-02FF, U+0304, U+0308, U+0329, U+1D00-1DBF, U+1E00-1E9F, U+1EF2-1EFF, U+2020, U+20A0-20AB, U+20AD-20C0, U+2113, U+2C60-2C7F, U+A720-A7FF;
}

/* — Noto Sans (Vietnamese) — tone-mark composed vowels (ế ộ ữ ơ ằ …).
   Shares the 'Noto Sans' family. Task i18n 42-langs / ADR 0047. */
@font-face {
    font-family: 'Noto Sans';
    font-style: normal;
    font-weight: 400;
    font-display: swap;
    src: url('fonts/noto/sans/vietnamese-400-normal.woff2') format('woff2');
    unicode-range: U+0102-0103, U+0110-0111, U+0128-0129, U+0168-0169, U+01A0-01A1, U+01AF-01B0, U+0300-0301, U+0303-0304, U+0308-0309, U+0323, U+0329, U+1EA0-1EF9, U+20AB;
}
@font-face {
    font-family: 'Noto Sans';
    font-style: normal;
    font-weight: 600;
    font-display: swap;
    src: url('fonts/noto/sans/vietnamese-600-normal.woff2') format('woff2');
    unicode-range: U+0102-0103, U+0110-0111, U+0128-0129, U+0168-0169, U+01A0-01A1, U+01AF-01B0, U+0300-0301, U+0303-0304, U+0308-0309, U+0323, U+0329, U+1EA0-1EF9, U+20AB;
}
@font-face {
    font-family: 'Noto Sans';
    font-style: normal;
    font-weight: 700;
    font-display: swap;
    src: url('fonts/noto/sans/vietnamese-700-normal.woff2') format('woff2');
    unicode-range: U+0102-0103, U+0110-0111, U+0128-0129, U+0168-0169, U+01A0-01A1, U+01AF-01B0, U+0300-0301, U+0303-0304, U+0308-0309, U+0323, U+0329, U+1EA0-1EF9, U+20AB;
}

/* — Noto Sans Arabic — */
@font-face {
    font-family: 'Noto Sans Arabic';
    font-style: normal;
    font-weight: 400;
    font-display: swap;
    src: url('fonts/noto/sans-arabic/arabic-400-normal.woff2') format('woff2');
    unicode-range: U+0600-06FF, U+0750-077F, U+0870-088E, U+0890-0891, U+0898-08E1, U+08E3-08FF, U+200C-200E, U+2010-2011, U+204F, U+2E41, U+FB50-FDFF, U+FE70-FEFC;
}
@font-face {
    font-family: 'Noto Sans Arabic';
    font-style: normal;
    font-weight: 600;
    font-display: swap;
    src: url('fonts/noto/sans-arabic/arabic-600-normal.woff2') format('woff2');
    unicode-range: U+0600-06FF, U+0750-077F, U+0870-088E, U+0890-0891, U+0898-08E1, U+08E3-08FF, U+200C-200E, U+2010-2011, U+204F, U+2E41, U+FB50-FDFF, U+FE70-FEFC;
}
@font-face {
    font-family: 'Noto Sans Arabic';
    font-style: normal;
    font-weight: 700;
    font-display: swap;
    src: url('fonts/noto/sans-arabic/arabic-700-normal.woff2') format('woff2');
    unicode-range: U+0600-06FF, U+0750-077F, U+0870-088E, U+0890-0891, U+0898-08E1, U+08E3-08FF, U+200C-200E, U+2010-2011, U+204F, U+2E41, U+FB50-FDFF, U+FE70-FEFC;
}

/* — Noto Sans Hebrew — */
@font-face {
    font-family: 'Noto Sans Hebrew';
    font-style: normal;
    font-weight: 400;
    font-display: swap;
    src: url('fonts/noto/sans-hebrew/hebrew-400-normal.woff2') format('woff2');
    unicode-range: U+0307-0308, U+0590-05FF, U+200C-2010, U+20AA, U+25CC, U+FB1D-FB4F;
}
@font-face {
    font-family: 'Noto Sans Hebrew';
    font-style: normal;
    font-weight: 600;
    font-display: swap;
    src: url('fonts/noto/sans-hebrew/hebrew-600-normal.woff2') format('woff2');
    unicode-range: U+0307-0308, U+0590-05FF, U+200C-2010, U+20AA, U+25CC, U+FB1D-FB4F;
}
@font-face {
    font-family: 'Noto Sans Hebrew';
    font-style: normal;
    font-weight: 700;
    font-display: swap;
    src: url('fonts/noto/sans-hebrew/hebrew-700-normal.woff2') format('woff2');
    unicode-range: U+0307-0308, U+0590-05FF, U+200C-2010, U+20AA, U+25CC, U+FB1D-FB4F;
}

/* — Noto Sans Devanagari — */
@font-face {
    font-family: 'Noto Sans Devanagari';
    font-style: normal;
    font-weight: 400;
    font-display: swap;
    src: url('fonts/noto/sans-devanagari/devanagari-400-normal.woff2') format('woff2');
    unicode-range: U+0900-097F, U+1CD0-1CF6, U+1CF8-1CF9, U+200C-200D, U+20A8, U+20B9, U+25CC, U+A830-A839, U+A8E0-A8FB;
}
@font-face {
    font-family: 'Noto Sans Devanagari';
    font-style: normal;
    font-weight: 600;
    font-display: swap;
    src: url('fonts/noto/sans-devanagari/devanagari-600-normal.woff2') format('woff2');
    unicode-range: U+0900-097F, U+1CD0-1CF6, U+1CF8-1CF9, U+200C-200D, U+20A8, U+20B9, U+25CC, U+A830-A839, U+A8E0-A8FB;
}
@font-face {
    font-family: 'Noto Sans Devanagari';
    font-style: normal;
    font-weight: 700;
    font-display: swap;
    src: url('fonts/noto/sans-devanagari/devanagari-700-normal.woff2') format('woff2');
    unicode-range: U+0900-097F, U+1CD0-1CF6, U+1CF8-1CF9, U+200C-200D, U+20A8, U+20B9, U+25CC, U+A830-A839, U+A8E0-A8FB;
}

/* — Noto Sans Bengali — */
@font-face {
    font-family: 'Noto Sans Bengali';
    font-style: normal;
    font-weight: 400;
    font-display: swap;
    src: url('fonts/noto/sans-bengali/bengali-400-normal.woff2') format('woff2');
    unicode-range: U+0951-0952, U+0964-0965, U+0980-09FE, U+1CD0, U+1CD2, U+1CD5-1CD6, U+1CD8, U+1CE1, U+1CEA, U+1CED, U+1CF2, U+1CF5-1CF7, U+200C-200D, U+20B9, U+25CC, U+A8F1;
}
@font-face {
    font-family: 'Noto Sans Bengali';
    font-style: normal;
    font-weight: 600;
    font-display: swap;
    src: url('fonts/noto/sans-bengali/bengali-600-normal.woff2') format('woff2');
    unicode-range: U+0951-0952, U+0964-0965, U+0980-09FE, U+1CD0, U+1CD2, U+1CD5-1CD6, U+1CD8, U+1CE1, U+1CEA, U+1CED, U+1CF2, U+1CF5-1CF7, U+200C-200D, U+20B9, U+25CC, U+A8F1;
}
@font-face {
    font-family: 'Noto Sans Bengali';
    font-style: normal;
    font-weight: 700;
    font-display: swap;
    src: url('fonts/noto/sans-bengali/bengali-700-normal.woff2') format('woff2');
    unicode-range: U+0951-0952, U+0964-0965, U+0980-09FE, U+1CD0, U+1CD2, U+1CD5-1CD6, U+1CD8, U+1CE1, U+1CEA, U+1CED, U+1CF2, U+1CF5-1CF7, U+200C-200D, U+20B9, U+25CC, U+A8F1;
}

/* — Noto Sans Tamil — */
@font-face {
    font-family: 'Noto Sans Tamil';
    font-style: normal;
    font-weight: 400;
    font-display: swap;
    src: url('fonts/noto/sans-tamil/tamil-400-normal.woff2') format('woff2');
    unicode-range: U+0964-0965, U+0B82-0BFA, U+200C-200D, U+20B9, U+25CC;
}
@font-face {
    font-family: 'Noto Sans Tamil';
    font-style: normal;
    font-weight: 600;
    font-display: swap;
    src: url('fonts/noto/sans-tamil/tamil-600-normal.woff2') format('woff2');
    unicode-range: U+0964-0965, U+0B82-0BFA, U+200C-200D, U+20B9, U+25CC;
}
@font-face {
    font-family: 'Noto Sans Tamil';
    font-style: normal;
    font-weight: 700;
    font-display: swap;
    src: url('fonts/noto/sans-tamil/tamil-700-normal.woff2') format('woff2');
    unicode-range: U+0964-0965, U+0B82-0BFA, U+200C-200D, U+20B9, U+25CC;
}

/* — Noto Sans Gurmukhi — */
@font-face {
    font-family: 'Noto Sans Gurmukhi';
    font-style: normal;
    font-weight: 400;
    font-display: swap;
    src: url('fonts/noto/sans-gurmukhi/gurmukhi-400-normal.woff2') format('woff2');
    unicode-range: U+0951-0952, U+0964-0965, U+0A01-0A75, U+200C-200D, U+20B9, U+25CC, U+262C, U+A830-A839;
}
@font-face {
    font-family: 'Noto Sans Gurmukhi';
    font-style: normal;
    font-weight: 600;
    font-display: swap;
    src: url('fonts/noto/sans-gurmukhi/gurmukhi-600-normal.woff2') format('woff2');
    unicode-range: U+0951-0952, U+0964-0965, U+0A01-0A75, U+200C-200D, U+20B9, U+25CC, U+262C, U+A830-A839;
}
@font-face {
    font-family: 'Noto Sans Gurmukhi';
    font-style: normal;
    font-weight: 700;
    font-display: swap;
    src: url('fonts/noto/sans-gurmukhi/gurmukhi-700-normal.woff2') format('woff2');
    unicode-range: U+0951-0952, U+0964-0965, U+0A01-0A75, U+200C-200D, U+20B9, U+25CC, U+262C, U+A830-A839;
}

/* — Noto Sans Gujarati — */
@font-face {
    font-family: 'Noto Sans Gujarati';
    font-style: normal;
    font-weight: 400;
    font-display: swap;
    src: url('fonts/noto/sans-gujarati/gujarati-400-normal.woff2') format('woff2');
    unicode-range: U+0964-0965, U+0A80-0AFF, U+200C-200D, U+20B9, U+25CC, U+A830-A839;
}
@font-face {
    font-family: 'Noto Sans Gujarati';
    font-style: normal;
    font-weight: 600;
    font-display: swap;
    src: url('fonts/noto/sans-gujarati/gujarati-600-normal.woff2') format('woff2');
    unicode-range: U+0964-0965, U+0A80-0AFF, U+200C-200D, U+20B9, U+25CC, U+A830-A839;
}
@font-face {
    font-family: 'Noto Sans Gujarati';
    font-style: normal;
    font-weight: 700;
    font-display: swap;
    src: url('fonts/noto/sans-gujarati/gujarati-700-normal.woff2') format('woff2');
    unicode-range: U+0964-0965, U+0A80-0AFF, U+200C-200D, U+20B9, U+25CC, U+A830-A839;
}

/* — Noto Sans Sinhala — */
@font-face {
    font-family: 'Noto Sans Sinhala';
    font-style: normal;
    font-weight: 400;
    font-display: swap;
    src: url('fonts/noto/sans-sinhala/sinhala-400-normal.woff2') format('woff2');
    unicode-range: U+0964-0965, U+0D81-0DF4, U+1CF2, U+200C-200D, U+25CC, U+111E1-111F4;
}
@font-face {
    font-family: 'Noto Sans Sinhala';
    font-style: normal;
    font-weight: 600;
    font-display: swap;
    src: url('fonts/noto/sans-sinhala/sinhala-600-normal.woff2') format('woff2');
    unicode-range: U+0964-0965, U+0D81-0DF4, U+1CF2, U+200C-200D, U+25CC, U+111E1-111F4;
}
@font-face {
    font-family: 'Noto Sans Sinhala';
    font-style: normal;
    font-weight: 700;
    font-display: swap;
    src: url('fonts/noto/sans-sinhala/sinhala-700-normal.woff2') format('woff2');
    unicode-range: U+0964-0965, U+0D81-0DF4, U+1CF2, U+200C-200D, U+25CC, U+111E1-111F4;
}

/* — Noto Sans Ethiopic (Amharic, Tigrinya) — */
@font-face {
    font-family: 'Noto Sans Ethiopic';
    font-style: normal;
    font-weight: 400;
    font-display: swap;
    src: url('fonts/noto/sans-ethiopic/ethiopic-400-normal.woff2') format('woff2');
    unicode-range: U+1200-1399, U+2D80-2DDE, U+AB01-AB2E;
}
@font-face {
    font-family: 'Noto Sans Ethiopic';
    font-style: normal;
    font-weight: 600;
    font-display: swap;
    src: url('fonts/noto/sans-ethiopic/ethiopic-600-normal.woff2') format('woff2');
    unicode-range: U+1200-1399, U+2D80-2DDE, U+AB01-AB2E;
}
@font-face {
    font-family: 'Noto Sans Ethiopic';
    font-style: normal;
    font-weight: 700;
    font-display: swap;
    src: url('fonts/noto/sans-ethiopic/ethiopic-700-normal.woff2') format('woff2');
    unicode-range: U+1200-1399, U+2D80-2DDE, U+AB01-AB2E;
}

/* — Noto Sans SC (Simplified Chinese — CJK Unified Ideographs) — */
@font-face {
    font-family: 'Noto Sans SC';
    font-style: normal;
    font-weight: 400;
    font-display: swap;
    src: url('fonts/noto/sans-sc/chinese-simplified-400-normal.woff2') format('woff2');
    unicode-range: U+4E00-9FFF, U+3000-303F, U+FF00-FFEF;
}
@font-face {
    font-family: 'Noto Sans SC';
    font-style: normal;
    font-weight: 600;
    font-display: swap;
    src: url('fonts/noto/sans-sc/chinese-simplified-600-normal.woff2') format('woff2');
    unicode-range: U+4E00-9FFF, U+3000-303F, U+FF00-FFEF;
}
@font-face {
    font-family: 'Noto Sans SC';
    font-style: normal;
    font-weight: 700;
    font-display: swap;
    src: url('fonts/noto/sans-sc/chinese-simplified-700-normal.woff2') format('woff2');
    unicode-range: U+4E00-9FFF, U+3000-303F, U+FF00-FFEF;
}

/* — Noto Sans JP / KR / TC — each its OWN family (ADR 0080 §4, UF).
   Regional Han glyph selection: see the --fontFamilyCJK token below.

   These three USED to declare `font-family: 'Noto Sans'` — the same family
   as the base Latin face — while both the JP and the TC face claimed the Han
   range U+4E00-9FFF at weight 400. Per CSS Fonts 3 §4.5, when rules in ONE
   family have overlapping unicode-range at the same style+weight, the LAST
   declared wins. TC was last, so Traditional-Chinese glyphs were served to
   Simplified-Chinese, Japanese AND Korean readers alike; and because family
   'Noto Sans' had no Han-covering face above weight 400, bold headings fell
   through to 'Noto Sans SC' and rendered Simplified. One page could paint
   body text in one region's letterforms and its headings in another's.

   Giving each face its own family removes the intra-family fight entirely:
   the winner is now decided by font-family LIST order, which is per-element
   and therefore selectable with :lang(). `ops.tests_font_family_overlap_invariants`
   fails the build if any family is ever re-folded like this again. */
@font-face {
    font-family: 'Noto Sans JP';
    font-style: normal;
    font-weight: 400;
    font-display: swap;
    src: url('fonts/noto/sans-jp/japanese-400-normal.woff2') format('woff2');
    unicode-range: U+3000-303F, U+3040-309F, U+30A0-30FF, U+3400-4DBF, U+4E00-9FFF, U+FF00-FFEF, U+20000-2A6DF;
}
@font-face {
    font-family: 'Noto Sans KR';
    font-style: normal;
    font-weight: 400;
    font-display: swap;
    src: url('fonts/noto/sans-kr/korean-400-normal.woff2') format('woff2');
    unicode-range: U+1100-11FF, U+3000-303F, U+3130-318F, U+A960-A97F, U+AC00-D7A3, U+D7B0-D7FF;
}
@font-face {
    font-family: 'Noto Sans TC';
    font-style: normal;
    font-weight: 400;
    font-display: swap;
    src: url('fonts/noto/sans-tc/chinese-traditional-400-normal.woff2') format('woff2');
    unicode-range: U+3000-303F, U+3400-4DBF, U+4E00-9FFF, U+F900-FAFF, U+FF00-FFEF;
}

/* ── Dark-language font phase (2026-07-23, ADR 0047): 8 per-script Noto
   families for the seeded dark scripts that had no shipped face. Each
   downloads only when its unicode-range appears on the page. ── */
/* — Noto Sans Thai — */
@font-face {
    font-family: 'Noto Sans Thai';
    font-style: normal;
    font-weight: 400;
    font-display: swap;
    src: url('fonts/noto/sans-thai/thai-400-normal.woff2') format('woff2');
    unicode-range: U+02D7,U+0303,U+0331,U+0E01-0E5B,U+200C-200D,U+25CC;
}
@font-face {
    font-family: 'Noto Sans Thai';
    font-style: normal;
    font-weight: 600;
    font-display: swap;
    src: url('fonts/noto/sans-thai/thai-600-normal.woff2') format('woff2');
    unicode-range: U+02D7,U+0303,U+0331,U+0E01-0E5B,U+200C-200D,U+25CC;
}
@font-face {
    font-family: 'Noto Sans Thai';
    font-style: normal;
    font-weight: 700;
    font-display: swap;
    src: url('fonts/noto/sans-thai/thai-700-normal.woff2') format('woff2');
    unicode-range: U+02D7,U+0303,U+0331,U+0E01-0E5B,U+200C-200D,U+25CC;
}
/* — Noto Sans Telugu — */
@font-face {
    font-family: 'Noto Sans Telugu';
    font-style: normal;
    font-weight: 400;
    font-display: swap;
    src: url('fonts/noto/sans-telugu/telugu-400-normal.woff2') format('woff2');
    unicode-range: U+0951-0952,U+0964-0965,U+0C00-0C7F,U+1CDA,U+1CF2,U+200C-200D,U+25CC;
}
@font-face {
    font-family: 'Noto Sans Telugu';
    font-style: normal;
    font-weight: 600;
    font-display: swap;
    src: url('fonts/noto/sans-telugu/telugu-600-normal.woff2') format('woff2');
    unicode-range: U+0951-0952,U+0964-0965,U+0C00-0C7F,U+1CDA,U+1CF2,U+200C-200D,U+25CC;
}
@font-face {
    font-family: 'Noto Sans Telugu';
    font-style: normal;
    font-weight: 700;
    font-display: swap;
    src: url('fonts/noto/sans-telugu/telugu-700-normal.woff2') format('woff2');
    unicode-range: U+0951-0952,U+0964-0965,U+0C00-0C7F,U+1CDA,U+1CF2,U+200C-200D,U+25CC;
}
/* — Noto Sans Kannada — */
@font-face {
    font-family: 'Noto Sans Kannada';
    font-style: normal;
    font-weight: 400;
    font-display: swap;
    src: url('fonts/noto/sans-kannada/kannada-400-normal.woff2') format('woff2');
    unicode-range: U+0951-0952,U+0964-0965,U+0C80-0CF3,U+1CD0,U+1CD2-1CD3,U+1CDA,U+1CF2,U+1CF4,U+200C-200D,U+20B9,U+25CC,U+A830-A835;
}
@font-face {
    font-family: 'Noto Sans Kannada';
    font-style: normal;
    font-weight: 600;
    font-display: swap;
    src: url('fonts/noto/sans-kannada/kannada-600-normal.woff2') format('woff2');
    unicode-range: U+0951-0952,U+0964-0965,U+0C80-0CF3,U+1CD0,U+1CD2-1CD3,U+1CDA,U+1CF2,U+1CF4,U+200C-200D,U+20B9,U+25CC,U+A830-A835;
}
@font-face {
    font-family: 'Noto Sans Kannada';
    font-style: normal;
    font-weight: 700;
    font-display: swap;
    src: url('fonts/noto/sans-kannada/kannada-700-normal.woff2') format('woff2');
    unicode-range: U+0951-0952,U+0964-0965,U+0C80-0CF3,U+1CD0,U+1CD2-1CD3,U+1CDA,U+1CF2,U+1CF4,U+200C-200D,U+20B9,U+25CC,U+A830-A835;
}
/* — Noto Sans Malayalam — */
@font-face {
    font-family: 'Noto Sans Malayalam';
    font-style: normal;
    font-weight: 400;
    font-display: swap;
    src: url('fonts/noto/sans-malayalam/malayalam-400-normal.woff2') format('woff2');
    unicode-range: U+0307,U+0323,U+0951-0952,U+0964-0965,U+0D00-0D7F,U+1CDA,U+1CF2,U+200C-200D,U+20B9,U+25CC,U+A830-A832;
}
@font-face {
    font-family: 'Noto Sans Malayalam';
    font-style: normal;
    font-weight: 600;
    font-display: swap;
    src: url('fonts/noto/sans-malayalam/malayalam-600-normal.woff2') format('woff2');
    unicode-range: U+0307,U+0323,U+0951-0952,U+0964-0965,U+0D00-0D7F,U+1CDA,U+1CF2,U+200C-200D,U+20B9,U+25CC,U+A830-A832;
}
@font-face {
    font-family: 'Noto Sans Malayalam';
    font-style: normal;
    font-weight: 700;
    font-display: swap;
    src: url('fonts/noto/sans-malayalam/malayalam-700-normal.woff2') format('woff2');
    unicode-range: U+0307,U+0323,U+0951-0952,U+0964-0965,U+0D00-0D7F,U+1CDA,U+1CF2,U+200C-200D,U+20B9,U+25CC,U+A830-A832;
}
/* — Noto Sans Myanmar — */
@font-face {
    font-family: 'Noto Sans Myanmar';
    font-style: normal;
    font-weight: 400;
    font-display: swap;
    src: url('fonts/noto/sans-myanmar/myanmar-400-normal.woff2') format('woff2');
    unicode-range: U+1000-109F,U+200C-200D,U+25CC,U+A92E,U+A9E0-A9FE,U+AA60-AA7F,U+116D0-116E3;
}
@font-face {
    font-family: 'Noto Sans Myanmar';
    font-style: normal;
    font-weight: 600;
    font-display: swap;
    src: url('fonts/noto/sans-myanmar/myanmar-600-normal.woff2') format('woff2');
    unicode-range: U+1000-109F,U+200C-200D,U+25CC,U+A92E,U+A9E0-A9FE,U+AA60-AA7F,U+116D0-116E3;
}
@font-face {
    font-family: 'Noto Sans Myanmar';
    font-style: normal;
    font-weight: 700;
    font-display: swap;
    src: url('fonts/noto/sans-myanmar/myanmar-700-normal.woff2') format('woff2');
    unicode-range: U+1000-109F,U+200C-200D,U+25CC,U+A92E,U+A9E0-A9FE,U+AA60-AA7F,U+116D0-116E3;
}
/* — Noto Sans Khmer — */
@font-face {
    font-family: 'Noto Sans Khmer';
    font-style: normal;
    font-weight: 400;
    font-display: swap;
    src: url('fonts/noto/sans-khmer/khmer-400-normal.woff2') format('woff2');
    unicode-range: U+1780-17FF,U+19E0-19FF,U+200C-200D,U+25CC;
}
@font-face {
    font-family: 'Noto Sans Khmer';
    font-style: normal;
    font-weight: 600;
    font-display: swap;
    src: url('fonts/noto/sans-khmer/khmer-600-normal.woff2') format('woff2');
    unicode-range: U+1780-17FF,U+19E0-19FF,U+200C-200D,U+25CC;
}
@font-face {
    font-family: 'Noto Sans Khmer';
    font-style: normal;
    font-weight: 700;
    font-display: swap;
    src: url('fonts/noto/sans-khmer/khmer-700-normal.woff2') format('woff2');
    unicode-range: U+1780-17FF,U+19E0-19FF,U+200C-200D,U+25CC;
}
/* — Noto Sans Georgian — */
@font-face {
    font-family: 'Noto Sans Georgian';
    font-style: normal;
    font-weight: 400;
    font-display: swap;
    src: url('fonts/noto/sans-georgian/georgian-400-normal.woff2') format('woff2');
    unicode-range: U+0589,U+10A0-10FF,U+1C90-1CBA,U+1CBD-1CBF,U+205A,U+2D00-2D2F,U+2E31;
}
@font-face {
    font-family: 'Noto Sans Georgian';
    font-style: normal;
    font-weight: 600;
    font-display: swap;
    src: url('fonts/noto/sans-georgian/georgian-600-normal.woff2') format('woff2');
    unicode-range: U+0589,U+10A0-10FF,U+1C90-1CBA,U+1CBD-1CBF,U+205A,U+2D00-2D2F,U+2E31;
}
@font-face {
    font-family: 'Noto Sans Georgian';
    font-style: normal;
    font-weight: 700;
    font-display: swap;
    src: url('fonts/noto/sans-georgian/georgian-700-normal.woff2') format('woff2');
    unicode-range: U+0589,U+10A0-10FF,U+1C90-1CBA,U+1CBD-1CBF,U+205A,U+2D00-2D2F,U+2E31;
}
/* — Noto Sans Oriya — */
@font-face {
    font-family: 'Noto Sans Oriya';
    font-style: normal;
    font-weight: 400;
    font-display: swap;
    src: url('fonts/noto/sans-oriya/oriya-400-normal.woff2') format('woff2');
    unicode-range: U+0951-0952,U+0964-0965,U+0B01-0B77,U+1CDA,U+1CF2,U+200C-200D,U+20B9,U+25CC;
}
@font-face {
    font-family: 'Noto Sans Oriya';
    font-style: normal;
    font-weight: 600;
    font-display: swap;
    src: url('fonts/noto/sans-oriya/oriya-600-normal.woff2') format('woff2');
    unicode-range: U+0951-0952,U+0964-0965,U+0B01-0B77,U+1CDA,U+1CF2,U+200C-200D,U+20B9,U+25CC;
}
@font-face {
    font-family: 'Noto Sans Oriya';
    font-style: normal;
    font-weight: 700;
    font-display: swap;
    src: url('fonts/noto/sans-oriya/oriya-700-normal.woff2') format('woff2');
    unicode-range: U+0951-0952,U+0964-0965,U+0B01-0B77,U+1CDA,U+1CF2,U+200C-200D,U+20B9,U+25CC;
}

/* — Noto Serif (Latin + Latin-ext) — editorial certificate body on the
   Foil landing hero. Latin-ext is required for Polish diacritics
   (ę ł ż ń ś ó) in the sample birth-certificate; without it those
   glyphs fall through to Georgia and break the within-card font match.
   Self-hosted (NOT Google Fonts CDN) per ADR 0047. ADR 0021 landing redesign. */
@font-face {
    font-family: 'Noto Serif';
    font-style: normal;
    font-weight: 400;
    font-display: swap;
    src: url('fonts/noto/serif/latin-400-normal.woff2') format('woff2');
    unicode-range: U+0000-00FF, U+0131, U+0152-0153, U+02BB-02BC, U+02C6, U+02DA, U+02DC, U+0304, U+0308, U+0329, U+2000-206F, U+2074, U+20AC, U+2122, U+2191, U+2193, U+2212, U+2215, U+FEFF, U+FFFD;
}
@font-face {
    font-family: 'Noto Serif';
    font-style: normal;
    font-weight: 600;
    font-display: swap;
    src: url('fonts/noto/serif/latin-600-normal.woff2') format('woff2');
    unicode-range: U+0000-00FF, U+0131, U+0152-0153, U+02BB-02BC, U+02C6, U+02DA, U+02DC, U+0304, U+0308, U+0329, U+2000-206F, U+2074, U+20AC, U+2122, U+2191, U+2193, U+2212, U+2215, U+FEFF, U+FFFD;
}
@font-face {
    font-family: 'Noto Serif';
    font-style: normal;
    font-weight: 700;
    font-display: swap;
    src: url('fonts/noto/serif/latin-700-normal.woff2') format('woff2');
    unicode-range: U+0000-00FF, U+0131, U+0152-0153, U+02BB-02BC, U+02C6, U+02DA, U+02DC, U+0304, U+0308, U+0329, U+2000-206F, U+2074, U+20AC, U+2122, U+2191, U+2193, U+2212, U+2215, U+FEFF, U+FFFD;
}
@font-face {
    font-family: 'Noto Serif';
    font-style: normal;
    font-weight: 400;
    font-display: swap;
    src: url('fonts/noto/serif/latin-ext-400-normal.woff2') format('woff2');
    unicode-range: U+0100-024F, U+0259, U+1E00-1EFF, U+2020, U+20A0-20AB, U+20AD-20C0, U+2113, U+2C60-2C7F, U+A720-A7FF;
}
@font-face {
    font-family: 'Noto Serif';
    font-style: normal;
    font-weight: 600;
    font-display: swap;
    src: url('fonts/noto/serif/latin-ext-600-normal.woff2') format('woff2');
    unicode-range: U+0100-024F, U+0259, U+1E00-1EFF, U+2020, U+20A0-20AB, U+20AD-20C0, U+2113, U+2C60-2C7F, U+A720-A7FF;
}
@font-face {
    font-family: 'Noto Serif';
    font-style: normal;
    font-weight: 700;
    font-display: swap;
    src: url('fonts/noto/serif/latin-ext-700-normal.woff2') format('woff2');
    unicode-range: U+0100-024F, U+0259, U+1E00-1EFF, U+2020, U+20A0-20AB, U+20AD-20C0, U+2113, U+2C60-2C7F, U+A720-A7FF;
}

/* ============================================================================
   1. Global tokens — palette, spacing, radius, shadow, motion, stroke, type
   ============================================================================ */

:root {
    /* ============================================================================
       PR 17: legacy colour token NAMES alias the new three-layer primitive
       tokens loaded from `tokens-primitive.css`. The hex values are
       identical (verified by `tests_polytxt_token_alias_invariants`); the
       indirection means a future edit to a colour in
       `tokens/polytxt.tokens.json` cascades through every component rule
       (most still reference `--brandXX` / `--greyXX` legacy names) without
       requiring a rule-by-rule rewrite. Subsequent PRs can migrate
       individual component rules from legacy names to `--prim-*` names at
       their own pace.

       DO NOT inline a hex value here. The token-discipline scanner
       (`tests_polytxt_token_aliases.py`) fails CI if any legacy
       colour declaration in this :root block uses a literal hex
       instead of `var(--prim-*)`.
       ============================================================================ */

    /* ── Brand palette (Microsoft Web Light, Brand 10–160) ───────────────── */
    --brand10:  var(--prim-blue-10);
    --brand20:  var(--prim-blue-20);
    --brand30:  var(--prim-blue-30);
    --brand40:  var(--prim-blue-40);
    --brand50:  var(--prim-blue-50);
    --brand60:  var(--prim-blue-60);
    --brand70:  var(--prim-blue-70);
    --brand80:  var(--prim-blue-80);   /* primary brand */
    --brand90:  var(--prim-blue-90);
    --brand100: var(--prim-blue-100);
    --brand110: var(--prim-blue-110);
    --brand120: var(--prim-blue-120);
    --brand130: var(--prim-blue-130);
    --brand140: var(--prim-blue-140);
    --brand150: var(--prim-blue-150);
    --brand160: var(--prim-blue-160);

    /* ── Neutral / grey ramp (Fluent v9 even-step palette) ───────────────── */
    --grey2:  var(--prim-grey-2);
    --grey4:  var(--prim-grey-4);
    --grey6:  var(--prim-grey-6);
    --grey8:  var(--prim-grey-8);
    --grey10: var(--prim-grey-10);
    --grey12: var(--prim-grey-12);
    --grey14: var(--prim-grey-14);
    --grey16: var(--prim-grey-16);
    --grey18: var(--prim-grey-18);
    --grey20: var(--prim-grey-20);
    --grey22: var(--prim-grey-22);
    --grey24: var(--prim-grey-24);
    --grey26: var(--prim-grey-26);
    --grey28: var(--prim-grey-28);
    --grey30: var(--prim-grey-30);
    --grey32: var(--prim-grey-32);
    --grey34: var(--prim-grey-34);
    --grey36: var(--prim-grey-36);
    --grey38: var(--prim-grey-38);
    --grey40: var(--prim-grey-40);
    --grey42: var(--prim-grey-42);
    --grey44: var(--prim-grey-44);
    --grey46: var(--prim-grey-46);
    --grey48: var(--prim-grey-48);
    --grey50: var(--prim-grey-50);
    --grey52: var(--prim-grey-52);
    --grey54: var(--prim-grey-54);
    --grey56: var(--prim-grey-56);
    --grey58: var(--prim-grey-58);
    --grey60: var(--prim-grey-60);
    --grey62: var(--prim-grey-62);
    --grey64: var(--prim-grey-64);
    --grey66: var(--prim-grey-66);
    --grey68: var(--prim-grey-68);
    --grey70: var(--prim-grey-70);
    --grey72: var(--prim-grey-72);
    --grey74: var(--prim-grey-74);
    --grey76: var(--prim-grey-76);
    --grey78: var(--prim-grey-78);
    --grey80: var(--prim-grey-80);
    --grey82: var(--prim-grey-82);
    --grey84: var(--prim-grey-84);
    --grey86: var(--prim-grey-86);
    --grey88: var(--prim-grey-88);
    --grey90: var(--prim-grey-90);
    --grey92: var(--prim-grey-92);
    --grey94: var(--prim-grey-94);
    --grey96: var(--prim-grey-96);
    --grey98: var(--prim-grey-98);
    --grey99: var(--prim-grey-99);
    --white:  var(--prim-white);
    --black:  var(--prim-black);

    /* ── Status palette (used by alerts, validation, status badges) ──────── */
    --statusSuccessForeground: var(--prim-green-500);
    --statusSuccessBackground: var(--prim-green-100);
    --statusSuccessBorder:     var(--prim-green-300);
    --statusDangerForeground:  var(--prim-red-500);
    --statusDangerBackground:  var(--prim-red-100);
    --statusDangerBorder:      var(--prim-red-300);
    --statusWarningForeground: var(--prim-orange-500);
    --statusWarningBackground: var(--prim-orange-100);
    --statusWarningBorder:     var(--prim-orange-300);
    --statusInfoForeground:    var(--prim-blue-70);
    --statusInfoBackground:    var(--prim-blue-160);
    --statusInfoBorder:        var(--prim-blue-140);

    /* ── Spacing (Fluent v9 spacings) ────────────────────────────────────── */
    --spacingHorizontalNone:    0;
    --spacingHorizontalXXS:     2px;
    --spacingHorizontalXS:      4px;
    --spacingHorizontalSNudge:  6px;
    --spacingHorizontalS:       8px;
    --spacingHorizontalMNudge: 10px;
    --spacingHorizontalM:      12px;
    --spacingHorizontalL:      16px;
    --spacingHorizontalXL:     20px;
    --spacingHorizontalXXL:    24px;
    --spacingHorizontalXXXL:   32px;
    --spacingVerticalNone:      0;
    --spacingVerticalXXS:       2px;
    --spacingVerticalXS:        4px;
    --spacingVerticalSNudge:    6px;
    --spacingVerticalS:         8px;
    --spacingVerticalMNudge:   10px;
    --spacingVerticalM:        12px;
    --spacingVerticalL:        16px;
    --spacingVerticalXL:       20px;
    --spacingVerticalXXL:      24px;
    --spacingVerticalXXXL:     32px;

    /* ── Border radius ───────────────────────────────────────────────────── */
    --borderRadiusNone:     0;
    --borderRadiusSmall:    2px;
    --borderRadiusMedium:   4px;
    --borderRadiusLarge:    6px;
    --borderRadiusXLarge:   8px;
    /* Foil landing redesign (ADR 0021): larger radii live in the JSON
       token source (prim.radius-5/6) so they appear in the
       /dz/design/polytxt/tokens/ gallery; aliased here for the Fluent
       vocabulary the marketing CSS reads. */
    --borderRadiusXXLarge:  var(--prim-radius-5);   /* 12px */
    --borderRadius3XLarge:  var(--prim-radius-6);   /* 16px */
    --borderRadiusCircular: 9999px;

    /* ── Stroke widths ───────────────────────────────────────────────────── */
    --strokeWidthThin:     1px;
    --strokeWidthThick:    2px;
    --strokeWidthThicker:  3px;
    --strokeWidthThickest: 4px;

    /* ── Elevation / shadow (Fluent v9 depth tokens) ─────────────────────── */
    --shadow2:  0 0 2px rgba(0, 0, 0, 0.12), 0 1px 2px rgba(0, 0, 0, 0.14);
    --shadow4:  0 0 2px rgba(0, 0, 0, 0.12), 0 2px 4px rgba(0, 0, 0, 0.14);
    --shadow8:  0 0 2px rgba(0, 0, 0, 0.12), 0 4px 8px rgba(0, 0, 0, 0.14);
    --shadow16: 0 0 2px rgba(0, 0, 0, 0.12), 0 8px 16px rgba(0, 0, 0, 0.14);
    --shadow28: 0 0 8px rgba(0, 0, 0, 0.12), 0 14px 28px rgba(0, 0, 0, 0.24);
    --shadow64: 0 0 8px rgba(0, 0, 0, 0.12), 0 32px 64px rgba(0, 0, 0, 0.24);
    /* Foil landing redesign (ADR 0021): soft, slate-blue-tinted shadows
       (sourced from prim.shadow-soft-* in the JSON token gallery). The
       Foil hero panel / cards use these rather than the cooler --shadowN. */
    --shadowSoft2:  var(--prim-shadow-soft-2);
    --shadowSoft8:  var(--prim-shadow-soft-8);
    --shadowSoft28: var(--prim-shadow-soft-28);

    /* ── Motion (durations + easing curves) ──────────────────────────────── */
    --durationUltraFast: 50ms;
    --durationFaster:   100ms;
    --durationFast:     150ms;
    --durationNormal:   200ms;
    --durationGentle:   250ms;
    --durationSlow:     300ms;
    --durationSlower:   400ms;
    --durationUltraSlow: 500ms;
    --curveEasyEase:        cubic-bezier(0.33, 0, 0.67, 1);
    --curveEasyEaseMax:     cubic-bezier(0.8,  0, 0.2,  1);
    --curveAccelerate:      cubic-bezier(0.9,  0.1, 1, 0.2);
    --curveDecelerate:      cubic-bezier(0.1,  0.9, 0.2, 1);
    --curveLinear:          cubic-bezier(0,   0, 1, 1);

    /* ── Panel motion — BOTH language panels read these ──────────────────
       One set of numbers for the site switcher and the hero's preview
       picker, because they are the same component doing the same job. They
       ran two undesigned clocks (Alpine x-transition's 150ms enter / 75ms
       leave) and the leave was the actual defect: measured on this build the
       close painted ONE mid-fade frame at 1440 with a 0.92 opacity step at
       390 — a blink, not a fade. Opacity finishes BEFORE the travel does, so
       the list is legible while it is still settling. */
    --durationPanelIn:     200ms;   /* entrance travel                    */
    --durationPanelInFade: 120ms;   /* entrance opacity — finishes first  */
    --durationPanelOut:    160ms;   /* exit, both properties              */
    --curvePanelMotion: cubic-bezier(0.2, 0, 0, 1);
    --curvePanelFade:   cubic-bezier(0.33, 0, 0.67, 1);
    /* Deliberately shallow, and scaleY only. x-transition's uniform
       scale(0.95) moved the site panel's left AND right edges by a measured
       22.8px on open and 22.64px on close — a panel that slides sideways
       while it fades, and below 880px it fights the viewport clamp. */
    --panelScaleFrom: 0.97;

    /* ── Hero row height — eased between two MEASURED heights ─────────────
       Duration is a function of DISTANCE, never of which document is
       showing: clamp(min, min + delta x msPerPx, max). Measured deltas on
       this build are 215px at 1440/900, 386px at 1100 and 548px at 390, so
       one fixed number would make a phone and a desktop feel like two
       products. hero_doc_picker.js reads these off the wrap, so no clock is
       restated as a literal in JS. --durationHeroHeightFix is the catch-up
       when the natural height drifted under the ramp; it is short because it
       corrects pixels, not distance. */
    --durationHeroHeightMin: 260ms;
    --durationHeroHeightMax: 460ms;
    --durationHeroHeightFix: 140ms;
    --heroHeightMsPerPx:     0.45;
    --curveHeroHeight: cubic-bezier(0.33, 0, 0.67, 1);

    /* ── Typography (font families, ramp, weights) ───────────────────────── */
    /* Order:
       1. Noto Sans (Latin/Cyrillic/Greek) — primary for European scripts.
       2. Per-script Noto families — browsers pick via unicode-range.
          Listing each one explicitly is what tells the browser to USE
          that family rather than fall through to system fonts when an
          Arabic/Devanagari/etc. glyph appears.
       3. Segoe UI Variable — Microsoft systems with the variable
          installed.
       4. System UI fallbacks (Roboto, Helvetica Neue, Arial, sans-serif).
       Task #G Phase 2 / ADR 0047. */
    --fontFamilyBase: "Noto Sans", "Noto Sans Arabic", "Noto Sans Hebrew",
                      "Noto Sans Devanagari", "Noto Sans Bengali",
                      "Noto Sans Tamil", "Noto Sans Gurmukhi",
                      "Noto Sans Gujarati", "Noto Sans Sinhala",
                      "Noto Sans Ethiopic", "Noto Sans SC",
                      "Noto Sans TC", "Noto Sans JP", "Noto Sans KR",
                      "Noto Sans Thai", "Noto Sans Telugu",
                      "Noto Sans Kannada", "Noto Sans Malayalam",
                      "Noto Sans Myanmar", "Noto Sans Khmer",
                      "Noto Sans Georgian", "Noto Sans Oriya",
                      "Segoe UI Variable", "Segoe UI Variable Display",
                      "Segoe UI", -apple-system, BlinkMacSystemFont,
                      "Roboto", "Helvetica Neue", Arial, sans-serif;
    --fontFamilyMonospace: "Cascadia Code", "Cascadia Mono", Menlo, Consolas,
                           "Liberation Mono", monospace;
    --fontWeightRegular:  400;
    --fontWeightMedium:   500;
    --fontWeightSemibold: 600;
    --fontWeightBold:     700;

    --fontSizeBase100: 10px;  --lineHeightBase100: 14px;  /* Caption 2 */
    --fontSizeBase200: 12px;  --lineHeightBase200: 16px;  /* Caption 1 */
    --fontSizeBase300: 14px;  --lineHeightBase300: 20px;  /* Body 1    */
    --fontSizeBase400: 16px;  --lineHeightBase400: 22px;  /* Subtitle 2 */
    --fontSizeBase500: 20px;  --lineHeightBase500: 26px;  /* Subtitle 1 */
    --fontSizeBase600: 24px;  --lineHeightBase600: 32px;  /* Title 3   */
    --fontSizeHero700: 28px;  --lineHeightHero700: 36px;  /* Title 2   */
    --fontSizeHero800: 32px;  --lineHeightHero800: 40px;  /* Title 1   */
    --fontSizeHero900: 40px;  --lineHeightHero900: 52px;  /* Large Title */
    --fontSizeHero1000: 68px; --lineHeightHero1000: 92px; /* Display    */
}

/* ── Regional Han glyph selection (ADR 0080 §4, UF) ──────────────────────────
   The four CJK faces cover overlapping Han codepoints with DIFFERENT regional
   letterforms, so which family comes first decides which region a reader sees.
   This token carries that choice, and the bare :lang() rules below re-point it
   per locale.

   Bare `:lang()`, never `html:lang()`: workstream UC tags individual blocks of
   translated text with their own lang=, so a Japanese document quoted inside an
   English page must pick up the Japanese face. `html:lang()` would only ever
   match the root element and would leave those blocks on the page default.

   Each locale gets an ORDERED tail, not just its own face:
     * ko — the shipped Korean subset carries NO Han codepoints at all, so hanja
       MUST fall through. Korean hanja use traditional forms, so TC before SC.
     * zh-Hant / ja — fall through to each other before SC, since both use
       traditional-derived forms for many shared characters.
   These rules are declared AFTER the :root block above: a bare :lang() and
   :root have the SAME specificity (0,1,0), so on <html> source order is the
   only tiebreak. Never wrap them in :where() — that drops specificity to 0. */
:root      { --fontFamilyCJK: "Noto Sans SC", "Noto Sans TC"; }
:lang(zh-Hans) { --fontFamilyCJK: "Noto Sans SC", "Noto Sans TC"; }
:lang(zh-Hant) { --fontFamilyCJK: "Noto Sans TC", "Noto Sans JP", "Noto Sans SC"; }
:lang(ja)      { --fontFamilyCJK: "Noto Sans JP", "Noto Sans TC", "Noto Sans SC"; }
:lang(ko)      { --fontFamilyCJK: "Noto Sans KR", "Noto Sans TC", "Noto Sans SC"; }

/* Any element carrying a lang= re-resolves the stack for its own subtree.
   This is an ATTRIBUTE selector on purpose: `:lang(ja)` matches every
   DESCENDANT too, which would override the monospace rules further down.
   `[lang]` matches only the nodes that actually declare a language, and normal
   inheritance carries the resolved list to their children.
   NOTE: --fontFamilyCJK must be consumed HERE, in a font-family declaration —
   splicing var(--fontFamilyCJK) into --fontFamilyBase itself does NOT work.
   A custom property is substituted at the element where it is declared and the
   SUBSTITUTED value inherits, so re-pointing --fontFamilyCJK on a descendant
   could never change an already-resolved --fontFamilyBase. NOTE: finalsay.css
   carries the same latent shape in its derived --md-sys-typescale-*-font
   aliases; a scanner for this class is a documented follow-up (not yet
   written — do not cite it as if it existed). */
[lang] { font-family: var(--fontFamilyCJK), var(--fontFamilyBase); }

/* ============================================================================
   2. Light theme alias tokens  (default — applies when no dark preference)
   ============================================================================ */

:root {
    --colorNeutralForeground1:         var(--grey14);
    --colorNeutralForeground2:         var(--grey26);
    --colorNeutralForeground3:         var(--grey38);
    --colorNeutralForegroundDisabled:  var(--grey74);
    --colorNeutralForegroundInverted:  var(--white);

    --colorBrandForeground1:           var(--brand80);
    --colorBrandForeground2:           var(--brand70);
    --colorBrandForegroundLink:        var(--brand70);
    --colorBrandForegroundLinkHover:   var(--brand60);
    --colorBrandForegroundLinkPressed: var(--brand50);

    --colorNeutralBackground1:         var(--white);
    --colorNeutralBackground2:         var(--grey98);
    --colorNeutralBackground3:         var(--grey96);
    --colorNeutralBackground4:         var(--grey94);
    --colorNeutralBackgroundDisabled:  var(--grey94);
    --colorNeutralBackgroundHover:     var(--grey94);

    --colorBrandBackground:            var(--brand80);
    --colorBrandBackgroundHover:       var(--brand70);
    --colorBrandBackgroundPressed:     var(--brand60);
    --colorBrandBackgroundSelected:    var(--brand160);
    --colorBrandBackgroundTint:        var(--brand160);

    --colorNeutralStroke1:             var(--grey82);
    --colorNeutralStroke2:             var(--grey88);
    --colorNeutralStroke3:             var(--grey94);
    --colorNeutralStrokeAccessible:    var(--grey38);
    --colorNeutralStrokeDisabled:      var(--grey88);
    --colorBrandStroke1:               var(--brand80);
    --colorBrandStroke2:               var(--brand140);

    /* Focus indicator: 2px brand outline + 2px offset (Fluent focus ring). */
    --colorStrokeFocus2:               var(--brand80);
}

/* ============================================================================
   [Section 3 — Dark theme alias — REMOVED 2026-05-30]
   ----------------------------------------------------------------------------
   The makeshift dark mode (auto via `prefers-color-scheme: dark` +
   manual override via `[data-theme="dark"]` attribute + Alpine theme
   toggle) has been removed pending a proper design-system-driven
   dark-mode pass. See ADR 0021 editorial amendment 2026-05-30.
   ============================================================================ */

/* ============================================================================
   4. Reset
   ============================================================================ */

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

html {
    /* Allow descendants to size against a sane root font-size. */
    font-size: 16px;
    -webkit-text-size-adjust: 100%;
    text-size-adjust: 100%;
}

/* Alpine.js x-cloak — required to hide x-show elements until Alpine
   initialises, otherwise the page flashes both states. Alpine itself
   doesn't ship this rule; the consumer must provide it. */
[x-cloak] { display: none !important; }

/* HTMX button-label show/hide during in-flight requests.
   ``htmx-indicator-hide`` is the idle label — hidden while the
   parent (form) carries the ``.htmx-request`` class HTMX adds
   during submission. ``htmx-indicator-show`` is the inverse —
   the spinner label, visible only while the request is in
   flight. Pair them inside the same button so the user sees
   exactly one of the two states at any time. */
.htmx-indicator-hide { display: inline; }
.htmx-indicator-show { display: none; }
.htmx-request .htmx-indicator-hide { display: none; }
.htmx-request .htmx-indicator-show { display: inline; }

/* ============================================================================
   Confirm-form polish (Sprint 3) — searchable language dropdown,
   primary-button-with-pill, document-preview gallery.
   ============================================================================ */

/* ── Translate-now button: arrow contrast + "Free" pill on blue ───────── */

/* The primary button on the confirm form carries a green "Free" pill.
   The pill's green-on-green palette doesn't pair with the brand-blue
   button background, and the arrow icon ships at a neutral colour
   that disappears on blue. Two small adjustments:
     1. Force the arrow to inherit the button's white text colour.
     2. Use a translucent white pill background so the pill reads
        as part of the primary button, not pasted-in from elsewhere. */
.btn--with-pill {
    display: inline-flex;
    align-items: center;
    gap: var(--spacingHorizontalS);
}
.btn--with-pill .btn__arrow {
    /* SVG icons set fill="currentColor" so inheriting button
       colour makes the arrow read crisp on the blue background. */
    filter: brightness(0) invert(1);
    opacity: 0.95;
}
.btn__pill {
    display: inline-flex;
    align-items: center;
    padding: 1px 8px;
    border-radius: var(--borderRadiusCircular, 999px);
    font-size: var(--fontSizeBase100);
    font-weight: var(--fontWeightSemibold);
    letter-spacing: 0.04em;
    text-transform: uppercase;
}
/* Translucent-white pill that reads on the brand-blue primary button.
   Pure-white background + brand-blue text keeps the pill legible at
   all sizes and respects the brand colour. */
.btn__pill--on-blue {
    background: rgba(255, 255, 255, 0.95);
    color: var(--colorBrandForeground1, #0F6CBD);
}

/* ── Searchable language dropdown (Fluent 2 styled) ───────────────────── */

.lang-select {
    width: 100%;
}

.lang-select__toggle {
    display: flex;
    align-items: center;
    width: 100%;
    padding: var(--spacingVerticalS) var(--spacingHorizontalM);
    background: var(--colorNeutralBackground1);
    border: var(--strokeWidthThin) solid var(--colorNeutralStroke1);
    border-radius: var(--borderRadiusMedium);
    color: var(--colorNeutralForeground1);
    font-family: var(--fontFamilyCJK), var(--fontFamilyBase);
    font-size: var(--fontSizeBase300);
    line-height: var(--lineHeightBase300);
    cursor: pointer;
    text-align: start;
    transition:
        border-color var(--durationFast) var(--curveEasyEase),
        box-shadow   var(--durationFast) var(--curveEasyEase),
        background   var(--durationFast) var(--curveEasyEase);
    min-height: 36px;
}
.lang-select__toggle:hover {
    background: var(--colorNeutralBackground2);
}
.lang-select__toggle:focus {
    outline: 0;
    border-color: var(--colorBrandStroke1);
    box-shadow: 0 0 0 1px var(--colorBrandStroke1);
}

.lang-select__display {
    display: flex;
    align-items: baseline;
    gap: var(--spacingHorizontalS);
    flex: 1 1 auto;
    min-width: 0;
}
.lang-select__placeholder {
    color: var(--colorNeutralForeground3);
    flex: 1 1 auto;
}
.lang-select__flags {
    flex: 0 0 auto;
    font-size: 1.05em;
    line-height: 1;
}
.lang-select__name {
    flex: 0 0 auto;
    color: var(--colorNeutralForeground1);
    overflow: hidden;
    text-overflow: ellipsis;
    white-space: nowrap;
}
.lang-select__native {
    flex: 1 1 auto;
    color: var(--colorNeutralForeground3);
    font-size: var(--fontSizeBase200);
    overflow: hidden;
    text-overflow: ellipsis;
    white-space: nowrap;
}
.lang-select__chevron {
    flex: 0 0 auto;
    margin-inline-start: auto;
    opacity: 0.6;
    transition: transform var(--durationFast) var(--curveEasyEase);
}
.lang-select__chevron.is-open {
    transform: rotate(180deg);
}

.lang-select__panel {
    position: absolute;
    top: calc(100% + 4px);
    left: 0;
    right: 0;
    z-index: 50;
    background: var(--colorNeutralBackground1);
    border: var(--strokeWidthThin) solid var(--colorNeutralStroke2);
    border-radius: var(--borderRadiusMedium);
    box-shadow: var(--shadow16);
    max-height: 320px;
    display: flex;
    flex-direction: column;
    overflow: hidden;
}
.lang-select__search {
    padding: var(--spacingVerticalS) var(--spacingHorizontalS);
    border-bottom: var(--strokeWidthThin) solid var(--colorNeutralStroke3);
}
.lang-select__search input {
    width: 100%;
    padding: var(--spacingVerticalXS) var(--spacingHorizontalS);
    background: var(--colorNeutralBackground2);
    border: var(--strokeWidthThin) solid transparent;
    border-radius: var(--borderRadiusMedium);
    color: var(--colorNeutralForeground1);
    font-family: var(--fontFamilyCJK), var(--fontFamilyBase);
    font-size: var(--fontSizeBase300);
}
.lang-select__search input:focus {
    outline: 0;
    border-color: var(--colorBrandStroke1);
}
.lang-select__list {
    list-style: none;
    margin: 0;
    padding: var(--spacingVerticalXS) 0;
    overflow-y: auto;
    flex: 1 1 auto;
}
.lang-select__option {
    display: flex;
    align-items: center;
    gap: var(--spacingHorizontalM);
    width: 100%;
    padding: var(--spacingVerticalS) var(--spacingHorizontalM);
    background: transparent;
    border: 0;
    color: var(--colorNeutralForeground1);
    font-family: var(--fontFamilyCJK), var(--fontFamilyBase);
    font-size: var(--fontSizeBase300);
    cursor: pointer;
    text-align: start;
}
.lang-select__option:hover {
    background: var(--colorNeutralBackgroundHover, var(--colorNeutralBackground2));
}
.lang-select__option.is-selected {
    background: var(--colorBrandBackgroundTint);
    color: var(--colorBrandForeground1);
}
.lang-select__option-flags {
    flex: 0 0 auto;
    font-size: 1.1em;
    line-height: 1;
    min-width: 36px;
}
.lang-select__option-text {
    flex: 1 1 auto;
    display: flex;
    flex-direction: column;
    min-width: 0;
}
.lang-select__option-name {
    color: inherit;
    font-weight: var(--fontWeightRegular);
    overflow: hidden;
    text-overflow: ellipsis;
    white-space: nowrap;
}
.lang-select__option-native {
    color: var(--colorNeutralForeground3);
    font-size: var(--fontSizeBase200);
    overflow: hidden;
    text-overflow: ellipsis;
    white-space: nowrap;
}

/* ── Document preview gallery (first N pages + overflow indicator) ───── */

.doc-preview-gallery {
    /* Set inline on the element so per-doc page counts can adjust
       the grid template; we just provide a stable fallback. */
    display: grid;
    gap: var(--spacingHorizontalS);
}
.doc-preview-page {
    background: var(--colorNeutralBackground2);
    border: var(--strokeWidthThin) solid var(--colorNeutralStroke2);
    border-radius: var(--borderRadiusMedium);
}
.doc-preview-page:hover {
    border-color: var(--colorNeutralStroke1);
}

body {
    margin: 0;
    background: var(--colorNeutralBackground2);
    color: var(--colorNeutralForeground1);
    font-family: var(--fontFamilyCJK), var(--fontFamilyBase);
    font-size: var(--fontSizeBase300);
    line-height: var(--lineHeightBase300);
    font-weight: var(--fontWeightRegular);
    -webkit-font-smoothing: antialiased;
    -moz-osx-font-smoothing: grayscale;
}

img, svg, video, canvas { max-width: 100%; height: auto; display: block; }

a {
    color: var(--colorBrandForegroundLink);
    text-decoration: none;
    transition: color var(--durationFast) var(--curveEasyEase);
}
a:hover    { color: var(--colorBrandForegroundLinkHover); text-decoration: underline; }
a:active   { color: var(--colorBrandForegroundLinkPressed); }

hr {
    border: 0;
    border-top: var(--strokeWidthThin) solid var(--colorNeutralStroke2);
    margin: var(--spacingVerticalL) 0;
}

button { font-family: inherit; }

/* ============================================================================
   5. Base typography  —  semantic tags map onto Fluent type ramp
   ============================================================================ */

h1, h2, h3, h4, h5, h6 {
    margin: 0 0 var(--spacingVerticalS);
    font-weight: var(--fontWeightSemibold);
    color: var(--colorNeutralForeground1);
    line-height: 1.25;
}

h1 { font-size: var(--fontSizeHero800); line-height: var(--lineHeightHero800); }
h2 { font-size: var(--fontSizeBase600); line-height: var(--lineHeightBase600); }
h3 { font-size: var(--fontSizeBase500); line-height: var(--lineHeightBase500); }
h4 { font-size: var(--fontSizeBase400); line-height: var(--lineHeightBase400); }
h5 { font-size: var(--fontSizeBase300); line-height: var(--lineHeightBase300); }
h6 { font-size: var(--fontSizeBase200); line-height: var(--lineHeightBase200); text-transform: uppercase; letter-spacing: 0.02em; color: var(--colorNeutralForeground3); }

p { margin: 0 0 var(--spacingVerticalM); }

small, .text-caption {
    font-size: var(--fontSizeBase200);
    line-height: var(--lineHeightBase200);
    color: var(--colorNeutralForeground3);
}

code, kbd, samp, pre {
    font-family: var(--fontFamilyMonospace);
    font-size: 0.9em;
}
code {
    background: var(--colorNeutralBackground3);
    padding: 0 var(--spacingHorizontalXS);
    border-radius: var(--borderRadiusSmall);
}

/* ============================================================================
   6. Page chrome
   ============================================================================ */

.page {
    max-width: 720px;
    margin: 0 auto;
    padding: var(--spacingVerticalXXL) var(--spacingHorizontalXL) var(--spacingVerticalXXXL);
}
.page--narrow { max-width: 480px; }
.page--wide   { max-width: 960px; }

.page-header {
    margin-bottom: var(--spacingVerticalXL);
}
.page-title {
    font-size: var(--fontSizeHero800);
    line-height: var(--lineHeightHero800);
    font-weight: var(--fontWeightSemibold);
    margin: 0 0 var(--spacingVerticalXS);
}
.page-subtitle {
    color: var(--colorNeutralForeground3);
    font-size: var(--fontSizeBase300);
    line-height: var(--lineHeightBase300);
    margin: 0;
}

/* ============================================================================
   7. Header / nav  (.app-header)
   ============================================================================ */

.app-header {
    background: var(--colorNeutralBackground1);
    border-bottom: var(--strokeWidthThin) solid var(--colorNeutralStroke2);
    padding: var(--spacingVerticalM) var(--spacingHorizontalXL);
    display: flex;
    align-items: center;
    justify-content: space-between;
    gap: var(--spacingHorizontalL);
}

.app-header__brand {
    font-size: var(--fontSizeBase400);
    line-height: var(--lineHeightBase400);
    font-weight: var(--fontWeightSemibold);
    color: var(--colorNeutralForeground1);
    text-decoration: none;
    display: inline-flex;
    align-items: center;
    gap: var(--spacingHorizontalS);
}
.app-header__brand:hover { text-decoration: none; color: var(--colorBrandForeground1); }

.app-header__nav {
    display: flex;
    align-items: center;
    gap: var(--spacingHorizontalL);
}
.app-header__nav a,
.app-header__nav button {
    color: var(--colorNeutralForeground2);
    font-size: var(--fontSizeBase300);
    line-height: var(--lineHeightBase300);
    background: transparent;
    border: 0;
    padding: var(--spacingVerticalXS) 0;
    cursor: pointer;
    text-decoration: none;
    font: inherit;
}
.app-header__nav a:hover,
.app-header__nav button:hover {
    color: var(--colorBrandForeground1);
    text-decoration: underline;
}
.app-header__nav .user-email {
    color: var(--colorNeutralForeground3);
    font-size: var(--fontSizeBase200);
    line-height: var(--lineHeightBase200);
}
.app-header__nav form {
    display: inline;
    margin: 0;
}

/* ============================================================================
   8. Buttons  (.btn variants)
   ============================================================================ */

.btn {
    display: inline-flex;
    align-items: center;
    justify-content: center;
    gap: var(--spacingHorizontalS);
    min-height: 32px;
    padding: 0 var(--spacingHorizontalM);
    border: var(--strokeWidthThin) solid transparent;
    border-radius: var(--borderRadiusMedium);
    font-family: inherit;
    font-size: var(--fontSizeBase300);
    line-height: var(--lineHeightBase300);
    font-weight: var(--fontWeightSemibold);
    cursor: pointer;
    text-decoration: none;
    user-select: none;
    transition:
        background-color var(--durationFast) var(--curveEasyEase),
        border-color    var(--durationFast) var(--curveEasyEase),
        color           var(--durationFast) var(--curveEasyEase),
        box-shadow      var(--durationFast) var(--curveEasyEase);
}
.btn:disabled,
.btn[aria-disabled="true"] {
    cursor: not-allowed;
    opacity: 1;
    color: var(--colorNeutralForegroundDisabled);
    background: var(--colorNeutralBackgroundDisabled);
    border-color: var(--colorNeutralStrokeDisabled);
}

/* Size variants. */
.btn--small  { min-height: 24px; padding: 0 var(--spacingHorizontalS);  font-size: var(--fontSizeBase200); }
.btn--large  { min-height: 40px; padding: 0 var(--spacingHorizontalL);  font-size: var(--fontSizeBase400); }

/* Primary — filled brand. */
.btn--primary {
    background: var(--colorBrandBackground);
    color: var(--colorNeutralForegroundInverted);
    border-color: var(--colorBrandBackground);
}
.btn--primary:hover    { background: var(--colorBrandBackgroundHover);   border-color: var(--colorBrandBackgroundHover); text-decoration: none; }
.btn--primary:active   { background: var(--colorBrandBackgroundPressed); border-color: var(--colorBrandBackgroundPressed); }

/* Secondary — neutral outline. */
.btn--secondary {
    background: var(--colorNeutralBackground1);
    color: var(--colorNeutralForeground1);
    border-color: var(--colorNeutralStroke1);
}
.btn--secondary:hover  { background: var(--colorNeutralBackgroundHover); text-decoration: none; }

/* Subtle — no border, low-emphasis. */
.btn--subtle {
    background: transparent;
    color: var(--colorNeutralForeground2);
}
.btn--subtle:hover { background: var(--colorNeutralBackgroundHover); color: var(--colorNeutralForeground1); text-decoration: none; }

/* Transparent — text-only, used inside dense regions like nav. */
.btn--transparent {
    background: transparent;
    color: var(--colorBrandForegroundLink);
    padding: 0;
    min-height: auto;
    border: 0;
    font-weight: var(--fontWeightRegular);
}
.btn--transparent:hover { color: var(--colorBrandForegroundLinkHover); text-decoration: underline; }

/* Danger — for destructive actions like log out, delete. */
.btn--danger {
    background: var(--statusDangerForeground);
    color: var(--white);
    border-color: var(--statusDangerForeground);
}
.btn--danger:hover { background: var(--prim-red-700); border-color: var(--prim-red-700); text-decoration: none; }

/* ============================================================================
   9. Form fields
   ============================================================================ */

.form {
    display: flex;
    flex-direction: column;
    gap: var(--spacingVerticalL);
}

.form-field {
    display: flex;
    flex-direction: column;
    gap: var(--spacingVerticalXS);
}
.form-field__label {
    font-size: var(--fontSizeBase300);
    line-height: var(--lineHeightBase300);
    font-weight: var(--fontWeightSemibold);
    color: var(--colorNeutralForeground1);
}
.form-field__label .required {
    color: var(--statusDangerForeground);
    margin-inline-start: var(--spacingHorizontalXXS);
}
.form-field__hint {
    font-size: var(--fontSizeBase200);
    line-height: var(--lineHeightBase200);
    color: var(--colorNeutralForeground3);
    margin: 0;
}

.form-field input[type="text"],
.form-field input[type="email"],
.form-field input[type="password"],
.form-field input[type="search"],
.form-field input[type="tel"],
.form-field input[type="url"],
.form-field input[type="number"],
.form-field input[type="date"],
.form-field input[type="datetime-local"],
.form-field input[type="time"],
.form-field input[type="file"],
.form-field select,
.form-field textarea {
    width: 100%;
    box-sizing: border-box;
    padding: var(--spacingVerticalS) var(--spacingHorizontalM);
    background: var(--colorNeutralBackground1);
    color: var(--colorNeutralForeground1);
    border: var(--strokeWidthThin) solid var(--colorNeutralStroke1);
    border-radius: var(--borderRadiusMedium);
    font-family: inherit;
    font-size: var(--fontSizeBase300);
    line-height: var(--lineHeightBase300);
    transition: border-color var(--durationFast) var(--curveEasyEase),
                box-shadow var(--durationFast) var(--curveEasyEase);
}
.form-field textarea {
    min-height: 96px;
    resize: vertical;
}
.form-field input:focus,
.form-field select:focus,
.form-field textarea:focus {
    outline: none;
    border-color: var(--colorBrandStroke1);
    /* 2px brand ring (was 1px) so keyboard focus is clearly visible —
       a11y tap/focus pass, refinement 4, 2026-06-09. */
    box-shadow: 0 0 0 var(--strokeWidthThick) var(--colorBrandStroke1);
}
.form-field input:disabled,
.form-field select:disabled,
.form-field textarea:disabled {
    background: var(--colorNeutralBackgroundDisabled);
    color: var(--colorNeutralForegroundDisabled);
    border-color: var(--colorNeutralStrokeDisabled);
    cursor: not-allowed;
}
.form-field input[aria-invalid="true"],
.form-field select[aria-invalid="true"],
.form-field textarea[aria-invalid="true"],
.form-field--invalid input,
.form-field--invalid select,
.form-field--invalid textarea {
    border-color: var(--statusDangerForeground);
}

.form-field__errors {
    list-style: none;
    margin: var(--spacingVerticalXS) 0 0;
    padding: 0;
    color: var(--statusDangerForeground);
    font-size: var(--fontSizeBase200);
    line-height: var(--lineHeightBase200);
}
.form-field__errors li { padding: 0; }

.form-error-summary {
    background: var(--statusDangerBackground);
    color: var(--statusDangerForeground);
    border: var(--strokeWidthThin) solid var(--statusDangerBorder);
    padding: var(--spacingVerticalS) var(--spacingHorizontalM);
    border-radius: var(--borderRadiusMedium);
    font-size: var(--fontSizeBase300);
    line-height: var(--lineHeightBase300);
    margin-bottom: var(--spacingVerticalL);
}

.form-actions {
    display: flex;
    gap: var(--spacingHorizontalM);
    align-items: center;
    margin-top: var(--spacingVerticalS);
}

/* ============================================================================
   10. Cards / surfaces
   ============================================================================ */

.card {
    background: var(--colorNeutralBackground1);
    border: var(--strokeWidthThin) solid var(--colorNeutralStroke2);
    border-radius: var(--borderRadiusLarge);
    padding: var(--spacingVerticalL) var(--spacingHorizontalL);
    box-shadow: var(--shadow2);
    margin-bottom: var(--spacingVerticalL);
}
.card--flat        { box-shadow: none; }
.card--elevated    { box-shadow: var(--shadow8); }

.card__title {
    margin: 0 0 var(--spacingVerticalS);
    font-size: var(--fontSizeBase400);
    line-height: var(--lineHeightBase400);
    font-weight: var(--fontWeightSemibold);
}
.card__row {
    display: flex;
    justify-content: space-between;
    gap: var(--spacingHorizontalM);
    padding: var(--spacingVerticalS) 0;
    border-bottom: var(--strokeWidthThin) solid var(--colorNeutralStroke3);
}
.card__row:last-child { border-bottom: 0; }
.card__row .label { color: var(--colorNeutralForeground3); }
.card__row .value { font-weight: var(--fontWeightSemibold); color: var(--colorNeutralForeground1); }

/* ============================================================================
   11. Badges
   ============================================================================ */

.badge {
    display: inline-flex;
    align-items: center;
    padding: 0 var(--spacingHorizontalS);
    min-height: 20px;
    font-size: var(--fontSizeBase200);
    line-height: var(--lineHeightBase200);
    font-weight: var(--fontWeightSemibold);
    border-radius: var(--borderRadiusCircular);
    background: var(--colorNeutralBackground3);
    color: var(--colorNeutralForeground2);
    border: var(--strokeWidthThin) solid var(--colorNeutralStroke2);
}
.badge--success { background: var(--statusSuccessBackground); color: var(--statusSuccessForeground); border-color: var(--statusSuccessBorder); }
.badge--danger  { background: var(--statusDangerBackground);  color: var(--statusDangerForeground);  border-color: var(--statusDangerBorder); }
.badge--warning { background: var(--statusWarningBackground); color: var(--statusWarningForeground); border-color: var(--statusWarningBorder); }
.badge--info    { background: var(--statusInfoBackground);    color: var(--statusInfoForeground);    border-color: var(--statusInfoBorder); }
.badge--brand   { background: var(--colorBrandBackgroundTint); color: var(--colorBrandForeground1); border-color: transparent; }

/* ============================================================================
   12. Alerts / messages
   ============================================================================ */

.messages {
    list-style: none;
    margin: 0 0 var(--spacingVerticalL);
    padding: 0;
    display: flex;
    flex-direction: column;
    gap: var(--spacingVerticalS);
}

.alert,
.messages li {
    padding: var(--spacingVerticalS) var(--spacingHorizontalL);
    border-radius: var(--borderRadiusMedium);
    font-size: var(--fontSizeBase300);
    line-height: var(--lineHeightBase300);
    border: var(--strokeWidthThin) solid transparent;
}
.alert--success, .messages li.success {
    background: var(--statusSuccessBackground);
    color: var(--statusSuccessForeground);
    border-color: var(--statusSuccessBorder);
}
.alert--error,   .messages li.error {
    background: var(--statusDangerBackground);
    color: var(--statusDangerForeground);
    border-color: var(--statusDangerBorder);
}
.alert--warning, .messages li.warning {
    background: var(--statusWarningBackground);
    color: var(--statusWarningForeground);
    border-color: var(--statusWarningBorder);
}
.alert--info,    .messages li.info, .messages li.debug {
    background: var(--statusInfoBackground);
    color: var(--statusInfoForeground);
    border-color: var(--statusInfoBorder);
}

/* ============================================================================
   12b. Dropzone + upload progress  (file intake / upload pages)

   Used by:
     - app/templates/accounts/job_form.html  (multi-file intake on /jobs/new/)
     - app/templates/accounts/job_documents.html  (per-job uploads)
       + app/accounts/static/customer/document_upload.js (toggles classes)

   The JS only toggles state classes (.is-active, .is-uploading); all
   visual styling lives here so the design system stays the source of
   truth for color, radius, spacing, motion.
   ============================================================================ */

.dropzone {
    border: var(--strokeWidthThick) dashed var(--colorNeutralStroke1);
    border-radius: var(--borderRadiusLarge);
    padding: var(--spacingVerticalL) var(--spacingHorizontalL);
    text-align: center;
    background: var(--colorNeutralBackground2);
    cursor: pointer;
    transition:
        background-color var(--durationFast) var(--curveEasyEase),
        border-color    var(--durationFast) var(--curveEasyEase);
}
.dropzone:hover { background: var(--colorNeutralBackground3); }
.dropzone.is-active {
    background: var(--colorBrandBackgroundTint);
    border-color: var(--colorBrandStroke1);
}

.dropzone__prompt {
    color: var(--colorNeutralForeground2);
    font-size: var(--fontSizeBase300);
    line-height: var(--lineHeightBase300);
}
.dropzone__prompt strong { color: var(--colorNeutralForeground1); }
.dropzone__prompt a,
.dropzone__prompt .dropzone__link {
    color: var(--colorBrandForegroundLink);
    text-decoration: underline;
}

.dropzone__file-list {
    list-style: none;
    padding: 0;
    margin: var(--spacingVerticalM) 0 0;
    text-align: start;
    font-size: var(--fontSizeBase300);
    line-height: var(--lineHeightBase300);
}
.dropzone__file-list-header {
    display: flex;
    justify-content: space-between;
    align-items: center;
    padding: var(--spacingVerticalXS) 0;
    border-bottom: var(--strokeWidthThin) solid var(--colorNeutralStroke3);
    font-weight: var(--fontWeightSemibold);
    color: var(--colorNeutralForeground2);
}
.dropzone__file-row {
    display: flex;
    justify-content: space-between;
    align-items: center;
    padding: var(--spacingVerticalXS) 0;
    border-bottom: var(--strokeWidthThin) solid var(--colorNeutralStroke3);
}
.dropzone__file-row:last-child { border-bottom: 0; }
.dropzone__file-name {
    overflow: hidden;
    text-overflow: ellipsis;
    white-space: nowrap;
}
.dropzone__remove-btn {
    background: transparent;
    border: 0;
    color: var(--colorNeutralForeground3);
    cursor: pointer;
    font-size: var(--fontSizeBase400);
    padding: 0 var(--spacingHorizontalXS);
    margin-inline-start: var(--spacingHorizontalS);
    line-height: 1;
    border-radius: var(--borderRadiusMedium);
    transition: color var(--durationFast) var(--curveEasyEase);
}
.dropzone__remove-btn:hover { color: var(--statusDangerForeground); }
.dropzone__clear-all {
    background: transparent;
    border: var(--strokeWidthThin) solid var(--colorNeutralStroke1);
    color: var(--colorNeutralForeground2);
    border-radius: var(--borderRadiusMedium);
    padding: var(--spacingVerticalXXS) var(--spacingHorizontalS);
    font-size: var(--fontSizeBase200);
    cursor: pointer;
}
.dropzone__clear-all:hover {
    background: var(--colorNeutralBackgroundHover);
    color: var(--colorNeutralForeground1);
}

/* Bulletproof file-input hide. The HTML `hidden` attribute is sometimes
   ignored by browser user-agent stylesheets for <input type="file">,
   leaving a thin sliver of the native control visible. Belt-and-braces
   it with display:none + position:absolute. */
.dropzone__file-input {
    display: none !important;
    position: absolute;
    width: 0;
    height: 0;
    opacity: 0;
    pointer-events: none;
}

/* Inline T&C / consent line directly under the dropzone — small,
   muted, legally clear but not visually heavy. */
.dropzone__legal {
    margin: var(--spacingVerticalM) auto 0;
    max-width: 540px;
    color: var(--colorNeutralForeground3);
    font-size: var(--fontSizeBase200);
    line-height: var(--lineHeightBase200);
    text-align: center;
}
.dropzone__legal a {
    color: var(--colorBrandForegroundLink);
    text-decoration: underline;
}

/* Image / file thumbnail used by job_documents preview cards. */
.upload-file-card {
    display: flex;
    gap: var(--spacingHorizontalM);
    align-items: center;
    margin-top: var(--spacingVerticalS);
}
.upload-file-thumb {
    width: 48px;
    height: 48px;
    object-fit: cover;
    border-radius: var(--borderRadiusMedium);
    flex-shrink: 0;
}
.upload-file-icon {
    width: 48px;
    height: 48px;
    border-radius: var(--borderRadiusMedium);
    background: var(--colorBrandBackgroundTint);
    color: var(--colorBrandForeground1);
    display: flex;
    align-items: center;
    justify-content: center;
    font-size: var(--fontSizeBase200);
    font-weight: var(--fontWeightBold);
    flex-shrink: 0;
}
.upload-file-meta {
    flex: 1;
    min-width: 0;
}
.upload-file-meta__name {
    font-weight: var(--fontWeightSemibold);
    overflow: hidden;
    text-overflow: ellipsis;
    white-space: nowrap;
    color: var(--colorNeutralForeground1);
}
.upload-file-meta__detail {
    font-size: var(--fontSizeBase200);
    line-height: var(--lineHeightBase200);
    color: var(--colorNeutralForeground3);
}

/* Upload progress bar (job_documents async submit). */
.upload-progress {
    margin-top: var(--spacingVerticalS);
}
.upload-progress__track {
    height: 6px;
    border-radius: var(--borderRadiusCircular);
    background: var(--colorNeutralBackground3);
    overflow: hidden;
}
.upload-progress__bar {
    height: 100%;
    width: 0;
    background: var(--colorBrandBackground);
    transition: width var(--durationFast) var(--curveEasyEase);
}
.upload-progress__label {
    font-size: var(--fontSizeBase200);
    line-height: var(--lineHeightBase200);
    color: var(--colorNeutralForeground3);
    margin-top: var(--spacingVerticalXS);
}

/* Hero-dropzone variant (Task #F PR-1). The progress UI lives
 * inside the .upload-file-card so the legacy /jobs/new/ flow
 * (which uses .upload-progress__track child) is untouched. Here
 * the .upload-progress element itself acts as the track. */
.upload-file-card .upload-progress {
    height: 8px;
    border-radius: var(--borderRadiusCircular, 999px);
    background: var(--colorNeutralBackground3, #e5e5e5);
    overflow: hidden;
    /* Bind the track to the parent flex column's available width;
     * the diagonal-stripe inner bar uses width: <percent> which is
     * relative to this. Without an explicit width the track can
     * grow against its flex children on very long filenames (the
     * "progress bar bleeding out" symptom from 2026-05-15). */
    width: 100%;
    margin-top: var(--spacingVerticalS);
}
.upload-file-card .upload-progress__bar {
    height: 100%;
    background: linear-gradient(
        90deg,
        var(--colorBrandStroke1, #0078d4) 0%,
        var(--colorBrandStroke1, #0078d4) 60%,
        rgba(0, 120, 212, 0.85) 100%
    );
    /* Animated diagonal stripe — implies progress even mid-jump. */
    background-image:
        linear-gradient(45deg,
            rgba(255, 255, 255, 0.20) 25%, transparent 25%,
            transparent 50%, rgba(255, 255, 255, 0.20) 50%,
            rgba(255, 255, 255, 0.20) 75%, transparent 75%, transparent
        ),
        linear-gradient(90deg,
            var(--colorBrandStroke1, #0078d4),
            var(--colorBrandStroke1, #0078d4)
        );
    background-size: 16px 16px, 100% 100%;
    animation: upload-progress-stripe 0.8s linear infinite;
    transition: width 240ms cubic-bezier(0.5, 0.0, 0.2, 1);
}
.upload-file-card .upload-progress__bar--done {
    animation-play-state: paused;
    background: var(--colorPaletteGreenForeground1, #22c55e);
    background-image: none;
}
@keyframes upload-progress-stripe {
    0%   { background-position: 0 0, 0 0; }
    100% { background-position: 16px 0, 0 0; }
}

/* New file-card sub-classes for the dropzone progress UI
 * (Task #F PR-1). Disambiguated from the legacy ``.upload-file-thumb``
 * / ``.upload-file-icon`` / ``.upload-file-meta`` triad which the
 * /jobs/new/ flow uses. */
.upload-file-card__icon {
    width: 56px;
    height: 56px;
    flex: 0 0 auto;
}
.upload-file-card__meta {
    flex: 1;
    min-width: 0;
    text-align: start;
}
.upload-file-card__name {
    font-weight: var(--fontWeightSemibold);
    color: var(--colorNeutralForeground1);
    overflow: hidden;
    text-overflow: ellipsis;
    white-space: nowrap;
    font-size: var(--fontSizeBase300);
}
.upload-file-card__sub {
    font-size: var(--fontSizeBase200);
    color: var(--colorNeutralForeground3);
    margin-top: 2px;
}

/* ============================================================================
   13. Tables  (.data-table)
   ============================================================================ */

.data-table {
    width: 100%;
    border-collapse: collapse;
    font-size: var(--fontSizeBase300);
    line-height: var(--lineHeightBase300);
}
.data-table th,
.data-table td {
    padding: var(--spacingVerticalS) var(--spacingHorizontalM);
    text-align: start;
    border-bottom: var(--strokeWidthThin) solid var(--colorNeutralStroke3);
}
.data-table th {
    color: var(--colorNeutralForeground3);
    font-weight: var(--fontWeightSemibold);
    font-size: var(--fontSizeBase200);
    line-height: var(--lineHeightBase200);
    text-transform: uppercase;
    letter-spacing: 0.02em;
}
.data-table tbody tr:hover { background: var(--colorNeutralBackgroundHover); }
.data-table tbody td.is-numeric { font-variant-numeric: tabular-nums; }

/* The stripped-down "key: value" table used on /home and inside placeholder
   pages. Two columns; first column muted, second column emphasised. */
.kv-table {
    width: 100%;
    border-collapse: collapse;
    font-size: var(--fontSizeBase300);
    line-height: var(--lineHeightBase300);
}
.kv-table td {
    padding: var(--spacingVerticalS) var(--spacingHorizontalM);
    border-bottom: var(--strokeWidthThin) solid var(--colorNeutralStroke3);
}
.kv-table td:first-child { color: var(--colorNeutralForeground3); width: 40%; }
.kv-table td:last-child  { font-weight: var(--fontWeightSemibold); }

/* ============================================================================
   14. Utilities  (sparing — favour semantic component classes when possible)
   ============================================================================ */

.u-text-muted { color: var(--colorNeutralForeground3); }
.u-text-caption {
    font-size: var(--fontSizeBase200);
    line-height: var(--lineHeightBase200);
    color: var(--colorNeutralForeground3);
}
.u-mt-S    { margin-top: var(--spacingVerticalS); }
.u-mt-M    { margin-top: var(--spacingVerticalM); }
.u-mt-L    { margin-top: var(--spacingVerticalL); }
.u-mt-XL   { margin-top: var(--spacingVerticalXL); }
.u-mb-0    { margin-bottom: 0; }
.u-flex    { display: flex; }
.u-flex-row-end { display: flex; justify-content: flex-end; gap: var(--spacingHorizontalM); }

/* ============================================================================
   15. Focus + reduced-motion  (Fluent focus ring + a11y)
   ============================================================================ */

/* Fluent v9 focus ring: 2px brand outline with a 2px transparent offset so
   the ring sits cleanly outside the focused element. Use :focus-visible so
   mouse clicks don't show the ring; only keyboard focus does. */
:focus-visible {
    outline: var(--strokeWidthThick) solid var(--colorStrokeFocus2);
    outline-offset: var(--strokeWidthThick);
    border-radius: var(--borderRadiusMedium);
}

/* For form inputs the focus state is the box-shadow above; suppress the
   outer outline so the visual stays clean. */
.form-field input:focus-visible,
.form-field select:focus-visible,
.form-field textarea:focus-visible {
    outline: none;
}

/* Skip-to-content link — visually hidden until focused. */
.skip-link {
    position: absolute;
    top: -40px;
    left: var(--spacingHorizontalS);
    padding: var(--spacingVerticalS) var(--spacingHorizontalM);
    background: var(--colorBrandBackground);
    color: var(--colorNeutralForegroundInverted);
    border-radius: var(--borderRadiusMedium);
    z-index: 1000;
    transition: top var(--durationFast) var(--curveEasyEase);
}
.skip-link:focus {
    top: var(--spacingVerticalS);
    text-decoration: none;
}

@media (prefers-reduced-motion: reduce) {
    *, *::before, *::after {
        animation-duration: 0.001ms !important;
        animation-iteration-count: 1 !important;
        transition-duration: 0.001ms !important;
        scroll-behavior: auto !important;
    }
    /* Reveal-on-scroll content must be fully visible for reduced-motion
       users regardless of whether the observer added the .js-reveal gate
       (see the [data-reveal] block in Section 17). Resets both the bare
       selector and the JS-gated one. */
    [data-reveal],
    .js-reveal [data-reveal] {
        opacity: 1 !important;
        transform: none !important;
    }
}

/* ============================================================================
   [Section 16 — Theme attribute overrides — REMOVED 2026-05-30]
   ----------------------------------------------------------------------------
   The `[data-theme="light"]` / `[data-theme="dark"]` blocks have been
   removed. Light theme values are now the default `:root` declarations
   in Section 2 (where they already lived; the explicit
   `[data-theme="light"]` block just duplicated them with higher
   specificity). Dark theme deferred — see ADR 0021 amendment.
   ============================================================================ */

/* Smooth colour transition when the user flips the theme. */
html { transition: background-color var(--durationNormal) var(--curveEasyEase); }
body { transition: background-color var(--durationNormal) var(--curveEasyEase),
                   color var(--durationNormal) var(--curveEasyEase); }

/* ============================================================================
   17. Marketing chrome  —  surfaces, gradients, keyframes
   ============================================================================ */

:root {
    /* Composite tokens used by marketing surfaces. */
    --surfaceBlur: blur(20px) saturate(180%);
    --gradientBrand:
        radial-gradient(at 0% 0%, var(--brand160) 0%, transparent 50%),
        radial-gradient(at 100% 100%, var(--brand140) 0%, transparent 50%),
        linear-gradient(135deg, var(--brand80) 0%, var(--brand60) 100%);
    /* Brand INK gradient — for glyphs and marks, not surfaces. These are
       the logo mark's own two stops (see
       polytxt/static/polytxt/brand/logo-mark.svg), so the wordmark's "txt"
       half and the mark share one gradient. Distinct from --gradientBrand
       above, which is a page-surface wash whose stops fade to transparent
       and reads as pale, partly see-through ink at text size. */
    --gradientBrandInk: linear-gradient(90deg, var(--prim-hd-accent) 0%, var(--prim-foil-4) 100%);
    --gradientSubtle:
        linear-gradient(180deg,
            color-mix(in srgb, var(--colorBrandBackgroundTint) 60%, transparent) 0%,
            transparent 100%);
    --gradientHero:
        radial-gradient(ellipse at top right, var(--colorBrandBackgroundTint) 0%, transparent 60%),
        radial-gradient(ellipse at bottom left,
            color-mix(in srgb, var(--brand140) 30%, transparent) 0%,
            transparent 60%);

    /* Foil landing redesign (ADR 0021): the iridescent "foil" gradient.
       Stop colours are JSON tokens (foil.holo-1..5 = Arctic tint). Used
       by the animated .pt-holo treatment (top rules, dropzone border,
       seal, eyebrow dash) on the landing hero + language popover. */
    --gradientHolo:
        linear-gradient(115deg,
            var(--foil-holo-1) 0%,  var(--foil-holo-2) 22%, var(--foil-holo-3) 44%,
            var(--foil-holo-4) 64%, var(--foil-holo-5) 82%, var(--foil-holo-1) 100%);
}

@keyframes revealUp {
    from { opacity: 0; transform: translateY(16px); }
    to   { opacity: 1; transform: translateY(0); }
}
@keyframes fadeIn {
    from { opacity: 0; }
    to   { opacity: 1; }
}
@keyframes pulse {
    0%, 100% { opacity: 1; }
    50%      { opacity: 0.5; }
}
@keyframes gradientShift {
    0%, 100% { background-position: 0% 50%; }
    50%      { background-position: 100% 50%; }
}
@keyframes spin {
    to { transform: rotate(360deg); }
}

/* Reveal-on-scroll utility — VISIBLE-BY-DEFAULT + progressive enhancement.
   ---------------------------------------------------------------------------
   2026-06-08 (wf_00e50379-e44 visual review, the #1 finding): the previous
   rule shipped [data-reveal] content HIDDEN (opacity:0) and only revealed it
   when the IntersectionObserver in base_polytxt.html added .is-revealed. That
   meant no-JS visitors, search-engine crawlers that don't run JS (hidden body
   text is an SEO quality signal), reduced-motion users, and any non-scrolling
   render saw EMPTY content. The prefers-reduced-motion block below only zeroed
   transition/animation DURATION — it never reset opacity/transform.

   The fix inverts the default: content is visible (opacity:1, no transform)
   unless the .js-reveal class is present on <html>. That class is added by the
   observer script ONLY after it has confirmed IntersectionObserver exists (and
   reduced-motion is not requested), so the hidden state is purely a
   progressive enhancement. The nice fade-in still plays for the normal
   JS + motion + scroll path. The reduced-motion block forces visibility with
   !important as belt-and-braces, even if .js-reveal was somehow added.

   Class-of-bug guard: app/ops/tests_reveal_visibility_invariants.py fails if a
   bare [data-reveal] rule ever ships opacity:0 / a translate transform again
   without a JS-gated parent class and a reduced-motion reset. */
[data-reveal] {
    transition: opacity var(--durationSlow) var(--curveDecelerate),
                transform var(--durationSlow) var(--curveDecelerate);
}
.js-reveal [data-reveal] {
    opacity: 0;
    transform: translateY(16px);
}
.js-reveal [data-reveal].is-revealed {
    opacity: 1;
    transform: translateY(0);
}

/* ============================================================================
   18. Marketing layout  —  .section, .page--marketing, .container
   ============================================================================ */

.page--marketing {
    max-width: none;
    padding: 0;
}
.container {
    width: 100%;
    max-width: 1120px;
    margin: 0 auto;
    padding-inline-start: var(--spacingHorizontalXL);
    padding-inline-end: var(--spacingHorizontalXL);
}
.container--narrow { max-width: 780px; }
.container--wide   { max-width: 1280px; }

.section {
    padding-top: clamp(48px, 8vw, 96px);
    padding-bottom: clamp(48px, 8vw, 96px);
}
.section--tight { padding-top: clamp(32px, 5vw, 56px); padding-bottom: clamp(32px, 5vw, 56px); }
.section--alt   { background: var(--colorNeutralBackground2); }
.section--brand { background: var(--gradientSubtle); }

.section-header {
    text-align: center;
    max-width: 720px;
    margin: 0 auto var(--spacingVerticalXXL);
}
.section-eyebrow {
    display: inline-block;
    font-size: var(--fontSizeBase200);
    line-height: var(--lineHeightBase200);
    font-weight: var(--fontWeightSemibold);
    text-transform: uppercase;
    letter-spacing: 0.08em;
    color: var(--colorBrandForeground1);
    margin-bottom: var(--spacingVerticalS);
}
.section-title {
    font-size: clamp(28px, 4vw, var(--fontSizeHero800));
    line-height: 1.15;
    font-weight: var(--fontWeightSemibold);
    margin: 0 0 var(--spacingVerticalM);
    letter-spacing: -0.02em;
}
.section-subtitle {
    font-size: var(--fontSizeBase400);
    line-height: var(--lineHeightBase400);
    color: var(--colorNeutralForeground2);
    margin: 0;
}

.grid {
    display: grid;
    gap: var(--spacingVerticalXL);
    /* Equal-height rows by default so a row of surface-cards matches
       without per-template inline ``align-items: stretch`` (the cards
       themselves carry ``height: 100%``). Grid already defaults to
       stretch — declaring it makes the contract explicit + intentional. */
    align-items: stretch;
}
.grid--2 { grid-template-columns: repeat(auto-fit, minmax(280px, 1fr)); }
.grid--3 { grid-template-columns: repeat(auto-fit, minmax(260px, 1fr)); }
.grid--4 { grid-template-columns: repeat(auto-fit, minmax(220px, 1fr)); }

/* ============================================================================
   19. Hero  —  full-bleed marketing landing block
   ============================================================================ */

.hero {
    position: relative;
    overflow: hidden;
    padding-top: clamp(64px, 12vw, 128px);
    padding-bottom: clamp(64px, 12vw, 128px);
    background: var(--gradientHero);
}
.hero::before {
    /* Fine grid pattern overlay for visual interest */
    content: "";
    position: absolute;
    inset: 0;
    background-image:
        linear-gradient(var(--colorNeutralStroke3) 1px, transparent 1px),
        linear-gradient(90deg, var(--colorNeutralStroke3) 1px, transparent 1px);
    background-size: 48px 48px;
    opacity: 0.25;
    mask-image: radial-gradient(ellipse at center, black 30%, transparent 80%);
    pointer-events: none;
}
.hero__content {
    position: relative;
    text-align: center;
    max-width: 800px;
    margin: 0 auto;
    animation: fadeIn var(--durationSlow) var(--curveDecelerate) both;
}
.hero__eyebrow {
    display: inline-flex;
    align-items: center;
    gap: var(--spacingHorizontalS);
    font-size: var(--fontSizeBase200);
    line-height: var(--lineHeightBase200);
    font-weight: var(--fontWeightSemibold);
    text-transform: uppercase;
    letter-spacing: 0.1em;
    color: var(--colorBrandForeground1);
    background: var(--colorNeutralBackground1);
    border: var(--strokeWidthThin) solid var(--colorNeutralStroke2);
    border-radius: var(--borderRadiusCircular);
    padding: var(--spacingVerticalXS) var(--spacingHorizontalM);
    margin-bottom: var(--spacingVerticalL);
    box-shadow: var(--shadow2);
}
.hero__title {
    font-size: clamp(36px, 7vw, var(--fontSizeHero900));
    line-height: 1.05;
    font-weight: var(--fontWeightBold);
    letter-spacing: -0.03em;
    color: var(--colorNeutralForeground1);
    margin: 0 0 var(--spacingVerticalL);
}
.hero__title-accent {
    background: linear-gradient(135deg, var(--brand80), var(--brand100));
    -webkit-background-clip: text;
    background-clip: text;
    color: transparent;
}
.hero__subtitle {
    font-size: clamp(16px, 2vw, var(--fontSizeBase500));
    line-height: 1.5;
    color: var(--colorNeutralForeground2);
    max-width: 640px;
    margin: 0 auto var(--spacingVerticalXL);
}
.hero__cta-row {
    display: flex;
    gap: var(--spacingHorizontalM);
    justify-content: center;
    flex-wrap: wrap;
}
/* Apostille-adapted trust band (hero polish refinement 5, 2026-06-09):
   tighten the scattered trust line into a confident concrete strip — an
   even-gap row of signal cells, width-constrained + centred, set off from
   the hero above by a subtle top hairline + tint band. Each cell stacks
   its bold claim over a muted sub-line (apostille's icon/claim/sub-line
   shape, text-only since home.html is out of partition; an icon slot is a
   later template-pass follow-up). Copy stays factual + free-AI-first per
   ADR 0057 — no invented reviews, no compliance-badge imagery; the
   existing "UK GDPR" / "Audit trail" cells are pre-existing copy, left
   as-is. */
.hero__trust-row {
    display: flex;
    gap: var(--spacingVerticalS) var(--spacingHorizontalXXL);
    justify-content: center;
    align-items: center;
    flex-wrap: wrap;
    max-inline-size: 760px;
    margin-inline: auto;
    margin-top: var(--spacingVerticalXXL);
    padding-block-start: var(--spacingVerticalL);
    border-block-start: var(--strokeWidthThin) solid var(--colorNeutralStroke2);
    color: var(--colorNeutralForeground3);
    font-size: var(--fontSizeBase200);
    line-height: var(--lineHeightBase300);
    text-align: center;
}
/* Each signal cell reads as a tidy unit; the bold claim is the prominent
   token, the trailing text its quiet qualifier (apostille claim/sub-line
   shape, kept inline so the existing "· qualifier" markup reads cleanly
   without template edits). */
.hero__trust-row > span {
    white-space: nowrap;
}
.hero__trust-row strong {
    color: var(--colorNeutralForeground1);
    font-weight: var(--fontWeightSemibold);
}

/* ============================================================================
   19b. Hero side-by-side showcase  (ADR 0057 Wave 4)

   The signature surface: a working side-by-side translator. Left pane =
   original document on a paper sheet AND the live dropzone; centre = an
   AI seam; right pane = the translated document. A canned sample reads
   the value in <1s; language chips flip the sample client-side.

   RTL-safe: every offset uses LOGICAL properties (inline-start/end,
   inline-size, text-align:start). The Arabic sample's right-to-left
   flow comes from the ``dir="rtl"`` attribute on the sample node, NOT
   from directional CSS — so the layout works for both audiences with
   no extra code (ops.tests_rtl_invariants enforces this file).
   ============================================================================ */

/* ============================================================================
   20. Feature card / service card / pricing card / testimonial

   ONE Fluent 2 card recipe (wf_00e50379-e44 visual-review refinement 1,
   2026-06-09). The five marketing card classes — .feature-card,
   .service-card, .pricing-card, .use-case-card (in §Marketing add-ons),
   .diff-card (idem) — previously diverged on radius, resting elevation,
   padding, and hover. They now SHARE one canonical contract via the
   grouped ``.surface-card, .feature-card, …`` rule below so a row of
   mixed card types reads as one family. The class names are NOT renamed
   (polytxt.tests_marketing_surface asserts they exist + templates
   reference them); each per-role rule that follows only declares its
   intentional deltas (icon badge, price ramp, brand-tinted title, etc.).

   Canonical values: Background1 surface, Stroke2 hairline border,
   borderRadiusXLarge (promotes use-case + diff up to the premium cards),
   generous uniform VerticalL/HorizontalL padding, a low resting shadow2
   lift (the single biggest "feels premium" lever — every card now sits
   off the page at rest), and a unified hover (translateY(-2px) +
   shadow8 + brandStroke2 border). display:flex column + height:100%
   makes a row equal-height so bodies fill and footers/CTAs align.
   ============================================================================ */

.surface-card,
.feature-card,
.service-card,
.pricing-card,
.use-case-card,
.diff-card {
    /* Brand-tinted title accent is opt-in per role (diff + use-case set
       it to brandForeground1); default keeps the neutral heading colour. */
    --card-title-color: var(--colorNeutralForeground1);
    background: var(--colorNeutralBackground1);
    border: var(--strokeWidthThin) solid var(--colorNeutralStroke2);
    border-radius: var(--borderRadiusXLarge);
    padding: var(--spacingVerticalL) var(--spacingHorizontalL);
    box-shadow: var(--shadow2);
    display: flex;
    flex-direction: column;
    height: 100%;
    transition:
        transform var(--durationNormal) var(--curveEasyEase),
        box-shadow var(--durationNormal) var(--curveEasyEase),
        border-color var(--durationNormal) var(--curveEasyEase);
}
.surface-card:hover,
.feature-card:hover,
.service-card:hover,
.pricing-card:hover,
.use-case-card:hover,
.diff-card:hover {
    transform: translateY(-2px);
    box-shadow: var(--shadow8);
    border-color: var(--colorBrandStroke2);
    text-decoration: none;
}

/* ── Canonical card content ramp: title → meta → body ─────────────────── */
/* The "meta" slot is the apostille "clear figure" role ("15 document
   types", "40+ languages", a "Free" pill) — surfaced as a stat, not a
   whisper (Foreground2, not the tiny grey count). Reused by .surface-card
   adopters; the existing per-class __title/__count/__body sub-elements
   carry the same ramp so a later template pass can converge on these. */
.surface-card__title {
    font-size: var(--fontSizeBase400);
    line-height: var(--lineHeightBase400);
    font-weight: var(--fontWeightSemibold);
    color: var(--card-title-color);
    margin: 0 0 var(--spacingVerticalS);
}
.surface-card__meta {
    font-size: var(--fontSizeBase200);
    line-height: var(--lineHeightBase200);
    color: var(--colorNeutralForeground2);
    margin: 0 0 var(--spacingVerticalXS);
}
.surface-card__body {
    font-size: var(--fontSizeBase300);
    line-height: var(--lineHeightBase300);
    color: var(--colorNeutralForeground2);
    margin: 0;
}

/* ── Per-role deltas (only what differs from the shared recipe) ───────── */
.feature-card {
    /* Slightly taller top padding for the icon-led feature cards. */
    padding-block: var(--spacingVerticalXL);
}
.feature-card__icon {
    width: 48px;
    height: 48px;
    border-radius: var(--borderRadiusLarge);
    background: var(--colorBrandBackgroundTint);
    color: var(--colorBrandForeground1);
    display: flex;
    align-items: center;
    justify-content: center;
    margin-bottom: var(--spacingVerticalL);
}
.feature-card__icon img,
.feature-card__icon svg { width: 24px; height: 24px; }
.feature-card__title {
    font-size: var(--fontSizeBase400);
    line-height: var(--lineHeightBase400);
    font-weight: var(--fontWeightSemibold);
    margin: 0 0 var(--spacingVerticalS);
    color: var(--colorNeutralForeground1);
}
.feature-card__body {
    font-size: var(--fontSizeBase300);
    line-height: var(--lineHeightBase300);
    color: var(--colorNeutralForeground2);
    margin: 0;
}

/* .service-card is an anchor — inherit the recipe's surface + hover and
   only keep the link-specific resets here. */
.service-card {
    text-decoration: none;
    color: inherit;
}
.service-card__header {
    display: flex;
    align-items: center;
    gap: var(--spacingHorizontalM);
    margin-bottom: var(--spacingVerticalM);
}
.service-card__icon {
    width: 40px; height: 40px;
    border-radius: var(--borderRadiusMedium);
    background: var(--colorBrandBackgroundTint);
    color: var(--colorBrandForeground1);
    display: flex; align-items: center; justify-content: center;
    flex-shrink: 0;
}
.service-card__title {
    font-size: var(--fontSizeBase400);
    line-height: var(--lineHeightBase400);
    font-weight: var(--fontWeightSemibold);
    margin: 0;
}
.service-card__count {
    /* The apostille "clear figure" slot ("N document types"). Read it as
       a stat, not a whisper — Foreground2 (was Foreground3). */
    font-size: var(--fontSizeBase200);
    color: var(--colorNeutralForeground2);
    margin: var(--spacingVerticalXXS) 0 0;
}
.service-card__list {
    list-style: none;
    padding: 0;
    margin: 0;
    display: flex;
    flex-wrap: wrap;
    gap: var(--spacingHorizontalXS);
}
.service-card__list li {
    font-size: var(--fontSizeBase200);
    color: var(--colorNeutralForeground2);
    background: var(--colorNeutralBackground2);
    border-radius: var(--borderRadiusCircular);
    padding: var(--spacingVerticalXXS) var(--spacingHorizontalS);
}

/* .pricing-card inherits the recipe; it only wants a slightly taller top
   pad so the tier/price masthead breathes. */
.pricing-card {
    padding-block: var(--spacingVerticalXL);
}
.pricing-card--featured {
    border-color: var(--colorBrandStroke1);
    box-shadow: var(--shadow8);
    transform: translateY(-4px);
    position: relative;
}
.pricing-card--featured::before {
    content: "Most popular";
    position: absolute;
    top: 0;
    left: 50%;
    transform: translate(-50%, -50%);
    background: var(--colorBrandBackground);
    color: var(--colorNeutralForegroundInverted);
    font-size: var(--fontSizeBase200);
    line-height: var(--lineHeightBase200);
    font-weight: var(--fontWeightSemibold);
    padding: var(--spacingVerticalXS) var(--spacingHorizontalM);
    border-radius: var(--borderRadiusCircular);
    text-transform: uppercase;
    letter-spacing: 0.06em;
}
.pricing-card__tier {
    font-size: var(--fontSizeBase400);
    line-height: var(--lineHeightBase400);
    font-weight: var(--fontWeightSemibold);
    margin: 0 0 var(--spacingVerticalXS);
    color: var(--colorBrandForeground1);
}
.pricing-card__price {
    display: flex;
    align-items: baseline;
    gap: var(--spacingHorizontalXS);
    margin-bottom: var(--spacingVerticalM);
}
.pricing-card__price-value {
    font-size: var(--fontSizeHero800);
    line-height: 1;
    font-weight: var(--fontWeightBold);
    color: var(--colorNeutralForeground1);
    letter-spacing: -0.02em;
}
.pricing-card__price-unit {
    font-size: var(--fontSizeBase300);
    color: var(--colorNeutralForeground3);
}
.pricing-card__summary {
    font-size: var(--fontSizeBase300);
    color: var(--colorNeutralForeground2);
    margin: 0 0 var(--spacingVerticalL);
}
.pricing-card__features {
    list-style: none;
    padding: 0;
    margin: 0 0 var(--spacingVerticalL);
    display: flex;
    flex-direction: column;
    gap: var(--spacingVerticalS);
    flex: 1;
}
.pricing-card__features li {
    display: flex;
    align-items: flex-start;
    gap: var(--spacingHorizontalS);
    font-size: var(--fontSizeBase300);
    color: var(--colorNeutralForeground2);
}
.pricing-card__features li::before {
    content: "✓";
    color: var(--statusSuccessForeground);
    font-weight: var(--fontWeightBold);
    flex-shrink: 0;
}

.testimonial-card {
    background: var(--colorNeutralBackground1);
    border: var(--strokeWidthThin) solid var(--colorNeutralStroke2);
    border-radius: var(--borderRadiusXLarge);
    padding: var(--spacingVerticalXL) var(--spacingHorizontalL);
    position: relative;
}
.testimonial-card::before {
    content: "\201C";
    position: absolute;
    top: var(--spacingVerticalS);
    left: var(--spacingHorizontalM);
    font-size: 64px;
    line-height: 1;
    color: var(--colorBrandBackgroundTint);
    font-family: Georgia, serif;
}
.testimonial-card__quote {
    font-size: var(--fontSizeBase400);
    line-height: 1.5;
    color: var(--colorNeutralForeground1);
    margin: 0 0 var(--spacingVerticalL);
    position: relative;
}
.testimonial-card__author {
    display: flex;
    align-items: center;
    gap: var(--spacingHorizontalM);
}
.testimonial-card__avatar {
    width: 40px; height: 40px;
    border-radius: var(--borderRadiusCircular);
    background: var(--colorBrandBackgroundTint);
    color: var(--colorBrandForeground1);
    display: flex; align-items: center; justify-content: center;
    font-weight: var(--fontWeightSemibold);
    flex-shrink: 0;
}
.testimonial-card__meta { font-size: var(--fontSizeBase200); }
.testimonial-card__name { font-weight: var(--fontWeightSemibold); color: var(--colorNeutralForeground1); }
.testimonial-card__role { color: var(--colorNeutralForeground3); }

/* ============================================================================
   21. How-it-works strip + step indicator + status timeline
   ============================================================================ */

.steps-strip {
    display: grid;
    grid-template-columns: repeat(auto-fit, minmax(220px, 1fr));
    gap: var(--spacingHorizontalXL);
    counter-reset: step;
}
.step-item {
    text-align: center;
    counter-increment: step;
}
.step-item__number {
    width: 56px; height: 56px;
    border-radius: var(--borderRadiusCircular);
    background: var(--gradientBrand);
    color: var(--colorNeutralForegroundInverted);
    display: flex; align-items: center; justify-content: center;
    margin: 0 auto var(--spacingVerticalM);
    font-size: var(--fontSizeBase500);
    font-weight: var(--fontWeightBold);
}
.step-item__number::before { content: counter(step); }
.step-item__title {
    font-size: var(--fontSizeBase400);
    font-weight: var(--fontWeightSemibold);
    margin: 0 0 var(--spacingVerticalS);
}
.step-item__body {
    font-size: var(--fontSizeBase300);
    line-height: var(--lineHeightBase300);
    color: var(--colorNeutralForeground2);
    margin: 0;
}

.stepper {
    display: flex;
    align-items: center;
    gap: var(--spacingHorizontalS);
    margin-bottom: var(--spacingVerticalXL);
}
.stepper__step {
    display: flex;
    align-items: center;
    gap: var(--spacingHorizontalS);
    color: var(--colorNeutralForeground3);
    font-size: var(--fontSizeBase300);
}
.stepper__step .step-num {
    width: 28px; height: 28px;
    border-radius: var(--borderRadiusCircular);
    background: var(--colorNeutralBackground3);
    color: var(--colorNeutralForeground3);
    display: flex; align-items: center; justify-content: center;
    font-size: var(--fontSizeBase200);
    font-weight: var(--fontWeightSemibold);
    flex-shrink: 0;
    transition: background var(--durationFast) var(--curveEasyEase),
                color var(--durationFast) var(--curveEasyEase);
}
.stepper__step.is-current .step-num,
.stepper__step.is-complete .step-num {
    background: var(--colorBrandBackground);
    color: var(--colorNeutralForegroundInverted);
}
.stepper__step.is-current { color: var(--colorNeutralForeground1); font-weight: var(--fontWeightSemibold); }
.stepper__step.is-complete { color: var(--colorNeutralForeground2); }
.stepper__step + .stepper__step::before {
    content: "";
    width: 32px;
    height: 1px;
    background: var(--colorNeutralStroke2);
    margin-inline-end: var(--spacingHorizontalS);
}

.status-timeline {
    list-style: none;
    padding: 0;
    margin: 0;
    position: relative;
}
.status-timeline::before {
    content: "";
    position: absolute;
    left: 13px;
    top: 14px;
    bottom: 14px;
    width: 2px;
    background: var(--colorNeutralStroke3);
}
.status-timeline li {
    position: relative;
    padding-inline-start: 44px;
    padding-bottom: var(--spacingVerticalL);
}
.status-timeline li:last-child { padding-bottom: 0; }
.status-timeline li::before {
    content: "";
    position: absolute;
    left: 6px;
    top: 4px;
    width: 16px; height: 16px;
    border-radius: var(--borderRadiusCircular);
    background: var(--colorNeutralBackground1);
    border: 2px solid var(--colorNeutralStroke2);
    z-index: 1;
}
.status-timeline li.is-complete::before {
    background: var(--statusSuccessForeground);
    border-color: var(--statusSuccessForeground);
}
.status-timeline li.is-current::before {
    background: var(--colorBrandBackground);
    border-color: var(--colorBrandBackground);
    box-shadow: 0 0 0 4px var(--colorBrandBackgroundTint);
    animation: pulse var(--durationUltraSlow) var(--curveEasyEase) infinite;
}

/* ============================================================================
   22. FAQ accordion (Alpine.js)
   ============================================================================ */

.faq-list {
    display: flex;
    flex-direction: column;
    gap: var(--spacingVerticalS);
}
.faq-item {
    background: var(--colorNeutralBackground1);
    border: var(--strokeWidthThin) solid var(--colorNeutralStroke2);
    border-radius: var(--borderRadiusLarge);
    overflow: hidden;
}
.faq-item__trigger {
    display: flex;
    align-items: center;
    justify-content: space-between;
    width: 100%;
    padding: var(--spacingVerticalM) var(--spacingHorizontalL);
    background: transparent;
    border: 0;
    font-family: inherit;
    font-size: var(--fontSizeBase300);
    line-height: var(--lineHeightBase300);
    font-weight: var(--fontWeightSemibold);
    color: var(--colorNeutralForeground1);
    text-align: start;
    cursor: pointer;
    transition: background var(--durationFast) var(--curveEasyEase);
}
.faq-item__trigger:hover { background: var(--colorNeutralBackgroundHover); }
.faq-item__icon {
    transition: transform var(--durationFast) var(--curveEasyEase);
    flex-shrink: 0;
}
.faq-item.is-open .faq-item__icon { transform: rotate(180deg); }
.faq-item__panel {
    padding: 0 var(--spacingHorizontalL) var(--spacingVerticalM);
    color: var(--colorNeutralForeground2);
    font-size: var(--fontSizeBase300);
    line-height: 1.55;
}
/* The accordion trigger is a primary touch target on every page that
   carries an FAQ (home + how-it-works). Guarantee a >=44px row height so
   it's thumb-friendly (refinement 4, 2026-06-09). It already pads to ~44px
   via VerticalM + line-height; pin it so a future copy/padding change
   can't shrink it under the threshold. */
.faq-item__trigger {
    min-block-size: 44px;
}

/* ============================================================================
   22b. Mobile tap-target + input-affordance audit  (refinement 4)

   wf_00e50379-e44 visual review, 2026-06-09. Below 600px, raise the
   interactive controls to a >=44px hit area (WCAG 2.5.5 target size) and
   guarantee form inputs paint a visible border + focus ring. CSS-only,
   on existing classes; desktop sizing is unchanged. Logical properties
   (min-block-size, padding-block) keep it RTL-safe.
   ============================================================================ */
@media (max-width: 600px) {
    /* Mobile nav drawer trigger — ensure the hamburger hit area clears
       44px without restructuring the 3-link nav. */
    .top-nav__menu-button {
        min-block-size: 44px;
        min-inline-size: 44px;
        align-items: center;
        justify-content: center;
    }
    /* Form controls: comfortable touch height + a guaranteed visible
       resting border (so inputs never read as borderless boxes on a
       phone). The Fluent focus ring is the box-shadow declared on
       :focus in §9; pin a clear resting border here. */
    .form-field input,
    .form-field select,
    .form-field textarea {
        min-block-size: 44px;
        border: var(--strokeWidthThin) solid var(--colorNeutralStroke1);
    }
    /* textarea is multi-line — height comes from its own min-height; just
       keep the visible border (no forced 44px single-line height). */
    .form-field textarea {
        min-block-size: 96px;
    }
}

/* ============================================================================
   23. Marketing top nav (.top-nav) + site footer (.site-footer)
   ============================================================================ */

.top-nav {
    position: sticky;
    top: 0;
    z-index: 50;
    background: color-mix(in srgb, var(--colorNeutralBackground1) 80%, transparent);
    backdrop-filter: var(--surfaceBlur);
    -webkit-backdrop-filter: var(--surfaceBlur);
    border-bottom: var(--strokeWidthThin) solid var(--colorNeutralStroke2);
    transition: background var(--durationFast) var(--curveEasyEase);
}
.top-nav__inner {
    display: flex;
    align-items: center;
    justify-content: space-between;
    height: 64px;
    gap: var(--spacingHorizontalL);
}
.top-nav__brand {
    display: inline-flex;
    align-items: center;
    gap: var(--spacingHorizontalS);
    font-size: var(--fontSizeBase400);
    font-weight: var(--fontWeightBold);
    color: var(--colorNeutralForeground1);
    text-decoration: none;
    letter-spacing: -0.01em;
}
/* ── Brand lockup (mark + wordmark) ──────────────────────────────────────
   Rendered by templates/polytxt/_brand_lockup.html in two variants.
   Header: detailed raster mark + two-tone wordmark on light surfaces.
   Footer: flat vector mark + single-tone wordmark on dark surfaces.
   Sizes are set here (not on the <img> attributes alone) so the lockup
   scales with its container's font-size context. */
/* The mark keeps its historical class name: it is pinned by
   tests_marketing_surface's component list AND by
   design_manifest.POLYTXT_COMPONENTS, both of which treat a missing
   selector as a deleted component. Only the paint changed (a CSS-drawn
   gradient square became the real logo image), so the name still describes
   the same thing. */
/* 31x32 is the design's size AND the raster master's own scale: the PNG is
   124x128, exactly 4x, so the mark now downsamples on a whole-pixel ratio.
   The previous 28x28 was neither — a 0.96875 aspect inside a square box
   letterboxed the art to 27.13x28.00 through `object-fit: contain`, i.e. the
   mark was rendering SMALLER than the box that reserved space for it.
   Nothing downstream can grow: `.top-nav__inner` is `height: 64px` and
   `.app-bar__inner` is `height: 56px`, both hard heights rather than
   min-heights, so `.hiw-sticky-cta`'s `inset-block-start: 72px` stays correct
   by construction. The two role=dialog drawers have no height cap and DO grow
   ~4px; measured, neither starts to scroll. */
.top-nav__brand-mark {
    width: 31px;
    height: 32px;
    object-fit: contain;
    flex: 0 0 auto;
}
/* The design's 64x25 wordmark is not reachable by scaling the inherited type:
   at 16px the word measures 55.91x20.00 (aspect 2.796) against the design's
   2.560, so the height is what is wrong, not the width. 18px/25px is the same
   treatment `.site-footer__wordmark` already carries from the same design.
   Absolute rather than inherited on purpose — the five include sites of
   _brand_lockup.html sat at two different sizes (16px under `.top-nav__brand`,
   14px under `.app-bar__brand` and `.onboarding-shell__brand`); one wordmark
   partial should render one wordmark. The existing `letter-spacing: -0.01em`
   already computes to the design's -0.18px at 18px and is left alone.
   The span is a flex item of an inline-flex parent, so it is blockified and
   its rect height IS its line-height. */
.brand-lockup__word {
    font-size: 18px;
    line-height: 25px;
    font-weight: var(--fontWeightBold);
    letter-spacing: -0.01em;
    color: inherit;
}
.brand-lockup__word-accent {
    /* Gradient ink on the "txt" half. background-clip:text needs a painted
       background, so the gradient IS the element's background and the
       glyphs punch through it.
       Deliberately NOT --gradientBrand: that token is a page-surface wash
       (two radial gradients fading to transparent over a diagonal base) and
       renders as pale, partly see-through ink at text size. */
    background: var(--gradientBrandInk);
    -webkit-background-clip: text;
    background-clip: text;
    color: transparent;
}
.brand-lockup__word--solid { color: inherit; }
.top-nav__brand:hover { text-decoration: none; color: var(--colorBrandForeground1); }
.top-nav__links {
    display: flex;
    gap: var(--spacingHorizontalL);
    align-items: center;
}
.top-nav__links a {
    color: var(--colorNeutralForeground2);
    text-decoration: none;
    font-size: var(--fontSizeBase300);
    font-weight: var(--fontWeightMedium);
    transition: color var(--durationFast) var(--curveEasyEase);
}
.top-nav__links a:hover { color: var(--colorNeutralForeground1); text-decoration: none; }
.top-nav__links a.is-active { color: var(--colorBrandForeground1); font-weight: var(--fontWeightSemibold); }
.top-nav__actions {
    display: flex;
    gap: var(--spacingHorizontalS);
    align-items: center;
}
.top-nav__menu-button {
    display: none;
    background: transparent;
    border: 0;
    padding: var(--spacingVerticalS);
    cursor: pointer;
    color: var(--colorNeutralForeground1);
    border-radius: var(--borderRadiusMedium);
}
.top-nav__menu-button:hover { background: var(--colorNeutralBackgroundHover); }

@media (max-width: 768px) {
    .top-nav__links { display: none; }
    .top-nav__menu-button { display: flex; }
    .top-nav__actions .auth-cta-text { display: none; }
}

/* Phones (≤480px): the action bar — language menu + auth CTA +
 * hamburger — overruns a ~375px viewport, pushing the menu button
 * off-screen (horizontal-overflow bug). Tuck the language menu into
 * the ≡ drawer (it already renders there via _top_nav_marketing.html's
 * variant="footer", like the main nav links) so the bar fits and the
 * CTA stays on one line.
 *
 * NB: this must target the CURRENT control class `.lang-menu`. The
 * pre-ADR-0065 `.lang-switcher` control (and its dead template) were
 * removed 2026-07; if a language control is ever renamed again, update
 * this selector in the SAME commit — a stale mobile-hide selector here
 * silently re-opens the 64px nav overflow the peek-viewer responsive
 * test caught (that was the .lang-switcher -> .lang-menu rename bug). */
@media (max-width: 480px) {
    .top-nav__actions .lang-menu { display: none; }
    .top-nav__actions .btn { white-space: nowrap; }
}

/* [Section 23 footer rules — REMOVED 2026-08-09]
   The four-column marketing footer was replaced by Figma 267:5418. Its rules
   are deleted rather than overridden: three class names survive into the new
   footer with different meanings, so leaving the old values meant every new
   number silently fought one nobody had written. Replacement: Section 37. */

/* ============================================================================
   [Section 24 — Theme toggle — REMOVED 2026-05-30]
   ----------------------------------------------------------------------------
   The Alpine.js theme toggle component and its `.theme-toggle` /
   `.theme-toggle__btn` styles have been removed alongside the dark-mode
   CSS. The template at `app/templates/polytxt/_theme_toggle.html` was
   also deleted. Will return as part of a dedicated dark-mode ADR.
   ============================================================================ */

/* ============================================================================
   25. App shell  (placeholder — fully populated in PR-2)
   ============================================================================ */

.app-bar {
    position: sticky;
    top: 0;
    z-index: 50;
    background: color-mix(in srgb, var(--colorNeutralBackground1) 80%, transparent);
    backdrop-filter: var(--surfaceBlur);
    -webkit-backdrop-filter: var(--surfaceBlur);
    border-bottom: var(--strokeWidthThin) solid var(--colorNeutralStroke2);
}
.app-bar__inner {
    display: flex;
    align-items: center;
    justify-content: space-between;
    height: 56px;
    gap: var(--spacingHorizontalL);
    padding: 0 var(--spacingHorizontalXL);
}
.app-bar__brand {
    display: inline-flex;
    align-items: center;
    gap: var(--spacingHorizontalS);
    font-weight: var(--fontWeightBold);
    color: var(--colorNeutralForeground1);
    text-decoration: none;
}
.app-bar__search {
    flex: 1;
    max-width: 480px;
}
.app-bar__actions {
    display: flex;
    gap: var(--spacingHorizontalXS);
    align-items: center;
}

.side-rail {
    width: 240px;
    flex-shrink: 0;
    padding: var(--spacingVerticalL) var(--spacingHorizontalM);
    border-inline-end: var(--strokeWidthThin) solid var(--colorNeutralStroke2);
    background: var(--colorNeutralBackground1);
}
.side-rail__group {
    list-style: none;
    padding: 0;
    margin: 0 0 var(--spacingVerticalL);
    display: flex;
    flex-direction: column;
    gap: var(--spacingVerticalXXS);
}
.side-rail__link {
    display: flex;
    align-items: center;
    gap: var(--spacingHorizontalM);
    padding: var(--spacingVerticalS) var(--spacingHorizontalM);
    border-radius: var(--borderRadiusMedium);
    color: var(--colorNeutralForeground2);
    text-decoration: none;
    font-size: var(--fontSizeBase300);
    font-weight: var(--fontWeightMedium);
    transition: background var(--durationFast) var(--curveEasyEase),
                color var(--durationFast) var(--curveEasyEase);
}
.side-rail__link:hover {
    background: var(--colorNeutralBackgroundHover);
    color: var(--colorNeutralForeground1);
    text-decoration: none;
}
.side-rail__link.is-active {
    background: var(--colorBrandBackgroundTint);
    color: var(--colorBrandForeground1);
}
.side-rail__link img,
.side-rail__link svg { width: 20px; height: 20px; flex-shrink: 0; }

/* ============================================================================
   26. Stat card  (placeholder — used by PR-2 dashboard)
   ============================================================================ */

.stat-card {
    background: var(--colorNeutralBackground1);
    border: var(--strokeWidthThin) solid var(--colorNeutralStroke2);
    border-radius: var(--borderRadiusLarge);
    padding: var(--spacingVerticalL) var(--spacingHorizontalL);
}
.stat-card__label {
    font-size: var(--fontSizeBase200);
    color: var(--colorNeutralForeground3);
    text-transform: uppercase;
    letter-spacing: 0.04em;
    margin-bottom: var(--spacingVerticalS);
    font-weight: var(--fontWeightSemibold);
}
.stat-card__value {
    font-size: var(--fontSizeHero700);
    line-height: 1;
    font-weight: var(--fontWeightBold);
    color: var(--colorNeutralForeground1);
    letter-spacing: -0.02em;
}
.stat-card__delta {
    font-size: var(--fontSizeBase200);
    margin-top: var(--spacingVerticalXS);
}
.stat-card__delta--up   { color: var(--statusSuccessForeground); }
.stat-card__delta--down { color: var(--statusDangerForeground); }

/* ============================================================================
   27. Modal / drawer / toast  (Alpine.js components)
   ============================================================================ */

.modal__backdrop {
    position: fixed;
    inset: 0;
    background: color-mix(in srgb, var(--black) 50%, transparent);
    backdrop-filter: blur(2px);
    z-index: 100;
    animation: fadeIn var(--durationFast) var(--curveDecelerate);
}
.modal {
    position: fixed;
    top: 50%;
    left: 50%;
    transform: translate(-50%, -50%);
    z-index: 101;
    background: var(--colorNeutralBackground1);
    border: var(--strokeWidthThin) solid var(--colorNeutralStroke2);
    border-radius: var(--borderRadiusXLarge);
    padding: var(--spacingVerticalXL) var(--spacingHorizontalXL);
    box-shadow: var(--shadow28);
    width: min(560px, calc(100vw - 32px));
    max-height: calc(100vh - 64px);
    overflow: auto;
    animation: revealUp var(--durationNormal) var(--curveDecelerate);
}
.modal__header {
    display: flex;
    align-items: center;
    justify-content: space-between;
    margin-bottom: var(--spacingVerticalM);
}
.modal__title {
    font-size: var(--fontSizeBase500);
    font-weight: var(--fontWeightSemibold);
    margin: 0;
}
.modal__close {
    background: transparent;
    border: 0;
    padding: var(--spacingVerticalXS);
    cursor: pointer;
    color: var(--colorNeutralForeground3);
    border-radius: var(--borderRadiusMedium);
}
.modal__close:hover { background: var(--colorNeutralBackgroundHover); color: var(--colorNeutralForeground1); }

.drawer {
    position: fixed;
    top: 0;
    right: 0;
    bottom: 0;
    width: min(360px, 100vw);
    z-index: 101;
    background: var(--colorNeutralBackground1);
    border-inline-start: var(--strokeWidthThin) solid var(--colorNeutralStroke2);
    box-shadow: var(--shadow28);
    overflow: auto;
    animation: drawerIn var(--durationNormal) var(--curveDecelerate);
}
.drawer--left {
    right: auto;
    left: 0;
    border-inline-start: 0;
    border-inline-end: var(--strokeWidthThin) solid var(--colorNeutralStroke2);
    animation: drawerInLeft var(--durationNormal) var(--curveDecelerate);
}
@keyframes drawerIn      { from { transform: translateX(100%); } to { transform: translateX(0); } }
@keyframes drawerInLeft  { from { transform: translateX(-100%); } to { transform: translateX(0); } }

/* Marketing drawer nav links. These carried their colour and text-decoration
   as INLINE attributes, which outrank every author rule — so they had no
   hover and could not be given one from here without !important. The pair now
   lives on this class (see _top_nav_marketing.html).
   --colorNeutralBackgroundHover is the RIGHT token on this bed: .drawer is
   --colorNeutralBackground1 (#ffffff), so #f0f0f0 is a proper 15-level step.
   That is the same token whose 0-delta collision on the #f0f0f0 segmented
   track is fixed elsewhere in this file — the token was never wrong, only its
   bed was. The pill is pulled back out by a negative inline margin so the
   label stays optically aligned with the rest of the drawer, and the whole
   box costs ~8px per link against a measured ~600px of drawer slack. */
.drawer__nav-link {
    color: var(--colorNeutralForeground1);
    text-decoration: none;
    padding-block: var(--spacingVerticalXS);
    padding-inline: var(--spacingHorizontalS);
    margin-inline: calc(-1 * var(--spacingHorizontalS));
    border-radius: var(--borderRadiusMedium);
    transition: background var(--durationFast) var(--curveEasyEase),
                color var(--durationFast) var(--curveEasyEase);
}
.drawer__nav-link:hover {
    background: var(--colorNeutralBackgroundHover);
    color: var(--colorBrandForeground1);
    text-decoration: none;
}

/* --- Modal body + footer (PR 15) ----------------------------------------- */
.modal__body {
    padding: var(--spacingVerticalL) var(--spacingHorizontalL);
    overflow-y: auto;
    flex: 1 1 auto;
}
.modal__body p:not(:last-child) { margin-bottom: var(--spacingVerticalS); }
.modal__footer {
    display: flex;
    justify-content: flex-end;
    gap: var(--spacingHorizontalS);
    padding: var(--spacingVerticalM) var(--spacingHorizontalL);
    border-top: var(--strokeWidthThin) solid var(--colorNeutralStroke2);
    background: var(--colorNeutralBackground2);
}

/* --- Drawer header + body (PR 15) ---------------------------------------- */
.drawer__header {
    display: flex;
    align-items: center;
    justify-content: space-between;
    padding: var(--spacingVerticalM) var(--spacingHorizontalL);
    border-bottom: var(--strokeWidthThin) solid var(--colorNeutralStroke2);
    flex-shrink: 0;
}
.drawer__title {
    font-size: var(--fontSizeBase400);
    font-weight: var(--fontWeightSemibold);
    color: var(--colorNeutralForeground1);
}
.drawer__body {
    overflow-y: auto;
    flex: 1 1 auto;
    padding: var(--spacingVerticalM) var(--spacingHorizontalL);
}
.drawer {
    /* Refine the parent rule: become a flex column so header + body
       layout cleanly with the body scrolling independently. */
    display: flex;
    flex-direction: column;
}

/* ============================================================================
   28. Empty state  +  loading skeleton
   ============================================================================ */

.empty-state {
    text-align: center;
    padding: var(--spacingVerticalXXXL) var(--spacingHorizontalL);
}
.empty-state__icon {
    width: 64px;
    height: 64px;
    border-radius: var(--borderRadiusCircular);
    background: var(--colorNeutralBackground3);
    color: var(--colorNeutralForeground3);
    display: inline-flex;
    align-items: center;
    justify-content: center;
    margin-bottom: var(--spacingVerticalL);
}
.empty-state__icon img,
.empty-state__icon svg { width: 32px; height: 32px; }
.empty-state__title {
    font-size: var(--fontSizeBase500);
    font-weight: var(--fontWeightSemibold);
    margin: 0 0 var(--spacingVerticalS);
}
.empty-state__body {
    color: var(--colorNeutralForeground2);
    margin: 0 auto var(--spacingVerticalL);
    max-width: 420px;
}

.skeleton {
    background: linear-gradient(90deg,
        var(--colorNeutralBackground3) 0%,
        var(--colorNeutralBackground4) 50%,
        var(--colorNeutralBackground3) 100%);
    background-size: 200% 100%;
    animation: gradientShift 1.5s ease-in-out infinite;
    border-radius: var(--borderRadiusMedium);
}

/* ============================================================================
   29. Pill / chip  (filter chips, language pills, etc.)
   ============================================================================ */

.chip {
    display: inline-flex;
    align-items: center;
    gap: var(--spacingHorizontalXS);
    padding: var(--spacingVerticalXXS) var(--spacingHorizontalM);
    background: var(--colorNeutralBackground2);
    color: var(--colorNeutralForeground2);
    border: var(--strokeWidthThin) solid var(--colorNeutralStroke2);
    border-radius: var(--borderRadiusCircular);
    font-size: var(--fontSizeBase200);
    font-weight: var(--fontWeightMedium);
    text-decoration: none;
    transition: background var(--durationFast) var(--curveEasyEase),
                color var(--durationFast) var(--curveEasyEase);
}
.chip:hover {
    background: var(--colorNeutralBackgroundHover);
    color: var(--colorNeutralForeground1);
    text-decoration: none;
}
.chip--brand {
    background: var(--colorBrandBackgroundTint);
    color: var(--colorBrandForeground1);
    border-color: transparent;
}
.chip--brand:hover { background: var(--colorBrandBackgroundTint); color: var(--colorBrandForeground2); }

/* Constrained, centred chip cluster (hero polish refinement 5,
   2026-06-09). Staged for the 40+-language strip on home (#languages),
   which currently sprawls edge-to-edge in an inline-styled div; a later
   template pass adds ``class="chip-cloud"`` to that strip to land the
   narrower, tidier cluster. Defining it now is inert for existing pages
   (no element uses it yet) so there's zero regression risk. */
.chip-cloud {
    display: flex;
    flex-wrap: wrap;
    gap: var(--spacingHorizontalS);
    justify-content: center;
    max-inline-size: 720px;
    margin-inline: auto;
}

/* ============================================================================
   30. Big CTA  (used in hero + final-CTA sections)
   ============================================================================ */

.btn--hero {
    min-height: 48px;
    padding: 0 var(--spacingHorizontalXL);
    font-size: var(--fontSizeBase400);
    border-radius: var(--borderRadiusLarge);
}
.btn--ghost {
    background: var(--colorNeutralBackground1);
    color: var(--colorNeutralForeground1);
    border-color: var(--colorNeutralStroke1);
}
.btn--ghost:hover { background: var(--colorNeutralBackgroundHover); text-decoration: none; }

/* ============================================================================
   30b. Closing CTA band  (.cta-band)

   ONE reusable "Ready when you are" band for the foot of every marketing
   page (wf_00e50379-e44 visual-review refinement 1, 2026-06-09). Today
   home / how-it-works / about each hand-roll their own closing band on
   different classes, and about's careers block breaks the pattern. This
   gives a single class so a template just writes ``<section class="cta-band">``.

   Uses the established closing-band treatment from home (the
   ``--gradientSubtle`` brand wash) so it standardises everywhere; the
   slightly more generous padding makes the close-out feel like an arrival.
   Reuses the .section-title / .section-subtitle / .hero__cta-row type and
   layout ramps so the band sits visually inside the existing system. The
   primary action is always the free side-by-side preview (free-AI-first,
   ADR 0057); per-page COPY stays per-page. Defining this does NOT change
   the existing hand-rolled bands (different classes) — zero regression to
   out-of-partition pages until a later pass swaps them. how-it-works is
   the reference adopter this wave.
   ============================================================================ */
.cta-band {
    background: var(--gradientSubtle);
    padding-block: clamp(48px, 7vw, 96px);
    text-align: center;
}
.cta-band__inner {
    max-inline-size: 640px;
    margin-inline: auto;
}
.cta-band__title {
    font-size: clamp(28px, 4vw, var(--fontSizeHero800));
    line-height: 1.15;
    font-weight: var(--fontWeightSemibold);
    letter-spacing: -0.02em;
    margin: 0 0 var(--spacingVerticalM);
    color: var(--colorNeutralForeground1);
}
.cta-band__subtitle {
    font-size: var(--fontSizeBase400);
    line-height: var(--lineHeightBase400);
    color: var(--colorNeutralForeground2);
    max-inline-size: 600px;
    margin: 0 auto var(--spacingVerticalXL);
}
.cta-band__actions {
    display: flex;
    gap: var(--spacingHorizontalM);
    justify-content: center;
    flex-wrap: wrap;
}

/* Home "Honest, fast, and built for the UK" trust strip — phone spacing.
   (wf_00e50379-e44 visual review, 2026-06-09.) At ≤390px the four value
   chips wrap onto multiple lines and sat a touch tight against the
   section title and each other, reading as a cramped pile rather than a
   deliberate row. This bumps the flex gap (which governs BOTH the
   between-chip and the wrapped-row vertical spacing) and adds a little
   breathing room below the title at narrow widths.

   Scoped CSS-only (no markup change): the trust strip is the inline-
   styled flex chip-row that is a direct child of a ``.section--alt``
   container — ``:not(#languages)`` excludes the dense languages cloud
   (the only other such strip), which has its own visual density. The
   ``:has(> .chip)`` guard means grids, step-strips, comparison tables,
   and ``.section-header`` blocks are never matched. RTL-safe: ``gap`` +
   ``padding-block-start`` are logical/axis-neutral. */
@media (max-width: 600px) {
    .section--alt:not(#languages) .container > div:has(> .chip) {
        gap: var(--spacingHorizontalM);
        padding-block-start: var(--spacingVerticalS);
    }
}

/* ============================================================================
   30c. How-it-works deep-dive layout  (.hiw-step, .hiw-sticky-cta)

   Refinement 2 (wf_00e50379-e44, 2026-06-09): re-architect the
   how-it-works prose wall into a scannable explainer. The numbered step
   strip up top reuses the existing .steps-strip / .step-item components;
   the four deep-dive sections become alternating two-column media/text
   rows (.hiw-step), and a slim sticky "Try it now — Free" bar
   (.hiw-sticky-cta) stays reachable on the long scroll. Defined here;
   placed in how_it_works.html by AGENT-HOWITWORKS.

   RTL-safe: the alternation uses the grid + ``order`` (logical), not
   ``float``/``left``/``right`` — so it flips correctly under dir="rtl".
   ============================================================================ */

/* Two-column media/text row. >768px: text + media side by side;
   <=768px: stacks with media above text. */
.hiw-step {
    display: grid;
    grid-template-columns: 1fr 1fr;
    align-items: center;
    gap: clamp(24px, 4vw, 56px);
}
.hiw-step + .hiw-step {
    margin-block-start: clamp(40px, 6vw, 80px);
}
/* Prose column — capped at a readable measure (~65ch). The single
   biggest readability win on the page. */
.hiw-step__body {
    max-inline-size: 65ch;
}
/* Media / illustration panel — a brand-tinted paper affordance that
   echoes the hero sheets (zero new raster assets; the existing
   polytxt/icons/*.svg sit inside it). */
.hiw-step__media {
    display: flex;
    align-items: center;
    justify-content: center;
    min-block-size: 220px;
    padding: clamp(24px, 5vw, 48px);
    background:
        radial-gradient(ellipse at top right,
            var(--colorBrandBackgroundTint) 0%, transparent 70%),
        var(--colorNeutralBackground2);
    border: var(--strokeWidthThin) solid var(--colorNeutralStroke2);
    border-radius: var(--borderRadiusXLarge);
    box-shadow: var(--shadow2);
}
.hiw-step__media img,
.hiw-step__media svg {
    width: clamp(64px, 12vw, 112px);
    height: auto;
    color: var(--colorBrandForeground1);
    opacity: 0.9;
}
/* Alternation: reverse rows put the media on the inline-end side. Uses
   ``order`` so the source order stays media-then-body (good for a11y +
   the stacked mobile view, which always shows media above text). */
.hiw-step--reverse .hiw-step__body { order: 2; }
.hiw-step--reverse .hiw-step__media { order: 1; }

@media (max-width: 768px) {
    .hiw-step {
        grid-template-columns: 1fr;
        gap: var(--spacingVerticalL);
    }
    /* Always media-above-text when stacked, regardless of --reverse. */
    .hiw-step__media,
    .hiw-step--reverse .hiw-step__media { order: 0; }
    .hiw-step__body,
    .hiw-step--reverse .hiw-step__body { order: 0; }
    .hiw-step__body { max-inline-size: none; }
}

/* Slim sticky "Try it now — Free" bar. position: sticky (NOT fixed) so it
   participates in flow and releases before the footer rather than
   overlapping it. Placed once near the top of the body by
   AGENT-HOWITWORKS; it sticks through the deep-dives.
   Browser-only behaviour — flagged for a Playwright assertion on its
   stuck position + the "Try it now" link target (2026-05-15 sticky-panel
   incident is this exact class). */
.hiw-sticky-cta {
    position: sticky;
    inset-block-start: 72px;   /* clears the 64px sticky .top-nav + a gap */
    z-index: 40;
    margin-block: var(--spacingVerticalL);
    display: flex;
    justify-content: center;    /* centre the inner pill on the page */
    pointer-events: none;       /* the bar itself is a passthrough; the
                                   inner pill re-enables pointer events so
                                   the long page scrolls normally around it */
}
.hiw-sticky-cta__inner {
    pointer-events: auto;
    display: inline-flex;
    margin-inline: auto;
    align-items: center;
    gap: var(--spacingHorizontalM);
    padding: var(--spacingVerticalS) var(--spacingHorizontalS)
             var(--spacingVerticalS) var(--spacingHorizontalL);
    background: color-mix(in srgb, var(--colorNeutralBackground1) 92%, transparent);
    backdrop-filter: var(--surfaceBlur);
    -webkit-backdrop-filter: var(--surfaceBlur);
    border: var(--strokeWidthThin) solid var(--colorNeutralStroke2);
    border-radius: var(--borderRadiusCircular);
    box-shadow: var(--shadow8);
}

/* ============================================================================
   31. Icon helpers  —  consistent inline icon sizing
   ============================================================================ */

.icon {
    display: inline-block;
    width: 20px;
    height: 20px;
    flex-shrink: 0;
    vertical-align: middle;
    color: currentColor;
}
.icon--small { width: 16px; height: 16px; }
.icon--large { width: 24px; height: 24px; }
.icon--xlarge { width: 32px; height: 32px; }

/* ============================================================================
   32. PR-2: Authenticated app-shell components (per ADR 0023)

   Command palette · today panel · sparkline · journal feed · floating
   help (FAB) · keyboard-hint pills · toast · side-rail collapse states ·
   reveal-on-hover · notifications drawer · account menu · onboarding
   layout · choice cards · toggle switch · status badges · skeleton.
   ============================================================================ */

:root { --curveSpring: cubic-bezier(0.34, 1.56, 0.64, 1); }

/* ── 32a · Command palette (Cmd+K) ──────────────────────────────────── */

.cmdk__backdrop {
    position: fixed; inset: 0; z-index: 200;
    background: color-mix(in srgb, var(--black) 40%, transparent);
    backdrop-filter: blur(4px); -webkit-backdrop-filter: blur(4px);
    animation: fadeIn var(--durationFast) var(--curveDecelerate);
}
.cmdk {
    position: fixed; top: 18vh; left: 50%; transform: translateX(-50%); z-index: 201;
    width: min(640px, calc(100vw - 32px));
    background: var(--colorNeutralBackground1);
    border: var(--strokeWidthThin) solid var(--colorNeutralStroke1);
    border-radius: var(--borderRadiusXLarge);
    box-shadow: var(--shadow64); overflow: hidden;
    animation: cmdkIn var(--durationNormal) var(--curveSpring);
}
@keyframes cmdkIn {
    from { opacity: 0; transform: translate(-50%, -8px) scale(0.96); }
    to   { opacity: 1; transform: translate(-50%, 0) scale(1); }
}
.cmdk__input-row {
    display: flex; align-items: center; gap: var(--spacingHorizontalM);
    padding: var(--spacingVerticalM) var(--spacingHorizontalL);
    border-bottom: var(--strokeWidthThin) solid var(--colorNeutralStroke3);
}
.cmdk__input-row .icon { color: var(--colorNeutralForeground3); flex-shrink: 0; }
.cmdk__input {
    flex: 1; min-width: 0; border: 0; background: transparent; outline: none; padding: 0;
    font-family: inherit; font-size: var(--fontSizeBase400); line-height: var(--lineHeightBase400);
    color: var(--colorNeutralForeground1);
}
.cmdk__input::placeholder { color: var(--colorNeutralForeground3); }
.cmdk__results {
    list-style: none; margin: 0; padding: var(--spacingVerticalS) 0;
    max-height: 50vh; overflow-y: auto;
}
.cmdk__group-label {
    padding: var(--spacingVerticalS) var(--spacingHorizontalL) var(--spacingVerticalXXS);
    font-size: var(--fontSizeBase200); font-weight: var(--fontWeightSemibold);
    color: var(--colorNeutralForeground3);
    text-transform: uppercase; letter-spacing: 0.06em;
}
.cmdk__item {
    display: flex; align-items: center; gap: var(--spacingHorizontalM);
    padding: var(--spacingVerticalS) var(--spacingHorizontalL);
    color: var(--colorNeutralForeground1); text-decoration: none; cursor: pointer;
    border: 0; background: transparent; width: 100%; text-align: start; font: inherit;
    transition: background var(--durationUltraFast) var(--curveEasyEase);
}
.cmdk__item.is-active,
.cmdk__item:hover { background: var(--colorBrandBackgroundTint); color: var(--colorBrandForeground1); text-decoration: none; }
.cmdk__item-icon {
    width: 32px; height: 32px; border-radius: var(--borderRadiusMedium);
    background: var(--colorNeutralBackground2); color: var(--colorNeutralForeground2);
    display: flex; align-items: center; justify-content: center; flex-shrink: 0;
}
.cmdk__item.is-active .cmdk__item-icon,
.cmdk__item:hover .cmdk__item-icon { background: var(--colorBrandBackground); color: var(--colorNeutralForegroundInverted); }
.cmdk__item-meta { flex: 1; min-width: 0; }
.cmdk__item-title { font-size: var(--fontSizeBase300); font-weight: var(--fontWeightMedium); overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.cmdk__item-subtitle { font-size: var(--fontSizeBase200); color: var(--colorNeutralForeground3); overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.cmdk__item.is-active .cmdk__item-subtitle,
.cmdk__item:hover .cmdk__item-subtitle { color: var(--colorBrandForeground2); }
.cmdk__footer {
    display: flex; align-items: center; justify-content: space-between; gap: var(--spacingHorizontalM);
    padding: var(--spacingVerticalS) var(--spacingHorizontalL);
    border-top: var(--strokeWidthThin) solid var(--colorNeutralStroke3);
    background: var(--colorNeutralBackground2);
    font-size: var(--fontSizeBase200); color: var(--colorNeutralForeground3);
}
.cmdk__empty { padding: var(--spacingVerticalXXL) var(--spacingHorizontalL); text-align: center; color: var(--colorNeutralForeground3); }

/* ── 32b · Keyboard-hint pills ──────────────────────────────────────── */

kbd, .kbd {
    display: inline-flex; align-items: center; justify-content: center;
    min-width: 22px; height: 22px; padding: 0 var(--spacingHorizontalXS);
    border-radius: var(--borderRadiusSmall);
    background: var(--colorNeutralBackground1);
    border: var(--strokeWidthThin) solid var(--colorNeutralStroke1);
    box-shadow: 0 1px 0 var(--colorNeutralStroke2);
    color: var(--colorNeutralForeground2);
    font-family: var(--fontFamilyMonospace); font-size: 11px; line-height: 1; font-weight: var(--fontWeightMedium);
}
.kbd-row { display: inline-flex; align-items: center; gap: var(--spacingHorizontalXS); }
.kbd-row .kbd-sep { color: var(--colorNeutralForeground3); font-size: var(--fontSizeBase200); }

/* ── 32c · Top-bar search input (opens cmdk on focus) ───────────────── */

.app-bar__search-input {
    display: flex; align-items: center; gap: var(--spacingHorizontalS); width: 100%;
    padding: 6px var(--spacingHorizontalM);
    background: var(--colorNeutralBackground2);
    border: var(--strokeWidthThin) solid var(--colorNeutralStroke2);
    border-radius: var(--borderRadiusCircular);
    color: var(--colorNeutralForeground3); font-size: var(--fontSizeBase300); cursor: pointer;
    transition: background var(--durationFast) var(--curveEasyEase),
                border-color var(--durationFast) var(--curveEasyEase);
}
.app-bar__search-input:hover { background: var(--colorNeutralBackground1); border-color: var(--colorNeutralStroke1); }
.app-bar__search-input .search-text { flex: 1; text-align: start; }
.app-bar__search-input .icon { color: var(--colorNeutralForeground3); flex-shrink: 0; }

/* ── 32d · Today panel (editorial hero on dashboard) ────────────────── */

.today-panel {
    position: relative; overflow: hidden;
    border-radius: var(--borderRadiusXLarge);
    padding: var(--spacingVerticalXXL) var(--spacingHorizontalXXL);
    background: var(--colorNeutralBackground1);
    border: var(--strokeWidthThin) solid var(--colorNeutralStroke2);
    margin-bottom: var(--spacingVerticalXL);
}
.today-panel::before {
    content: ""; position: absolute; inset: 0;
    background: var(--gradientHero); opacity: 0.6; pointer-events: none;
}
.today-panel::after {
    content: ""; position: absolute; inset: 0;
    background-image:
        linear-gradient(var(--colorNeutralStroke3) 1px, transparent 1px),
        linear-gradient(90deg, var(--colorNeutralStroke3) 1px, transparent 1px);
    background-size: 40px 40px; opacity: 0.18;
    mask-image: radial-gradient(ellipse at 80% 30%, black 20%, transparent 70%);
    pointer-events: none;
}
.today-panel__inner { position: relative; }
.today-panel__eyebrow {
    display: inline-flex; align-items: center; gap: var(--spacingHorizontalS);
    font-size: var(--fontSizeBase200); font-weight: var(--fontWeightSemibold);
    text-transform: uppercase; letter-spacing: 0.1em;
    color: var(--colorBrandForeground1); margin-bottom: var(--spacingVerticalM);
}
.today-panel__title {
    font-size: clamp(28px, 4vw, var(--fontSizeHero800));
    line-height: 1.1; font-weight: var(--fontWeightBold); letter-spacing: -0.02em;
    color: var(--colorNeutralForeground1);
    margin: 0 0 var(--spacingVerticalM); max-width: 680px;
}
.today-panel__title .accent {
    background: linear-gradient(135deg, var(--brand80), var(--brand100));
    -webkit-background-clip: text; background-clip: text; color: transparent;
}
.today-panel__sub {
    font-size: var(--fontSizeBase400); line-height: 1.5;
    color: var(--colorNeutralForeground2);
    margin: 0 0 var(--spacingVerticalL); max-width: 640px;
}
.today-panel__cta-row { display: flex; flex-wrap: wrap; gap: var(--spacingHorizontalM); }

/* ── 32e · Stat card extras + sparkline ─────────────────────────────── */

.stat-card .stat-card__value { font-variant-numeric: tabular-nums; letter-spacing: -0.02em; }
.stat-card__row {
    display: flex; align-items: baseline; justify-content: space-between;
    gap: var(--spacingHorizontalS); margin-bottom: var(--spacingVerticalS);
}
.stat-card__hint { font-size: var(--fontSizeBase200); color: var(--colorNeutralForeground3); margin: 0; }
.sparkline {
    display: inline-flex; align-items: flex-end; gap: 2px;
    height: 28px; width: 88px; margin-top: var(--spacingVerticalS);
}
.sparkline__bar {
    width: 4px; border-radius: var(--borderRadiusSmall);
    background: var(--colorBrandBackground);
    height: calc(var(--h, 0) * 100%); min-height: 2px;
    opacity: 0.6;
}
.sparkline__bar:last-child { opacity: 1; }

/* ── 32f · Journal-style activity feed ──────────────────────────────── */

.journal-feed { list-style: none; padding: 0; margin: 0; display: flex; flex-direction: column; gap: var(--spacingVerticalM); }
.journal-item {
    display: grid; grid-template-columns: 36px 1fr auto; gap: var(--spacingHorizontalM);
    align-items: start;
    padding: var(--spacingVerticalM) var(--spacingHorizontalM);
    border-radius: var(--borderRadiusLarge);
    border: var(--strokeWidthThin) solid transparent;
    transition: background var(--durationFast) var(--curveEasyEase),
                border-color var(--durationFast) var(--curveEasyEase);
}
.journal-item:hover { background: var(--colorNeutralBackground1); border-color: var(--colorNeutralStroke3); }
.journal-item__avatar {
    width: 36px; height: 36px; border-radius: var(--borderRadiusCircular);
    background: var(--colorBrandBackgroundTint); color: var(--colorBrandForeground1);
    display: flex; align-items: center; justify-content: center;
    font-weight: var(--fontWeightSemibold); font-size: var(--fontSizeBase300); flex-shrink: 0;
}
.journal-item__avatar img,
.journal-item__avatar svg { width: 18px; height: 18px; }
.journal-item__avatar--system { background: var(--colorNeutralBackground3); color: var(--colorNeutralForeground2); }
.journal-item__body { min-width: 0; }
.journal-item__title { font-size: var(--fontSizeBase300); line-height: 1.45; color: var(--colorNeutralForeground1); margin: 0; }
.journal-item__title strong { font-weight: var(--fontWeightSemibold); }
.journal-item__meta {
    font-size: var(--fontSizeBase200); color: var(--colorNeutralForeground3);
    margin-top: var(--spacingVerticalXXS);
    display: flex; flex-wrap: wrap; gap: var(--spacingHorizontalS); align-items: center;
}
.journal-item__time { font-size: var(--fontSizeBase200); color: var(--colorNeutralForeground3); font-variant-numeric: tabular-nums; white-space: nowrap; }

/* ── 32g · Floating help button (FAB) ───────────────────────────────── */

.help-fab {
    position: fixed; right: var(--spacingHorizontalXL); bottom: var(--spacingVerticalXL); z-index: 90;
    width: 52px; height: 52px; border-radius: var(--borderRadiusCircular);
    background: var(--gradientBrand); background-size: 200% 200%;
    color: var(--colorNeutralForegroundInverted);
    display: flex; align-items: center; justify-content: center;
    box-shadow: var(--shadow16); border: 0; cursor: pointer;
    transition: transform var(--durationFast) var(--curveEasyEase),
                box-shadow var(--durationFast) var(--curveEasyEase);
}
.help-fab:hover { transform: translateY(-2px); box-shadow: var(--shadow28); }

/* ── 32h · Toast ─────────────────────────────────────────────────────── */

.toast-stack {
    position: fixed; top: var(--spacingVerticalL); right: var(--spacingHorizontalL); z-index: 110;
    display: flex; flex-direction: column; gap: var(--spacingVerticalS); pointer-events: none;
}
.toast {
    display: flex; align-items: center; gap: var(--spacingHorizontalM);
    padding: var(--spacingVerticalS) var(--spacingHorizontalL);
    background: var(--colorNeutralBackground1);
    border: var(--strokeWidthThin) solid var(--colorNeutralStroke2);
    border-radius: var(--borderRadiusLarge);
    box-shadow: var(--shadow16); min-width: 280px; max-width: 420px; pointer-events: auto;
    animation: toastIn var(--durationNormal) var(--curveSpring);
}
@keyframes toastIn { from { opacity: 0; transform: translateY(-12px); } to { opacity: 1; transform: translateY(0); } }
.toast__icon {
    width: 24px; height: 24px; border-radius: var(--borderRadiusCircular);
    display: flex; align-items: center; justify-content: center; flex-shrink: 0;
    background: var(--colorBrandBackgroundTint); color: var(--colorBrandForeground1);
}
.toast--success .toast__icon { background: var(--statusSuccessBackground); color: var(--statusSuccessForeground); }
.toast--info    .toast__icon { background: var(--statusInfoBackground);    color: var(--statusInfoForeground); }
.toast--warning .toast__icon { background: var(--statusWarningBackground); color: var(--statusWarningForeground); }
.toast--error   .toast__icon { background: var(--statusDangerBackground);  color: var(--statusDangerForeground); }
.toast__body { flex: 1; font-size: var(--fontSizeBase300); color: var(--colorNeutralForeground1); }
.toast__close { background: transparent; border: 0; padding: var(--spacingVerticalXS); color: var(--colorNeutralForeground3); cursor: pointer; border-radius: var(--borderRadiusMedium); flex-shrink: 0; }
.toast__close:hover { background: var(--colorNeutralBackgroundHover); color: var(--colorNeutralForeground1); }

/* ── 32i · Side rail collapsed state ────────────────────────────────── */

.side-rail { transition: width var(--durationNormal) var(--curveEasyEase); }
.side-rail.is-collapsed { width: 64px; padding-inline-start: var(--spacingHorizontalS); padding-inline-end: var(--spacingHorizontalS); }
.side-rail.is-collapsed .side-rail__link { justify-content: center; padding-inline-start: var(--spacingHorizontalXS); padding-inline-end: var(--spacingHorizontalXS); }
.side-rail.is-collapsed .side-rail__link .label,
.side-rail.is-collapsed .side-rail__group-label { display: none; }
.side-rail__group-label {
    font-size: var(--fontSizeBase200); font-weight: var(--fontWeightSemibold);
    text-transform: uppercase; letter-spacing: 0.06em;
    color: var(--colorNeutralForeground3);
    margin: 0 var(--spacingHorizontalM) var(--spacingVerticalS);
}
.side-rail__toggle {
    background: transparent; border: 0; padding: var(--spacingVerticalS);
    margin: var(--spacingVerticalM) auto 0;
    color: var(--colorNeutralForeground3); cursor: pointer;
    border-radius: var(--borderRadiusMedium); display: block;
}
.side-rail__toggle:hover { background: var(--colorNeutralBackgroundHover); color: var(--colorNeutralForeground1); }

/* ── 32j · Reveal-on-hover (radial gradient follows cursor) ─────────── */

.reveal-card { position: relative; overflow: hidden; }
.reveal-card::before {
    content: ""; position: absolute; inset: 0; pointer-events: none;
    background: radial-gradient(
        320px circle at var(--mx, 50%) var(--my, 50%),
        color-mix(in srgb, var(--colorBrandBackgroundTint) 60%, transparent),
        transparent 60%);
    opacity: 0; transition: opacity var(--durationFast) var(--curveEasyEase);
}
.reveal-card:hover::before { opacity: 1; }

/* ── 32k · Notifications drawer ─────────────────────────────────────── */

.notif-drawer { width: min(420px, 100vw); padding: 0; display: flex; flex-direction: column; }
.notif-drawer__header {
    display: flex; align-items: center; justify-content: space-between;
    padding: var(--spacingVerticalL) var(--spacingHorizontalL);
    border-bottom: var(--strokeWidthThin) solid var(--colorNeutralStroke2);
}
.notif-drawer__title { font-size: var(--fontSizeBase400); font-weight: var(--fontWeightSemibold); margin: 0; }
.notif-drawer__list { list-style: none; padding: var(--spacingVerticalS) 0; margin: 0; overflow-y: auto; flex: 1; }
.notif-item {
    display: flex; flex-direction: column;
    padding: var(--spacingVerticalS) var(--spacingHorizontalL);
    border-inline-start: 3px solid transparent;
    color: inherit; text-decoration: none;
    transition: background var(--durationFast) var(--curveEasyEase),
                border-inline-start-color var(--durationFast) var(--curveEasyEase);
}
.notif-item:hover { background: var(--colorNeutralBackgroundHover); text-decoration: none; }
.notif-item.is-unread { background: var(--colorBrandBackgroundTint); border-inline-start-color: var(--colorBrandStroke1); }
.notif-item.is-unread:hover { background: var(--colorBrandBackgroundTint); }
.notif-item__title { font-size: var(--fontSizeBase300); line-height: 1.4; color: var(--colorNeutralForeground1); font-weight: var(--fontWeightMedium); }
.notif-item__meta {
    font-size: var(--fontSizeBase200); color: var(--colorNeutralForeground3);
    margin-top: var(--spacingVerticalXXS);
    display: flex; gap: var(--spacingHorizontalS); align-items: center;
}
.notif-item__kind-dot {
    display: inline-block; width: 8px; height: 8px;
    border-radius: var(--borderRadiusCircular);
    background: var(--colorBrandBackground); flex-shrink: 0;
}
.notif-item.is-read .notif-item__kind-dot { background: var(--colorNeutralStroke1); }

.notif-badge {
    position: absolute; top: -4px; right: -4px;
    min-width: 16px; height: 16px; padding: 0 4px;
    border-radius: var(--borderRadiusCircular);
    background: var(--statusDangerForeground); color: var(--white);
    font-size: 10px; line-height: 16px; font-weight: var(--fontWeightBold);
    text-align: center;
    border: 2px solid var(--colorNeutralBackground1);
}

/* ── 32l · Account menu (top-bar dropdown) ──────────────────────────── */

.account-menu {
    position: absolute; top: calc(100% + 8px); right: 0; width: 280px; z-index: 60;
    background: var(--colorNeutralBackground1);
    border: var(--strokeWidthThin) solid var(--colorNeutralStroke2);
    border-radius: var(--borderRadiusLarge);
    box-shadow: var(--shadow16); overflow: hidden;
    animation: dropIn var(--durationFast) var(--curveDecelerate);
}
@keyframes dropIn { from { opacity: 0; transform: translateY(-4px); } to { opacity: 1; transform: translateY(0); } }
.account-menu__header {
    padding: var(--spacingVerticalM) var(--spacingHorizontalL);
    border-bottom: var(--strokeWidthThin) solid var(--colorNeutralStroke3);
}
.account-menu__name { font-weight: var(--fontWeightSemibold); color: var(--colorNeutralForeground1); font-size: var(--fontSizeBase300); }
.account-menu__email {
    font-size: var(--fontSizeBase200); color: var(--colorNeutralForeground3);
    margin-top: var(--spacingVerticalXXS);
    overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
}
.account-menu__list { list-style: none; margin: 0; padding: var(--spacingVerticalXS) 0; }
.account-menu__item {
    display: flex; align-items: center; gap: var(--spacingHorizontalM);
    padding: var(--spacingVerticalS) var(--spacingHorizontalL);
    color: var(--colorNeutralForeground1); text-decoration: none;
    font-size: var(--fontSizeBase300);
    transition: background var(--durationFast) var(--curveEasyEase);
}
.account-menu__item:hover { background: var(--colorNeutralBackgroundHover); text-decoration: none; }
.account-menu__item .icon { color: var(--colorNeutralForeground3); }
.account-menu__sep { border: 0; border-top: var(--strokeWidthThin) solid var(--colorNeutralStroke3); margin: 0; }

.avatar-button {
    display: inline-flex; align-items: center; justify-content: center;
    width: 36px; height: 36px; border-radius: var(--borderRadiusCircular);
    background: var(--colorBrandBackgroundTint); color: var(--colorBrandForeground1);
    border: 0; cursor: pointer;
    font-weight: var(--fontWeightSemibold); font-family: inherit; font-size: var(--fontSizeBase300);
    position: relative;
    transition: background var(--durationFast) var(--curveEasyEase);
}
.avatar-button:hover { background: var(--colorBrandBackgroundSelected); }
.notif-button { position: relative; }

/* ── 32m · Onboarding step layout ───────────────────────────────────── */

.onboarding-shell {
    min-height: 100vh;
    background: radial-gradient(ellipse at top right, var(--colorBrandBackgroundTint) 0%, transparent 60%), var(--colorNeutralBackground2);
    display: flex; flex-direction: column;
}
.onboarding-shell__top {
    display: flex; align-items: center; justify-content: space-between;
    padding: var(--spacingVerticalL) var(--spacingHorizontalXL);
}
.onboarding-shell__brand {
    display: inline-flex; align-items: center; gap: var(--spacingHorizontalS);
    font-weight: var(--fontWeightBold); color: var(--colorNeutralForeground1); text-decoration: none;
}
.onboarding-shell__progress { display: flex; gap: var(--spacingHorizontalXS); }
.onboarding-shell__dot {
    width: 8px; height: 8px; border-radius: var(--borderRadiusCircular);
    background: var(--colorNeutralStroke2);
    transition: background var(--durationFast) var(--curveEasyEase),
                width var(--durationFast) var(--curveEasyEase);
}
.onboarding-shell__dot.is-current { background: var(--colorBrandBackground); width: 24px; }
.onboarding-shell__dot.is-complete { background: var(--colorBrandBackground); }

.onboarding-step {
    flex: 1; display: flex; align-items: center; justify-content: center;
    padding: var(--spacingVerticalXXL) var(--spacingHorizontalXL);
}
.onboarding-step__inner {
    max-width: 560px; width: 100%; text-align: center;
    animation: revealUp var(--durationSlow) var(--curveDecelerate);
}
.onboarding-step__icon {
    width: 64px; height: 64px; border-radius: var(--borderRadiusCircular);
    background: var(--colorBrandBackgroundTint); color: var(--colorBrandForeground1);
    display: inline-flex; align-items: center; justify-content: center;
    margin-bottom: var(--spacingVerticalL);
}
.onboarding-step__icon img,
.onboarding-step__icon svg { width: 32px; height: 32px; }
.onboarding-step__title {
    font-size: clamp(24px, 4vw, var(--fontSizeBase600));
    line-height: 1.2; font-weight: var(--fontWeightBold); letter-spacing: -0.01em;
    color: var(--colorNeutralForeground1); margin: 0 0 var(--spacingVerticalS);
}
.onboarding-step__sub {
    font-size: var(--fontSizeBase400); line-height: 1.5;
    color: var(--colorNeutralForeground2);
    margin: 0 auto var(--spacingVerticalXL); max-width: 440px;
}
.onboarding-step__form { text-align: start; margin-bottom: var(--spacingVerticalXL); }
.onboarding-step__actions {
    display: flex; align-items: center; justify-content: center; gap: var(--spacingHorizontalM); flex-wrap: wrap;
}
.onboarding-step__skip { color: var(--colorNeutralForeground3); text-decoration: none; font-size: var(--fontSizeBase300); }
.onboarding-step__skip:hover { color: var(--colorNeutralForeground2); text-decoration: underline; }

/* ── 32n · Choice grid (multi-select cards for onboarding step 2) ───── */

.choice-grid {
    display: grid; grid-template-columns: repeat(auto-fill, minmax(180px, 1fr));
    gap: var(--spacingHorizontalM);
}
.choice-card {
    position: relative; display: flex; flex-direction: column; align-items: center;
    gap: var(--spacingVerticalS);
    padding: var(--spacingVerticalL) var(--spacingHorizontalM);
    background: var(--colorNeutralBackground1);
    border: var(--strokeWidthThick) solid var(--colorNeutralStroke2);
    border-radius: var(--borderRadiusLarge);
    cursor: pointer; text-align: center;
    transition: border-color var(--durationFast) var(--curveEasyEase),
                background var(--durationFast) var(--curveEasyEase),
                transform var(--durationFast) var(--curveEasyEase);
}
.choice-card:hover { transform: translateY(-2px); border-color: var(--colorBrandStroke2); }
.choice-card input[type="checkbox"] { position: absolute; opacity: 0; pointer-events: none; }
.choice-card.is-checked,
.choice-card:has(input[type="checkbox"]:checked) {
    border-color: var(--colorBrandStroke1);
    background: var(--colorBrandBackgroundTint);
}
.choice-card__icon { width: 32px; height: 32px; color: var(--colorBrandForeground1); }
.choice-card__label {
    font-size: var(--fontSizeBase300); font-weight: var(--fontWeightSemibold);
    color: var(--colorNeutralForeground1);
}

/* ── 32o · Toggle switch ────────────────────────────────────────────── */

.toggle-row {
    display: flex; align-items: center; justify-content: space-between;
    gap: var(--spacingHorizontalL);
    padding: var(--spacingVerticalM) 0;
    border-bottom: var(--strokeWidthThin) solid var(--colorNeutralStroke3);
}
.toggle-row:last-child { border-bottom: 0; }
.toggle-row__text { flex: 1; }
.toggle-row__label { font-size: var(--fontSizeBase300); font-weight: var(--fontWeightSemibold); color: var(--colorNeutralForeground1); }
.toggle-row__hint { font-size: var(--fontSizeBase200); color: var(--colorNeutralForeground3); margin-top: var(--spacingVerticalXXS); }
.toggle { position: relative; display: inline-flex; width: 44px; height: 24px; flex-shrink: 0; cursor: pointer; }
.toggle input { opacity: 0; width: 0; height: 0; }
.toggle__slider {
    position: absolute; inset: 0;
    background: var(--colorNeutralStroke1); border-radius: var(--borderRadiusCircular);
    transition: background var(--durationFast) var(--curveEasyEase);
}
.toggle__slider::before {
    content: ""; position: absolute; left: 2px; top: 2px;
    width: 20px; height: 20px; border-radius: var(--borderRadiusCircular);
    background: var(--white); box-shadow: var(--shadow2);
    transition: transform var(--durationFast) var(--curveEasyEase);
}
.toggle input:checked + .toggle__slider { background: var(--colorBrandBackground); }
.toggle input:checked + .toggle__slider::before { transform: translateX(20px); }

/* ── 32p · Animated brand mark ──────────────────────────────────────── */

.brand-mark--animated {
    background: var(--gradientBrand); background-size: 200% 200%;
    animation: gradientShift 12s var(--curveEasyEase) infinite;
}

/* ── 32q · Quick-action band on dashboard ───────────────────────────── */

.quick-actions {
    display: grid; grid-template-columns: repeat(auto-fit, minmax(220px, 1fr));
    gap: var(--spacingHorizontalM); margin-top: var(--spacingVerticalXL);
}
.quick-action {
    display: flex; align-items: center; gap: var(--spacingHorizontalM);
    padding: var(--spacingVerticalL) var(--spacingHorizontalL);
    background: var(--colorNeutralBackground1);
    border: var(--strokeWidthThin) solid var(--colorNeutralStroke2);
    border-radius: var(--borderRadiusLarge);
    color: var(--colorNeutralForeground1); text-decoration: none;
    transition: transform var(--durationFast) var(--curveEasyEase),
                box-shadow var(--durationFast) var(--curveEasyEase),
                border-color var(--durationFast) var(--curveEasyEase);
}
.quick-action:hover { transform: translateY(-2px); box-shadow: var(--shadow8); border-color: var(--colorBrandStroke2); text-decoration: none; }
.quick-action__icon {
    width: 40px; height: 40px; border-radius: var(--borderRadiusMedium);
    background: var(--colorBrandBackgroundTint); color: var(--colorBrandForeground1);
    display: flex; align-items: center; justify-content: center; flex-shrink: 0;
}
.quick-action__title { font-size: var(--fontSizeBase300); font-weight: var(--fontWeightSemibold); }
.quick-action__sub { font-size: var(--fontSizeBase200); color: var(--colorNeutralForeground3); margin-top: var(--spacingVerticalXXS); }

/* ── 32r · Status badge (customer-safe states) ──────────────────────── */

.status-badge {
    display: inline-flex; align-items: center; gap: var(--spacingHorizontalXS);
    padding: 2px var(--spacingHorizontalS); border-radius: var(--borderRadiusCircular);
    font-size: var(--fontSizeBase200); line-height: 1.4; font-weight: var(--fontWeightSemibold);
    background: var(--colorNeutralBackground3); color: var(--colorNeutralForeground2);
}
.status-badge--received   { background: var(--statusInfoBackground);    color: var(--statusInfoForeground); }
.status-badge--progress   { background: var(--colorBrandBackgroundTint); color: var(--colorBrandForeground1); }
.status-badge--review     { background: var(--statusWarningBackground); color: var(--statusWarningForeground); }
.status-badge--almost     { background: var(--colorBrandBackgroundTint); color: var(--colorBrandForeground1); }
.status-badge--completed  { background: var(--statusSuccessBackground); color: var(--statusSuccessForeground); }
.status-badge--cancelled  { background: var(--statusDangerBackground);  color: var(--statusDangerForeground); }
.status-badge__dot { width: 6px; height: 6px; border-radius: var(--borderRadiusCircular); background: currentColor; flex-shrink: 0; }

/* ── 32s · Skeleton row ──────────────────────────────────────────────── */

.skeleton-row { height: 16px; border-radius: var(--borderRadiusSmall); margin: var(--spacingVerticalS) 0; }
.skeleton-row--short { width: 40%; }
.skeleton-row--medium { width: 65%; }
.skeleton-row--long { width: 90%; }

/* ── 32t · App-page footer (slim) ───────────────────────────────────── */

.app-footer {
    padding: var(--spacingVerticalL) var(--spacingHorizontalXL);
    border-top: var(--strokeWidthThin) solid var(--colorNeutralStroke3);
    color: var(--colorNeutralForeground3);
    font-size: var(--fontSizeBase200);
    display: flex; flex-wrap: wrap; justify-content: space-between; gap: var(--spacingHorizontalM);
}
.app-footer a { color: var(--colorNeutralForeground3); text-decoration: none; }
.app-footer a:hover { color: var(--colorBrandForeground1); }

/* ── 32u · Mobile + responsive ──────────────────────────────────────── */

.app-bar__menu-button { display: none; background: transparent; border: 0; padding: var(--spacingVerticalS); cursor: pointer; color: var(--colorNeutralForeground1); border-radius: var(--borderRadiusMedium); }
.app-bar__menu-button:hover { background: var(--colorNeutralBackgroundHover); }
@media (max-width: 768px) {
    .side-rail { display: none; }
    .app-bar__menu-button { display: inline-flex; }
    .app-bar__search { max-width: none; }
}

/* ============================================================================
   33. PR-3: Document library

   /library/ — grid + list of every Document the user owns across
   every job, with HTMX-driven filters + search and a single-document
   detail view.

   Components:
     33a · library page header + filter bar
     33b · doc card (grid mode)
     33c · doc list row (list mode)
     33d · view toggle (grid / list)
     33e · doc thumbnail (placeholder content-type icon today; real
            <img> from PageImageSnapshot in a follow-up)
     33f · library detail page (single doc)
   ============================================================================ */

/* ── 33a · Library page header + filter bar ─────────────────────────── */

.library-header {
    display: flex;
    align-items: end;
    justify-content: space-between;
    gap: var(--spacingHorizontalL);
    flex-wrap: wrap;
    margin-bottom: var(--spacingVerticalL);
}
.library-header__title { margin: 0; }
.library-header__subtitle {
    font-size: var(--fontSizeBase300);
    color: var(--colorNeutralForeground3);
    margin: var(--spacingVerticalXXS) 0 0;
}

.library-toolbar {
    display: flex;
    align-items: center;
    gap: var(--spacingHorizontalM);
    margin-bottom: var(--spacingVerticalL);
    flex-wrap: wrap;
}
.library-toolbar__search {
    flex: 1 1 280px;
    min-width: 0;
    position: relative;
}
.library-toolbar__search input {
    width: 100%;
    padding: var(--spacingVerticalS) var(--spacingHorizontalL) var(--spacingVerticalS) calc(var(--spacingHorizontalL) + 24px);
    background: var(--colorNeutralBackground1);
    border: var(--strokeWidthThin) solid var(--colorNeutralStroke1);
    border-radius: var(--borderRadiusCircular);
    font-family: inherit;
    font-size: var(--fontSizeBase300);
    color: var(--colorNeutralForeground1);
    outline: none;
    transition: border-color var(--durationFast) var(--curveEasyEase),
                box-shadow var(--durationFast) var(--curveEasyEase);
}
.library-toolbar__search input::placeholder { color: var(--colorNeutralForeground3); }
.library-toolbar__search input:focus {
    border-color: var(--colorBrandStroke1);
    box-shadow: 0 0 0 1px var(--colorBrandStroke1);
}
.library-toolbar__search-icon {
    position: absolute;
    left: var(--spacingHorizontalM);
    top: 50%;
    transform: translateY(-50%);
    color: var(--colorNeutralForeground3);
    pointer-events: none;
}

.library-filters {
    display: flex;
    flex-wrap: wrap;
    gap: var(--spacingHorizontalXS);
    align-items: center;
}
.library-filter-select {
    appearance: none;
    -webkit-appearance: none;
    padding: var(--spacingVerticalXS) calc(var(--spacingHorizontalL) + 16px) var(--spacingVerticalXS) var(--spacingHorizontalM);
    background: var(--colorNeutralBackground1);
    border: var(--strokeWidthThin) solid var(--colorNeutralStroke1);
    border-radius: var(--borderRadiusCircular);
    font-family: inherit;
    font-size: var(--fontSizeBase200);
    color: var(--colorNeutralForeground2);
    cursor: pointer;
    background-image: linear-gradient(45deg, transparent 50%, var(--colorNeutralForeground3) 50%),
                      linear-gradient(135deg, var(--colorNeutralForeground3) 50%, transparent 50%);
    background-position:
        calc(100% - 14px) 50%,
        calc(100% - 9px) 50%;
    background-size: 5px 5px, 5px 5px;
    background-repeat: no-repeat;
}
.library-filter-select:hover { border-color: var(--colorNeutralStrokeAccessible); }
.library-filter-select:focus { outline: none; border-color: var(--colorBrandStroke1); box-shadow: 0 0 0 1px var(--colorBrandStroke1); }

/* ── 33b · View toggle (grid / list) ───────────────────────────────── */

.library-view-toggle {
    display: inline-flex;
    background: var(--colorNeutralBackground2);
    border: var(--strokeWidthThin) solid var(--colorNeutralStroke2);
    border-radius: var(--borderRadiusCircular);
    padding: 2px;
}
.library-view-toggle a {
    display: inline-flex;
    align-items: center;
    justify-content: center;
    width: 32px;
    height: 28px;
    border-radius: var(--borderRadiusCircular);
    color: var(--colorNeutralForeground3);
    text-decoration: none;
    transition: background var(--durationFast) var(--curveEasyEase),
                color var(--durationFast) var(--curveEasyEase);
}
.library-view-toggle a:hover { color: var(--colorNeutralForeground1); }
.library-view-toggle a.is-active {
    background: var(--colorNeutralBackground1);
    color: var(--colorBrandForeground1);
    box-shadow: var(--shadow2);
}

/* ── 33c · Library counts row ──────────────────────────────────────── */

.library-count {
    font-size: var(--fontSizeBase200);
    color: var(--colorNeutralForeground3);
    margin-bottom: var(--spacingVerticalM);
    font-variant-numeric: tabular-nums;
}
.library-count strong {
    color: var(--colorNeutralForeground1);
    font-weight: var(--fontWeightSemibold);
}

/* ── 33d · Grid mode (doc cards) ───────────────────────────────────── */

.library-grid {
    display: grid;
    grid-template-columns: repeat(auto-fill, minmax(220px, 1fr));
    gap: var(--spacingHorizontalL);
}
.doc-card {
    display: flex;
    flex-direction: column;
    background: var(--colorNeutralBackground1);
    border: var(--strokeWidthThin) solid var(--colorNeutralStroke2);
    border-radius: var(--borderRadiusLarge);
    overflow: hidden;
    text-decoration: none;
    color: inherit;
    transition: transform var(--durationFast) var(--curveEasyEase),
                box-shadow var(--durationFast) var(--curveEasyEase),
                border-color var(--durationFast) var(--curveEasyEase);
    position: relative;
}
.doc-card:hover {
    transform: translateY(-2px);
    box-shadow: var(--shadow8);
    border-color: var(--colorBrandStroke2);
    text-decoration: none;
}

.doc-card__thumb {
    position: relative;
    aspect-ratio: 4 / 3;
    background: linear-gradient(135deg, var(--colorNeutralBackground3) 0%, var(--colorNeutralBackground4) 100%);
    display: flex;
    align-items: center;
    justify-content: center;
    overflow: hidden;
}
.doc-card__thumb-icon {
    width: 56px;
    height: 56px;
    color: var(--colorBrandForeground1);
    opacity: 0.85;
}
.doc-card__type-pill {
    position: absolute;
    top: var(--spacingVerticalS);
    left: var(--spacingHorizontalS);
    background: var(--colorNeutralBackground1);
    color: var(--colorNeutralForeground2);
    border: var(--strokeWidthThin) solid var(--colorNeutralStroke2);
    border-radius: var(--borderRadiusCircular);
    padding: 2px var(--spacingHorizontalS);
    font-size: 11px;
    font-weight: var(--fontWeightSemibold);
    box-shadow: var(--shadow2);
}

.doc-card__body {
    padding: var(--spacingVerticalM) var(--spacingHorizontalL);
    display: flex;
    flex-direction: column;
    gap: var(--spacingVerticalXXS);
    flex: 1;
}
.doc-card__name {
    font-size: var(--fontSizeBase300);
    line-height: 1.4;
    font-weight: var(--fontWeightSemibold);
    color: var(--colorNeutralForeground1);
    overflow: hidden;
    text-overflow: ellipsis;
    display: -webkit-box;
    -webkit-line-clamp: 2;
    -webkit-box-orient: vertical;
}
.doc-card__meta {
    font-size: var(--fontSizeBase200);
    line-height: var(--lineHeightBase200);
    color: var(--colorNeutralForeground3);
}
.doc-card__job-link {
    font-size: var(--fontSizeBase200);
    color: var(--colorBrandForegroundLink);
    text-decoration: none;
    margin-top: auto;
    padding-top: var(--spacingVerticalS);
}
.doc-card__job-link:hover { text-decoration: underline; }

/* ── 33e · List mode ────────────────────────────────────────────────── */

.library-list {
    display: flex;
    flex-direction: column;
    background: var(--colorNeutralBackground1);
    border: var(--strokeWidthThin) solid var(--colorNeutralStroke2);
    border-radius: var(--borderRadiusLarge);
    overflow: hidden;
}
.library-list__header,
.doc-row {
    display: grid;
    grid-template-columns: 32px minmax(220px, 2fr) 1fr 1fr 120px 90px;
    gap: var(--spacingHorizontalM);
    align-items: center;
    padding: var(--spacingVerticalS) var(--spacingHorizontalL);
}
.library-list__header {
    background: var(--colorNeutralBackground2);
    border-bottom: var(--strokeWidthThin) solid var(--colorNeutralStroke3);
    font-size: var(--fontSizeBase200);
    line-height: var(--lineHeightBase200);
    font-weight: var(--fontWeightSemibold);
    color: var(--colorNeutralForeground3);
    text-transform: uppercase;
    letter-spacing: 0.04em;
}
.doc-row {
    border-bottom: var(--strokeWidthThin) solid var(--colorNeutralStroke3);
    color: inherit;
    text-decoration: none;
    transition: background var(--durationFast) var(--curveEasyEase);
}
.doc-row:last-child { border-bottom: 0; }
.doc-row:hover { background: var(--colorNeutralBackgroundHover); text-decoration: none; }

.doc-row__icon {
    width: 32px;
    height: 32px;
    border-radius: var(--borderRadiusMedium);
    background: var(--colorBrandBackgroundTint);
    color: var(--colorBrandForeground1);
    display: flex;
    align-items: center;
    justify-content: center;
    flex-shrink: 0;
}
.doc-row__name {
    font-weight: var(--fontWeightMedium);
    color: var(--colorNeutralForeground1);
    overflow: hidden;
    text-overflow: ellipsis;
    white-space: nowrap;
}
.doc-row__job {
    font-size: var(--fontSizeBase200);
    color: var(--colorNeutralForeground2);
    overflow: hidden;
    text-overflow: ellipsis;
    white-space: nowrap;
}
.doc-row__type {
    font-size: var(--fontSizeBase200);
    color: var(--colorNeutralForeground3);
    overflow: hidden;
    text-overflow: ellipsis;
    white-space: nowrap;
}
.doc-row__date,
.doc-row__size {
    font-size: var(--fontSizeBase200);
    color: var(--colorNeutralForeground3);
    font-variant-numeric: tabular-nums;
    text-align: end;
}

@media (max-width: 768px) {
    .library-list__header { display: none; }
    .doc-row {
        grid-template-columns: 32px 1fr 80px;
        grid-template-rows: auto auto;
        row-gap: var(--spacingVerticalXXS);
    }
    .doc-row__icon { grid-row: 1 / span 2; }
    .doc-row__name { grid-column: 2 / 3; }
    .doc-row__job { grid-column: 2 / 3; grid-row: 2 / 3; }
    .doc-row__type { display: none; }
    .doc-row__date { display: none; }
    .doc-row__size { grid-column: 3 / 4; grid-row: 1 / span 2; }
}

/* ── 33f · Library detail (single document) ─────────────────────────── */

.library-detail {
    display: grid;
    grid-template-columns: minmax(0, 2fr) minmax(0, 1fr);
    gap: var(--spacingHorizontalXXL);
    align-items: start;
}
@media (max-width: 768px) {
    .library-detail { grid-template-columns: 1fr; }
}
.library-detail__hero {
    background: linear-gradient(135deg, var(--colorBrandBackgroundTint) 0%, var(--colorNeutralBackground1) 100%);
    border: var(--strokeWidthThin) solid var(--colorNeutralStroke2);
    border-radius: var(--borderRadiusXLarge);
    padding: var(--spacingVerticalXXL) var(--spacingHorizontalXXL);
    text-align: center;
}
.library-detail__hero-icon {
    width: 96px;
    height: 96px;
    color: var(--colorBrandForeground1);
    margin-bottom: var(--spacingVerticalL);
}
.library-detail__filename {
    font-size: var(--fontSizeBase500);
    line-height: 1.3;
    font-weight: var(--fontWeightBold);
    color: var(--colorNeutralForeground1);
    word-break: break-all;
    margin: 0 0 var(--spacingVerticalS);
}
.library-detail__type {
    color: var(--colorBrandForeground1);
    font-weight: var(--fontWeightSemibold);
    text-transform: uppercase;
    letter-spacing: 0.06em;
    font-size: var(--fontSizeBase200);
}

.library-detail__sidebar { display: flex; flex-direction: column; gap: var(--spacingVerticalL); }

/* ── 33g · Empty state when filters return nothing ──────────────────── */

.library-empty {
    text-align: center;
    padding: var(--spacingVerticalXXXL) var(--spacingHorizontalL);
    background: var(--colorNeutralBackground1);
    border: var(--strokeWidthThin) dashed var(--colorNeutralStroke2);
    border-radius: var(--borderRadiusLarge);
}

/* ============================================================================
   34. PR-4: Requests rework — list + detail with status stepper hero

   /requests/ — customer-facing list (replaces the legacy /account/
                "your translation requests" section)
   /requests/<reference>/ — single-request detail with a horizontal
                customer-stage stepper, two-column body
   ============================================================================ */

/* ── 34a · Stepper hero panel on /requests/<ref>/ ───────────────────── */

.stepper-hero {
    position: relative;
    overflow: hidden;
    border-radius: var(--borderRadiusXLarge);
    padding: var(--spacingVerticalXXL) var(--spacingHorizontalXXL);
    background: var(--colorNeutralBackground1);
    border: var(--strokeWidthThin) solid var(--colorNeutralStroke2);
    margin-bottom: var(--spacingVerticalXL);
}
.stepper-hero::before {
    content: "";
    position: absolute;
    inset: 0;
    background: var(--gradientHero);
    opacity: 0.4;
    pointer-events: none;
}
.stepper-hero__inner { position: relative; }

.stepper-hero__eyebrow {
    display: inline-flex;
    align-items: center;
    gap: var(--spacingHorizontalS);
    font-size: var(--fontSizeBase200);
    font-weight: var(--fontWeightSemibold);
    text-transform: uppercase;
    letter-spacing: 0.08em;
    color: var(--colorBrandForeground1);
    margin-bottom: var(--spacingVerticalS);
}
.stepper-hero__title {
    font-size: clamp(24px, 3.2vw, var(--fontSizeBase600));
    line-height: 1.2;
    font-weight: var(--fontWeightBold);
    letter-spacing: -0.01em;
    margin: 0 0 var(--spacingVerticalS);
    color: var(--colorNeutralForeground1);
}
.stepper-hero__reference {
    font-family: var(--fontFamilyMonospace);
    color: var(--colorNeutralForeground3);
    font-size: var(--fontSizeBase300);
    margin: 0 0 var(--spacingVerticalL);
}

.stepper-hero__cancelled {
    background: var(--statusDangerBackground);
    color: var(--statusDangerForeground);
    border: var(--strokeWidthThin) solid var(--statusDangerBorder);
    border-radius: var(--borderRadiusLarge);
    padding: var(--spacingVerticalM) var(--spacingHorizontalL);
    display: flex;
    align-items: center;
    gap: var(--spacingHorizontalM);
    font-size: var(--fontSizeBase300);
}

/* The stepper itself. Reuses the .stepper / .stepper__step base from
   section 21 with PR-4 adjustments for visual prominence in the hero. */
.stepper-hero .stepper {
    margin: 0;
    flex-wrap: wrap;
    row-gap: var(--spacingVerticalS);
}
.stepper-hero .stepper__step .step-num {
    transition: transform var(--durationFast) var(--curveEasyEase);
}
.stepper-hero .stepper__step.is-current .step-num {
    transform: scale(1.1);
    box-shadow: 0 0 0 4px var(--colorBrandBackgroundTint);
    animation: pulse var(--durationUltraSlow) var(--curveEasyEase) infinite;
}

/* ── 34b · Two-column body for the detail page ──────────────────────── */

.request-detail {
    display: grid;
    grid-template-columns: minmax(0, 2fr) minmax(0, 1fr);
    gap: var(--spacingHorizontalXXL);
    align-items: start;
}
@media (max-width: 768px) {
    .request-detail { grid-template-columns: 1fr; }
}

.request-detail__main { min-width: 0; }
.request-detail__main h2 {
    margin: var(--spacingVerticalXL) 0 var(--spacingVerticalM);
    font-size: var(--fontSizeBase400);
}
.request-detail__main h2:first-child { margin-top: 0; }

.request-detail__sidebar {
    display: flex;
    flex-direction: column;
    gap: var(--spacingVerticalL);
}

/* ── 34c · Doc preview row inside request detail ────────────────────── */

.doc-preview-row {
    display: grid;
    grid-template-columns: 32px minmax(0, 1fr) auto;
    gap: var(--spacingHorizontalM);
    align-items: center;
    padding: var(--spacingVerticalS) var(--spacingHorizontalM);
    border-radius: var(--borderRadiusMedium);
    color: inherit;
    text-decoration: none;
    transition: background var(--durationFast) var(--curveEasyEase);
}
.doc-preview-row:hover {
    background: var(--colorNeutralBackgroundHover);
    text-decoration: none;
}
.doc-preview-row__icon {
    width: 32px;
    height: 32px;
    border-radius: var(--borderRadiusMedium);
    background: var(--colorBrandBackgroundTint);
    color: var(--colorBrandForeground1);
    display: flex;
    align-items: center;
    justify-content: center;
}
.doc-preview-row__name {
    font-weight: var(--fontWeightMedium);
    color: var(--colorNeutralForeground1);
    overflow: hidden;
    text-overflow: ellipsis;
    white-space: nowrap;
}
.doc-preview-row__meta {
    font-size: var(--fontSizeBase200);
    color: var(--colorNeutralForeground3);
    margin-top: 2px;
    overflow: hidden;
    text-overflow: ellipsis;
    white-space: nowrap;
}
.doc-preview-row__size {
    font-size: var(--fontSizeBase200);
    color: var(--colorNeutralForeground3);
    font-variant-numeric: tabular-nums;
}

/* ── 34d · Filter chips row (request list + reusable) ───────────────── */

.filter-chip-row {
    display: flex;
    flex-wrap: wrap;
    gap: var(--spacingHorizontalXS);
    margin-bottom: var(--spacingVerticalL);
}
.filter-chip {
    display: inline-flex;
    align-items: center;
    gap: var(--spacingHorizontalXS);
    padding: 4px var(--spacingHorizontalM);
    background: var(--colorNeutralBackground2);
    color: var(--colorNeutralForeground2);
    border: var(--strokeWidthThin) solid var(--colorNeutralStroke2);
    border-radius: var(--borderRadiusCircular);
    font-size: var(--fontSizeBase200);
    font-weight: var(--fontWeightMedium);
    text-decoration: none;
    cursor: pointer;
    transition: background var(--durationFast) var(--curveEasyEase),
                color var(--durationFast) var(--curveEasyEase),
                border-color var(--durationFast) var(--curveEasyEase);
}
.filter-chip:hover {
    background: var(--colorNeutralBackgroundHover);
    color: var(--colorNeutralForeground1);
    text-decoration: none;
}
.filter-chip.is-active {
    background: var(--colorBrandBackground);
    color: var(--colorNeutralForegroundInverted);
    border-color: var(--colorBrandBackground);
}

/* ── 34e · Request list rows (card-link style, uses card patterns) ──── */

.requests-list {
    display: flex;
    flex-direction: column;
    gap: var(--spacingVerticalS);
}
.request-row {
    display: grid;
    grid-template-columns: minmax(0, 1fr) auto;
    gap: var(--spacingHorizontalM);
    align-items: center;
    background: var(--colorNeutralBackground1);
    border: var(--strokeWidthThin) solid var(--colorNeutralStroke2);
    border-radius: var(--borderRadiusLarge);
    padding: var(--spacingVerticalM) var(--spacingHorizontalL);
    text-decoration: none;
    color: inherit;
    transition: transform var(--durationFast) var(--curveEasyEase),
                box-shadow var(--durationFast) var(--curveEasyEase),
                border-color var(--durationFast) var(--curveEasyEase);
}
.request-row:hover {
    transform: translateY(-1px);
    box-shadow: var(--shadow4);
    border-color: var(--colorBrandStroke2);
    text-decoration: none;
}
.request-row__main { min-width: 0; }
.request-row__title {
    font-size: var(--fontSizeBase300);
    line-height: 1.3;
    font-weight: var(--fontWeightSemibold);
    color: var(--colorNeutralForeground1);
    overflow: hidden;
    text-overflow: ellipsis;
    white-space: nowrap;
}
.request-row__meta {
    font-size: var(--fontSizeBase200);
    color: var(--colorNeutralForeground3);
    margin-top: var(--spacingVerticalXXS);
    display: flex;
    flex-wrap: wrap;
    gap: var(--spacingHorizontalS);
    align-items: center;
}
.request-row__meta .ref {
    font-family: var(--fontFamilyMonospace);
    color: var(--colorNeutralForeground2);
}

/* ──────────────────────────────────────────────────────────────────────
 * Marketing — Fluent-2 + SEO rebrand components
 *   - .legal-summary        — plain-English summary box atop legal pages
 *   - .legal-toc            — anchor-link table of contents on legal pages
 *   - .step-eyebrow         — "Step 1" / "Step 2" eyebrow on how-it-works
 *   - .comparison-table     — vs Google Translate / agency comparison grid
 *   - .use-case-card        — "UK visa", "NHS", etc. card on pricing page
 *   - .diff-card            — "What makes us different" card pair
 * ────────────────────────────────────────────────────────────────────── */

/* Plain-English summary box at top of /privacy + /terms. Trust signal +
 * featured-snippet bait. Visually distinct from the legal body so the
 * reader knows this is the TL;DR, not the binding text. */
.legal-summary {
    background: var(--colorNeutralBackground2);
    border-inline-start: 3px solid var(--colorBrandStroke1);
    border-radius: var(--borderRadiusMedium);
    padding: var(--spacingVerticalL) var(--spacingHorizontalL);
    margin: var(--spacingVerticalL) 0 var(--spacingVerticalXL);
}
.legal-summary__title {
    margin: 0 0 var(--spacingVerticalS);
    font-size: var(--fontSizeBase400);
    font-weight: var(--fontWeightSemibold);
    color: var(--colorNeutralForeground1);
}
.legal-summary__list {
    margin: 0;
    padding-inline-start: var(--spacingHorizontalL);
    display: grid;
    gap: var(--spacingVerticalXS);
    color: var(--colorNeutralForeground2);
}
.legal-summary__list li {
    line-height: var(--lineHeightBase300);
}

/* Anchor table-of-contents block for legal pages. */
.legal-toc {
    background: var(--colorNeutralBackground1);
    border: 1px solid var(--colorNeutralStroke2);
    border-radius: var(--borderRadiusMedium);
    padding: var(--spacingVerticalM) var(--spacingHorizontalL);
    margin: 0 0 var(--spacingVerticalXL);
}
.legal-toc__title {
    margin: 0 0 var(--spacingVerticalS);
    font-size: var(--fontSizeBase300);
    font-weight: var(--fontWeightSemibold);
    color: var(--colorNeutralForeground2);
}
.legal-toc ol {
    margin: 0;
    padding-inline-start: var(--spacingHorizontalL);
    color: var(--colorNeutralForeground2);
    font-size: var(--fontSizeBase200);
    line-height: var(--lineHeightBase200);
    columns: 2;
    column-gap: var(--spacingHorizontalL);
}
.legal-toc ol li {
    padding: var(--spacingVerticalXXS) 0;
    break-inside: avoid;
}
.legal-toc a {
    color: var(--colorBrandForeground1);
    text-decoration: none;
}
.legal-toc a:hover {
    text-decoration: underline;
}
@media (max-width: 640px) {
    .legal-toc ol { columns: 1; }
    /* Tappable menu on mobile — each TOC link gets a >=44px hit area so it
       reads as a list of buttons, not a cramped run of links (refinement 4,
       2026-06-09). Logical padding — RTL-safe. */
    .legal-toc a {
        display: flex;
        align-items: center;
        min-block-size: 44px;
        padding-block: var(--spacingVerticalS);
    }
}

/* Legal / long-form prose readability on phones (refinement 4). Dense
   legal text at the 14px Body-1 size is sub-readable on a small screen;
   bump the prose that sits DIRECTLY inside a narrow container to >=16px
   with a roomier 1.6 line-height below 600px. Scoped to direct children
   (``>``) so the .legal-summary / .legal-toc chrome (nested deeper) and
   component cards are untouched, and gated to mobile so desktop is
   unchanged. Applies to the legal pages (bare h2/p/ul prose) and any
   other narrow-container long-form. */
@media (max-width: 600px) {
    .container--narrow > p,
    .container--narrow > ul > li,
    .container--narrow > ol > li,
    .container--narrow > dl > dd {
        font-size: max(16px, var(--fontSizeBase300));
        line-height: 1.6;
    }
}

/* "Step N" eyebrow on how-it-works step sections. */
.step-eyebrow {
    display: inline-block;
    padding: var(--spacingVerticalXXS) var(--spacingHorizontalS);
    background: var(--colorBrandBackgroundTint);
    color: var(--colorBrandForeground2);
    font-size: var(--fontSizeBase100);
    font-weight: var(--fontWeightSemibold);
    text-transform: uppercase;
    letter-spacing: 0.05em;
    border-radius: var(--borderRadiusCircular, 999px);
    margin-bottom: var(--spacingVerticalS);
}

/* Pricing-page comparison strip: Polylingo vs Google Translate vs agency.
 * Built as a CSS-grid table for accessibility (role=table / row / cell). */
.comparison-table {
    display: grid;
    border: 1px solid var(--colorNeutralStroke2);
    border-radius: var(--borderRadiusMedium);
    overflow: hidden;
    background: var(--colorNeutralBackground1);
}
.comparison-table__row {
    display: grid;
    grid-template-columns: 1.4fr 1fr 1fr 1fr;
    align-items: center;
    gap: 0;
    border-top: 1px solid var(--colorNeutralStroke3);
}
.comparison-table__row:first-child {
    border-top: 0;
}
.comparison-table__row > * {
    padding: var(--spacingVerticalM) var(--spacingHorizontalM);
    font-size: var(--fontSizeBase300);
    color: var(--colorNeutralForeground1);
    text-align: center;
}
.comparison-table__row > [role="rowheader"] {
    text-align: start;
    font-weight: var(--fontWeightSemibold);
    color: var(--colorNeutralForeground2);
    background: var(--colorNeutralBackground2);
}
.comparison-table__row--head {
    background: var(--colorBrandBackground2);
}
.comparison-table__row--head > * {
    font-size: var(--fontSizeBase200);
    font-weight: var(--fontWeightSemibold);
    text-transform: uppercase;
    letter-spacing: 0.05em;
    color: var(--colorBrandForeground2);
    background: var(--colorBrandBackground2);
}
@media (max-width: 720px) {
    .comparison-table__row {
        grid-template-columns: 1fr;
    }
    .comparison-table__row--head { display: none; }
    .comparison-table__row > [role="cell"]::before {
        content: attr(data-label);
        display: block;
        font-size: var(--fontSizeBase100);
        color: var(--colorNeutralForeground3);
        text-transform: uppercase;
        letter-spacing: 0.05em;
        margin-bottom: var(--spacingVerticalXXS);
    }
}
/* <=600px: stacked self-contained CARDS (refinement 4, 2026-06-09).
 * pricing.html's [role="cell"] divs carry NO data-label, so the
 * attr(data-label) ::before labels above render empty when stacked —
 * leaving orphaned values with no column context. Since pricing.html is
 * out of this wave's partition (CSS can't add data-label), the remedy is
 * to make each row read as a card whose [role="rowheader"] is the title
 * ("WHAT is being compared") and the three values stack beneath it. A
 * tiny follow-up adding data-label="Polytxt"/"Google Translate"/"Agency"
 * to pricing.html would light up the ::before labels too. */
@media (max-width: 600px) {
    .comparison-table {
        /* Let the rows breathe as separate cards instead of a single
         * bordered grid block. */
        display: block;
        border: 0;
        background: transparent;
        border-radius: 0;
    }
    .comparison-table__row {
        border: var(--strokeWidthThin) solid var(--colorNeutralStroke2);
        border-radius: var(--borderRadiusMedium);
        background: var(--colorNeutralBackground1);
        margin-block-end: var(--spacingVerticalM);
        box-shadow: var(--shadow2);
    }
    .comparison-table__row:last-child { margin-block-end: 0; }
    /* The feature name becomes the card heading. */
    .comparison-table__row > [role="rowheader"] {
        font-size: var(--fontSizeBase400);
        font-weight: var(--fontWeightSemibold);
        color: var(--colorNeutralForeground1);
        background: transparent;
        text-align: start;
        border-bottom: var(--strokeWidthThin) solid var(--colorNeutralStroke3);
    }
    /* Value rows sit under the heading, start-aligned so they read as a
     * list rather than centred orphans. */
    .comparison-table__row > [role="cell"] {
        text-align: start;
    }
}

/* Use-case cards on pricing page + how-it-works. Inherit the shared
   surface-card recipe (surface, XLarge radius, resting shadow2, unified
   hover, equal-height flex column) — only the heading/body type ramp is
   declared here. (Previously borderRadiusMedium + a bespoke hover — the
   recipe promotes them to the premium card family.) */
.use-case-card h3 {
    margin: 0 0 var(--spacingVerticalS);
    font-size: var(--fontSizeBase400);
    color: var(--colorNeutralForeground1);
}
.use-case-card p {
    margin: 0;
    font-size: var(--fontSizeBase300);
    color: var(--colorNeutralForeground2);
    line-height: var(--lineHeightBase300);
}

/* "What makes Polytxt different" differentiator cards on how-it-works.
   Inherit the recipe; the brand-tinted title is the intentional accent
   variant (routed through --card-title-color so the recipe doesn't
   flatten it). The recipe also GIVES these a hover they previously
   lacked, so a row of diff cards now lifts consistently. */
.diff-card {
    --card-title-color: var(--colorBrandForeground1);
}
.diff-card h3 {
    margin: 0 0 var(--spacingVerticalS);
    font-size: var(--fontSizeBase400);
    color: var(--card-title-color);
}
.diff-card p {
    margin: 0;
    font-size: var(--fontSizeBase300);
    color: var(--colorNeutralForeground2);
    line-height: var(--lineHeightBase300);
}

/* ──────────────────────────────────────────────────────────────────────
 * Peek viewer (Task #1 PR γ)
 *
 * The guest preview surface — replaces the old "your translation is
 * being prepared" placeholder with a 2-column source↔target view of
 * the AI-translated batches, a blurred next-5 teaser zone, a static
 * ad-banner slot, a Windows-11-style loading spinner, and one of
 * three terminal states: register-to-continue, manual-retry, or the
 * 4-button follow-up row (Save / Certify / Baked-in / Upload another).
 *
 * Per user clarifications:
 *   #5 — Spinner is a simple Windows-11 rotating-arc style.
 *   #6 — 2-column source↔target layout on mobile too (cramped on
 *        very narrow phones is acceptable; explicit user choice).
 *   #7 — No language-pair edit UI; pair is fixed at job creation.
 *
 * Component selectors:
 *   .peek-header           — filename + locked language pair
 *   .peek-preparing        — pre-package waiting state with spinner
 *   .peek-viewer           — outer wrapper, HTMX swap target
 *   .peek-grid             — 2-column grid of unit pairs
 *   .peek-row              — one source↔target pair
 *   .peek-row--blurred     — next-N teaser pair (filter: blur)
 *   .peek-blurred-zone     — wrapper around blurred rows
 *   .ad-slot               — real ad slot (ADR 0056; via the ad_slot tag)
 *   .peek-view-more        — view-more button + progress label
 *   .peek-spinner          — Windows-11 rotating arc
 *   .peek-cap              — register-to-continue gate (200-cap)
 *   .peek-error            — manual-retry CTA + error detail
 *   .peek-followups        — 4-button row when all done
 *   .peek-disclaimer       — footer note about free vs certified
 * ────────────────────────────────────────────────────────────────────── */

/* Header: filename + language pair (no edit UI). */
.peek-header {
    text-align: center;
    margin: 0 0 var(--spacingVerticalXL);
}
.peek-header__title {
    font-size: var(--fontSizeBase600);
    font-weight: var(--fontWeightSemibold);
    color: var(--colorNeutralForeground1);
    margin: var(--spacingVerticalM) 0 var(--spacingVerticalS);
    word-break: break-word;
}
.peek-header__pair {
    display: inline-flex;
    align-items: center;
    gap: var(--spacingHorizontalS);
    padding: var(--spacingVerticalXS) var(--spacingHorizontalM);
    background: var(--colorNeutralBackground2);
    border: 1px solid var(--colorNeutralStroke2);
    border-radius: var(--borderRadiusCircular, 999px);
    font-size: var(--fontSizeBase200);
    color: var(--colorNeutralForeground2);
    margin: 0;
}
.peek-header__lang {
    font-weight: var(--fontWeightMedium);
}
.peek-header__arrow {
    opacity: 0.65;
}

/* Pre-package waiting state. */
.peek-preparing {
    display: flex;
    flex-direction: column;
    align-items: center;
    text-align: center;
    padding: var(--spacingVerticalXXL) var(--spacingHorizontalL);
    background: var(--colorNeutralBackground1);
    border: 1px solid var(--colorNeutralStroke3);
    border-radius: var(--borderRadiusLarge, 12px);
}
.peek-preparing h2 {
    font-size: var(--fontSizeBase500);
    margin: var(--spacingVerticalL) 0 var(--spacingVerticalS);
    color: var(--colorNeutralForeground1);
}
.peek-preparing__hint {
    margin: 0;
    color: var(--colorNeutralForeground3);
    font-size: var(--fontSizeBase300);
    max-width: 400px;
}

/* The peek viewer body itself. */
.peek-viewer {
    display: grid;
    gap: var(--spacingVerticalL);
}

/* 2-column source↔target grid. Per clarification #6, stays 2-col
 * on mobile too (the design accepts cramped on phones rather than
 * stacking which would make scrolling awkward). */
.peek-grid {
    display: grid;
    grid-template-columns: 1fr 1fr;
    gap: var(--spacingHorizontalS) var(--spacingHorizontalM);
    background: var(--colorNeutralBackground1);
    border: 1px solid var(--colorNeutralStroke2);
    border-radius: var(--borderRadiusLarge, 12px);
    padding: var(--spacingVerticalM);
}

.peek-row {
    display: contents;
}
.peek-row__source,
.peek-row__target {
    padding: var(--spacingVerticalS) var(--spacingHorizontalS);
    border-bottom: 1px solid var(--colorNeutralStroke3);
    font-size: var(--fontSizeBase300);
    line-height: var(--lineHeightBase400);
    word-break: break-word;
}
.peek-row__source {
    color: var(--colorNeutralForeground2);
    background: var(--colorNeutralBackground2);
    border-top-left-radius: var(--borderRadiusSmall);
    border-bottom-left-radius: var(--borderRadiusSmall);
}
.peek-row__target {
    color: var(--colorNeutralForeground1);
    font-weight: var(--fontWeightMedium);
}
/* Strip bottom border off the last visible row. CSS :last-child
 * with display:contents is tricky; use a nth-from-end selector
 * that matches both columns of the final row. */
.peek-grid > .peek-row:last-child > .peek-row__source,
.peek-grid > .peek-row:last-child > .peek-row__target {
    border-bottom: 0;
}

/* Blurred teaser zone + View-More overlay (Task #F PR-1).
 *
 * The blurred zone is now a positioned container; the View-More
 * button + multi-step loader sit on TOP of the blur as an overlay
 * with a semi-transparent backdrop. This gives a single cohesive
 * affordance instead of "blur block + separate button" stack. */
.peek-blurred-zone {
    position: relative;
    margin-top: calc(var(--spacingVerticalL) * -1);
    min-height: 160px;
    /* Soft top-fade so the blur transition feels deliberate. */
    mask-image: linear-gradient(to bottom, transparent, black 20%);
    -webkit-mask-image: linear-gradient(to bottom, transparent, black 20%);
}
.peek-grid--blurred {
    filter: blur(4px);
    pointer-events: none;
    user-select: none;
    opacity: 0.85;
}
/* View-More overlay — absolute centred on the blurred zone.
 * The backdrop sits at low opacity to keep the teaser visible
 * while ensuring the button reads cleanly. */
.peek-blur-overlay {
    position: absolute;
    inset: 0;
    display: grid;
    place-items: center;
    gap: var(--spacingVerticalM);
    padding: var(--spacingVerticalL) var(--spacingHorizontalL);
    /* Subtle scrim to ensure the button is legible over blurred text */
    background: linear-gradient(
        to bottom,
        rgba(255, 255, 255, 0.45),
        rgba(255, 255, 255, 0.75) 40%,
        rgba(255, 255, 255, 0.85) 100%
    );
    /* Don't escape the rounded outer of the parent peek-grid */
    border-radius: var(--borderRadiusMedium, 8px);
    text-align: center;
}
/* [Peek-viewer dark-mode overlay variant — REMOVED 2026-05-30 with the
   broader dark-mode removal. The light-mode overlay above is the only
   variant in use now.] */
/* When peek_more is in-flight the View-More button disappears and
 * the multi-step loader takes its place. The htmx-request class
 * lands on the parent .peek-viewer-body during the XHR. */
.htmx-request .peek-blur-overlay .peek-view-more__btn {
    visibility: hidden;
}

/* Legacy: blurred row's placeholder target (━━━ ascii bar). After
 * Task #F PR-1 the blur teaser re-uses real translated content so
 * this class is unused; keeping the rule for graceful degrade if a
 * future variant brings the placeholder back. */
.peek-row--blurred .peek-row__target--placeholder {
    color: var(--colorNeutralForeground3);
    letter-spacing: -2px;
}

/* Real ad slot (ADR 0056). Rendered by the {% ad_slot %} tag only when ads
 * are live; replaces the static .peek-ad placeholder.
 *
 * Graceful-collapse contract (hardened 2026-06-08, harness proof-run
 * wf_80f22d3b-3ca). Ads are LIVE at launch (ADR 0057), so the slot has to
 * be invisible — not just "rendered nothing server-side" — in every
 * absence mode the browser can land in:
 *
 *   1. not-configured  → the {% ad_slot %} tag returns render=False, so the
 *                         wrapper isn't emitted at all (handled in Python).
 *   2. live-but-unfilled → AdSense loads, attempts a fill, finds no ad, and
 *                         sets data-ad-status="unfilled" on the <ins> (it
 *                         also sets the <ins> to display:none). The wrapper
 *                         <div> IS in the DOM.
 *   3. adblocked / load-failure → adsbygoogle.js never runs, so
 *                         data-ad-status is NEVER set on the <ins>. The
 *                         wrapper <div> IS in the DOM.
 *
 * In modes 2 and 3 the wrapper must paint NOTHING: no border, no
 * background, no padding, no label, no min-height — a true zero box that
 * leaves no gap and no phantom scrollbar.
 *
 * The mechanism: the wrapper is bare by DEFAULT and only paints its chrome
 * when it actually CONTAINS a filled unit. Two redundant signals drive the
 * "filled" state so neither path alone can leave a hollow box:
 *   • CSS :has(ins[data-ad-status="filled"]) — reacts to the attribute
 *     AdSense sets on a successful fill (live in all evergreen browsers).
 *   • a .ad-slot--filled class toggled by the render-callback in
 *     advertising/_ad_slot.html — belt-and-braces for the (rare) browser
 *     whose :has() doesn't re-evaluate on a late attribute mutation, and a
 *     clean hook for tests.
 * Either one matching reveals the chrome; in the unfilled/blocked modes
 * NEITHER matches, so the box stays at zero.
 *
 * Logical properties only — RTL-safe per §18.2 (ops.tests_rtl_invariants
 * enforces). */
.ad-slot {
    /* Bare by default: zero box in the unfilled / adblocked / pre-fill
     * states. No border / background / padding / min-height here — the
     * chrome lives entirely on the filled rules below. */
    text-align: center;
    margin-inline: auto;
    overflow: hidden;
}
.ad-slot--w-auto { width: 100%; }
.ad-slot--w-fixed { width: 100%; }   /* max-width applied inline from max_width_px */

/* The "Advertisement" transparency label is hidden until the slot fills, so
 * an empty/blocked slot announces nothing (no orphan label over blank
 * space). It's revealed alongside the chrome in the filled rules below. */
.ad-slot__label {
    display: none;
    font-size: var(--fontSizeBase100);
    color: var(--colorNeutralForeground3);
    text-transform: uppercase;
    letter-spacing: 0.05em;
    margin-block-end: var(--spacingVerticalXS);
}
.ad-slot .adsbygoogle { display: block; }

/* ── Filled state ──────────────────────────────────────────────────────
 * Paint the chrome ONLY when the slot holds a filled AdSense unit. Both
 * the :has() selector and the JS-toggled class trigger it (see the
 * graceful-collapse note above). The 'card' look is the default; the
 * inline / minimal style variants strip the box (rules further down). */
.ad-slot:has(ins[data-ad-status="filled"]),
.ad-slot--filled {
    border: 1px solid var(--colorNeutralStroke2);
    border-radius: var(--borderRadiusMedium);
    padding: var(--spacingVerticalM) var(--spacingHorizontalL);
    background: var(--colorNeutralBackground2);
    /* Reserve a sensible minimum once filled so the painted ad doesn't
     * jitter as the responsive unit settles to its final height — a
     * filled slot is the only state that takes vertical space, so this
     * costs ZERO layout shift in the empty/blocked states. */
    min-block-size: 90px;
}
.ad-slot:has(ins[data-ad-status="filled"]) .ad-slot__label,
.ad-slot--filled .ad-slot__label {
    display: block;
}

/* Style variants (AdPlacement.style). 'card' is the default look above; the
 * inline / minimal variants drop the border + background even when filled,
 * but the label + min-height still gate on the filled state. */
.ad-slot--inline:has(ins[data-ad-status="filled"]),
.ad-slot--inline.ad-slot--filled,
.ad-slot--minimal:has(ins[data-ad-status="filled"]),
.ad-slot--minimal.ad-slot--filled {
    border: 0;
    background: transparent;
}
.ad-slot--minimal:has(ins[data-ad-status="filled"]),
.ad-slot--minimal.ad-slot--filled { padding: 0; }

/* View-more button + progress label. */
.peek-view-more {
    text-align: center;
    display: grid;
    gap: var(--spacingVerticalS);
    justify-items: center;
}
.peek-view-more__btn {
    /* Inherits .btn .btn--primary .btn--hero — nothing extra here. */
}
.peek-view-more__btn[disabled] {
    opacity: 0.6;
    cursor: wait;
}
.peek-view-more__progress {
    margin: 0;
    font-size: var(--fontSizeBase200);
    color: var(--colorNeutralForeground3);
}

/* Windows-11 / Fluent-style spinner. A 270° arc rotates infinitely.
 * Used as an htmx-indicator so it appears only during in-flight
 * peek_more requests. */
.peek-spinner {
    display: none;
    align-items: center;
    justify-content: center;
    gap: var(--spacingHorizontalS);
    padding: var(--spacingVerticalS) 0;
    color: var(--colorNeutralForeground2);
    font-size: var(--fontSizeBase200);
}
.htmx-request .peek-spinner,
.peek-spinner.htmx-request,
.htmx-request.peek-spinner {
    display: inline-flex;
}
/* Preparing-state spinner: always visible (not gated on htmx-request
 * — the preparing state is driven by a meta-refresh, not HTMX). */
.peek-preparing .peek-spinner {
    display: inline-flex;
}
.peek-spinner__arc {
    width: 20px;
    height: 20px;
    border: 2px solid var(--colorNeutralStroke3);
    border-top-color: var(--colorBrandStroke1);
    border-radius: 50%;
    animation: peek-spin 0.9s linear infinite;
    flex: 0 0 auto;
}
.peek-spinner--large .peek-spinner__arc {
    width: 44px;
    height: 44px;
    border-width: 3px;
}
.peek-spinner__label {
    color: var(--colorNeutralForeground3);
}
@keyframes peek-spin {
    to { transform: rotate(360deg); }
}

/* Multi-step loader variant (Task #F PR-1). Three pulsing dots
 * + a status label that cycles via Alpine ("Reading text…" →
 * "Translating…" → "Finishing up…"). Replaces the bare arc spinner
 * inside the View-More overlay so the wait reads as deliberate
 * progress, not a generic in-flight indicator. */
.peek-spinner--multi-step {
    flex-direction: column;
    gap: var(--spacingVerticalS);
    padding: var(--spacingVerticalM) 0;
}
.peek-spinner__dots {
    display: inline-flex;
    gap: 6px;
}
.peek-spinner__dot {
    width: 9px;
    height: 9px;
    border-radius: 50%;
    background: var(--colorBrandStroke1, #0078d4);
    opacity: 0.4;
    animation: peek-dot-pulse 1.2s ease-in-out infinite;
}
.peek-spinner__dot:nth-child(1) { animation-delay: 0s; }
.peek-spinner__dot:nth-child(2) { animation-delay: 0.2s; }
.peek-spinner__dot:nth-child(3) { animation-delay: 0.4s; }
@keyframes peek-dot-pulse {
    0%, 80%, 100% { opacity: 0.4; transform: scale(0.85); }
    40% { opacity: 1; transform: scale(1.0); }
}
.peek-spinner--multi-step .peek-spinner__label {
    font-size: var(--fontSizeBase300);
    color: var(--colorNeutralForeground2);
    min-width: 8rem;
    text-align: center;
}

/* 200-segment cap → register-to-continue gate. */
.peek-cap {
    background: linear-gradient(
        135deg,
        var(--colorBrandBackground2),
        var(--colorNeutralBackground1)
    );
    border: 1px solid var(--colorBrandStroke2, var(--colorNeutralStroke2));
    border-radius: var(--borderRadiusLarge, 12px);
    padding: var(--spacingVerticalXXL) var(--spacingHorizontalL);
}
.peek-cap__inner {
    max-width: 540px;
    margin: 0 auto;
    text-align: center;
}
.peek-cap__title {
    margin: 0 0 var(--spacingVerticalS);
    font-size: var(--fontSizeBase500);
    color: var(--colorNeutralForeground1);
}
.peek-cap__detail {
    margin: 0 0 var(--spacingVerticalL);
    color: var(--colorNeutralForeground2);
    font-size: var(--fontSizeBase300);
    line-height: var(--lineHeightBase400);
}
.peek-cap__actions {
    display: flex;
    gap: var(--spacingHorizontalM);
    justify-content: center;
    flex-wrap: wrap;
    margin-bottom: var(--spacingVerticalM);
}
.peek-cap__hint {
    margin: 0;
    font-size: var(--fontSizeBase200);
    color: var(--colorNeutralForeground3);
}

/* Failure state — manual retry CTA per user clarification #3. */
.peek-error {
    display: grid;
    grid-template-columns: auto 1fr auto;
    gap: var(--spacingHorizontalM);
    align-items: start;
    padding: var(--spacingVerticalM) var(--spacingHorizontalL);
    background: var(--colorPaletteRedBackground1, #fde7e9);
    border: 1px solid var(--colorPaletteRedBorder1, #f1707b);
    border-radius: var(--borderRadiusMedium);
}
.peek-error__icon {
    flex: 0 0 auto;
    color: var(--colorPaletteRedForeground1, #b10e1c);
}
.peek-error__title {
    margin: 0 0 var(--spacingVerticalXS);
    font-size: var(--fontSizeBase400);
    color: var(--colorPaletteRedForeground1, #b10e1c);
}
.peek-error__detail {
    margin: 0 0 var(--spacingVerticalS);
    color: var(--colorNeutralForeground2);
    font-size: var(--fontSizeBase300);
}
.peek-error__technical {
    font-size: var(--fontSizeBase200);
    color: var(--colorNeutralForeground3);
}
.peek-error__technical summary {
    cursor: pointer;
    user-select: none;
}
.peek-error__technical code {
    display: inline-block;
    margin-top: var(--spacingVerticalXS);
    padding: 2px 6px;
    background: var(--colorNeutralBackground1);
    border: 1px solid var(--colorNeutralStroke3);
    border-radius: var(--borderRadiusSmall);
    font-family: var(--fontFamilyMonospace);
}
.peek-error__retry {
    flex: 0 0 auto;
    white-space: nowrap;
}

/* Throttled (rate-limit) inline notice. Returned by peek_more as 429
 * when the same session fires requests faster than the cooldown. */
.peek-throttled {
    padding: var(--spacingVerticalS) var(--spacingHorizontalM);
    background: var(--colorPaletteYellowBackground1, #fff4ce);
    border: 1px solid var(--colorPaletteYellowBorder1, #f2cd1f);
    border-radius: var(--borderRadiusMedium);
    color: var(--colorNeutralForeground1);
    font-size: var(--fontSizeBase300);
    text-align: center;
}
.peek-throttled p {
    margin: 0;
}

/* Page-truncation notice (Task #3 / ADR 0033). Renders above the
 * 4-button follow-up row whenever the OCR pipeline clipped a
 * multi-page guest PDF to GUEST_PAGE_CAP pages. Visual treatment:
 * blue informational stripe (NOT a warning/error) so the visitor
 * reads it as "here's some info" rather than "you did something
 * wrong." Sits above the follow-ups row so the constraint is
 * visible before the CTAs.
 */
.peek-truncation-notice {
    padding: var(--spacingVerticalS) var(--spacingHorizontalM);
    margin-bottom: var(--spacingVerticalM);
    background: var(--colorBrandBackground2, #ebf3fc);
    border: 1px solid var(--colorBrandStroke2, #b4d6fa);
    border-inline-start: 3px solid var(--colorBrandStroke1, #0f6cbd);
    border-radius: var(--borderRadiusMedium);
    color: var(--colorNeutralForeground1);
    font-size: var(--fontSizeBase300);
}
.peek-truncation-notice p {
    margin: 0;
}
.peek-truncation-notice strong {
    color: var(--colorBrandForeground1, #0f6cbd);
}

/* 4-button follow-up row (Tasks D + E sub-tasks of #1). Renders when
 * the whole document is translated AND the cap wasn't hit. Equal-width
 * grid on desktop, 2×2 on tablet, single column on narrow phones. */
.peek-followups {
    background: var(--colorNeutralBackground2);
    border: 1px solid var(--colorNeutralStroke3);
    border-radius: var(--borderRadiusLarge, 12px);
    padding: var(--spacingVerticalL) var(--spacingHorizontalL);
}
.peek-followups__title {
    margin: 0 0 var(--spacingVerticalL);
    font-size: var(--fontSizeBase500);
    text-align: center;
    color: var(--colorNeutralForeground1);
}
.peek-followups__grid {
    display: grid;
    grid-template-columns: repeat(4, 1fr);
    gap: var(--spacingHorizontalM);
}
@media (max-width: 880px) {
    .peek-followups__grid {
        grid-template-columns: repeat(2, 1fr);
    }
}
@media (max-width: 480px) {
    .peek-followups__grid {
        grid-template-columns: 1fr;
    }
}
.peek-followup-btn {
    display: flex !important;
    flex-direction: column;
    align-items: center;
    text-align: center;
    gap: var(--spacingVerticalS);
    padding: var(--spacingVerticalL) var(--spacingHorizontalM) !important;
    height: auto !important;
    min-height: 156px;
    line-height: var(--lineHeightBase200);
    transition: transform 0.18s ease-out, box-shadow 0.18s ease-out;
}
.peek-followup-btn:hover {
    transform: translateY(-2px);
    box-shadow: 0 6px 16px rgba(0, 0, 0, 0.08);
}
.peek-followup-btn__illustration {
    width: 64px;
    height: 64px;
    flex: 0 0 auto;
    margin-bottom: var(--spacingVerticalXS);
}
.peek-followup-btn__label {
    font-weight: var(--fontWeightSemibold);
    font-size: var(--fontSizeBase300);
}
.peek-followup-btn__hint {
    font-size: var(--fontSizeBase200);
    color: inherit;
    opacity: 0.75;
}
/* Pressed-state animation when share opens its modal. */
.peek-followup-btn[data-followup="share"]:active {
    transform: translateY(0);
    box-shadow: 0 2px 8px rgba(0, 0, 0, 0.12) inset;
}

/* ──────────────────────────────────────────────────────────────────────
 * Bottom Save CTA (Task #F PR-1)
 *
 * Renders only when the document is fully translated. A single
 * prominent affordance for "save to your account" — the final
 * conversion ask after the user has consumed the entire preview.
 * ────────────────────────────────────────────────────────────────────── */
.peek-save-bottom {
    margin-top: var(--spacingVerticalXL, var(--spacingVerticalXXL));
    padding: var(--spacingVerticalXL) var(--spacingHorizontalL);
    background: linear-gradient(
        135deg,
        var(--colorBrandBackground2, #ebf3fc),
        var(--colorNeutralBackground1) 80%
    );
    border: 1px solid var(--colorBrandStroke2, #b4d6fa);
    border-radius: var(--borderRadiusLarge, 12px);
    display: grid;
    place-items: center;
}
.peek-save-bottom__btn {
    display: flex !important;
    flex-direction: row !important;
    align-items: center;
    gap: var(--spacingHorizontalM);
    padding: var(--spacingVerticalL) var(--spacingHorizontalXL) !important;
    height: auto !important;
    min-height: 88px;
    max-width: 560px;
    text-align: start;
    transition: transform 0.18s ease-out, box-shadow 0.18s ease-out;
}
.peek-save-bottom__btn:hover {
    transform: translateY(-2px);
    box-shadow: 0 8px 24px rgba(0, 120, 212, 0.20);
}
.peek-save-bottom__illustration {
    width: 56px;
    height: 56px;
    flex: 0 0 auto;
}
.peek-save-bottom__label {
    display: block;
    font-weight: var(--fontWeightSemibold);
    font-size: var(--fontSizeBase500);
    line-height: var(--lineHeightBase400);
}
.peek-save-bottom__hint {
    display: block;
    font-size: var(--fontSizeBase200);
    line-height: var(--lineHeightBase200);
    opacity: 0.85;
    margin-top: 2px;
}
@media (max-width: 600px) {
    .peek-save-bottom__btn {
        flex-direction: column !important;
        text-align: center;
    }
}

/* ──────────────────────────────────────────────────────────────────────
 * Share modal placeholder (Task #F PR-1)
 *
 * Centred overlay dialog driven by Alpine.js. PR F-2 will wire this
 * to the SharedLinkToken creation flow; for now it shows a
 * "coming soon" panel so the Share button has a visible response.
 * ────────────────────────────────────────────────────────────────────── */
[x-cloak] { display: none !important; }
.peek-share-modal {
    position: fixed;
    inset: 0;
    z-index: 1000;
    display: grid;
    place-items: center;
    padding: var(--spacingHorizontalM);
}
.peek-share-modal__backdrop {
    position: absolute;
    inset: 0;
    background: rgba(0, 0, 0, 0.45);
}
.peek-share-modal__panel {
    position: relative;
    background: var(--colorNeutralBackground1);
    border-radius: var(--borderRadiusLarge, 12px);
    padding: var(--spacingVerticalXL) var(--spacingHorizontalXL);
    max-width: 480px;
    width: 100%;
    box-shadow: 0 24px 64px rgba(0, 0, 0, 0.30);
    text-align: start;
}
.peek-share-modal__close {
    position: absolute;
    top: var(--spacingVerticalM);
    right: var(--spacingHorizontalM);
    background: transparent;
    border: 0;
    font-size: 32px;
    line-height: 1;
    cursor: pointer;
    color: var(--colorNeutralForeground3);
    padding: 4px 8px;
}
.peek-share-modal__close:hover {
    color: var(--colorNeutralForeground1);
}
.peek-share-modal__heading {
    margin: 0 0 var(--spacingVerticalM);
    font-size: var(--fontSizeBase500);
    color: var(--colorNeutralForeground1);
}
.peek-share-modal__detail {
    margin: 0 0 var(--spacingVerticalM);
    font-size: var(--fontSizeBase300);
    line-height: var(--lineHeightBase300);
    color: var(--colorNeutralForeground2);
}
.peek-share-modal__actions {
    display: flex;
    gap: var(--spacingHorizontalS);
    margin-top: var(--spacingVerticalL);
    flex-wrap: wrap;
}
/* Real share modal UI (PR F-2). */
.peek-share-modal__detail--error {
    color: var(--colorPaletteRedForeground1, #c0392b);
}
.peek-share-modal__validity {
    margin: 0 0 var(--spacingVerticalM);
    padding: var(--spacingVerticalS) var(--spacingHorizontalM);
    background: var(--colorPaletteYellowBackground1, #fff4ce);
    border-inline-start: 3px solid var(--colorPaletteYellowBorder1, #f2cd1f);
    border-radius: var(--borderRadiusMedium);
    font-size: var(--fontSizeBase200);
    color: var(--colorNeutralForeground1);
}
.peek-share-modal__url-row {
    display: flex;
    gap: var(--spacingHorizontalS);
    margin: var(--spacingVerticalM) 0;
    align-items: stretch;
}
.peek-share-modal__url-input {
    flex: 1;
    min-width: 0;
    font-family: ui-monospace, SFMono-Regular, Menlo, Consolas, monospace;
    font-size: var(--fontSizeBase200);
    padding: var(--spacingVerticalS) var(--spacingHorizontalM);
    border: 1px solid var(--colorNeutralStroke2);
    border-radius: var(--borderRadiusMedium);
    background: var(--colorNeutralBackground2);
    color: var(--colorNeutralForeground1);
}
.peek-share-modal__copy {
    flex: 0 0 auto;
}

/* ──────────────────────────────────────────────────────────────────────
 * Invitee mode (Task #F PR-2)
 * ────────────────────────────────────────────────────────────────────── */

.peek-invitee-validity {
    margin-top: var(--spacingVerticalL);
    padding: var(--spacingVerticalM) var(--spacingHorizontalL);
    background: var(--colorPaletteYellowBackground1, #fff4ce);
    border: 1px solid var(--colorPaletteYellowBorder1, #f2cd1f);
    border-inline-start: 4px solid var(--colorPaletteYellowForeground1, #c19a00);
    border-radius: var(--borderRadiusMedium);
    color: var(--colorNeutralForeground1);
    font-size: var(--fontSizeBase300);
}
.peek-invitee-validity p {
    margin: 0;
}

.peek-invitee-hero {
    margin: var(--spacingVerticalL) 0;
    padding: var(--spacingVerticalXXL) var(--spacingHorizontalXL);
    background: linear-gradient(
        135deg,
        var(--colorBrandBackground2, #ebf3fc),
        var(--colorNeutralBackground1) 80%
    );
    border: 1px solid var(--colorBrandStroke2, #b4d6fa);
    border-radius: var(--borderRadiusLarge, 12px);
    text-align: center;
}
.peek-invitee-hero__title {
    margin: 0 0 var(--spacingVerticalM);
    font-size: var(--fontSizeBase600);
    font-weight: var(--fontWeightSemibold);
    color: var(--colorNeutralForeground1);
}
.peek-invitee-hero__detail {
    margin: 0 0 var(--spacingVerticalL);
    font-size: var(--fontSizeBase300);
    color: var(--colorNeutralForeground2);
    line-height: var(--lineHeightBase400);
    max-width: 560px;
    margin-inline-start: auto;
    margin-inline-end: auto;
}
.peek-invitee-hero__actions {
    display: flex;
    justify-content: center;
    gap: var(--spacingHorizontalM);
    flex-wrap: wrap;
}

.peek-invitee-empty {
    padding: var(--spacingVerticalXL) var(--spacingHorizontalL);
    text-align: center;
    color: var(--colorNeutralForeground3);
    font-size: var(--fontSizeBase300);
}

.peek-invitee-invalid {
    margin: var(--spacingVerticalXXL) auto;
    padding: var(--spacingVerticalXXL) var(--spacingHorizontalXL);
    background: var(--colorNeutralBackground2);
    border: 1px solid var(--colorNeutralStroke3);
    border-radius: var(--borderRadiusLarge, 12px);
    text-align: center;
}
.peek-invitee-invalid__title {
    margin: 0 0 var(--spacingVerticalM);
    font-size: var(--fontSizeBase600);
    color: var(--colorNeutralForeground1);
}
.peek-invitee-invalid__detail {
    margin: 0 0 var(--spacingVerticalXL);
    color: var(--colorNeutralForeground2);
    line-height: var(--lineHeightBase400);
    max-width: 520px;
    margin-inline-start: auto;
    margin-inline-end: auto;
}

/* Footer disclaimer note. */
.peek-disclaimer {
    margin-top: var(--spacingVerticalL);
    font-size: var(--fontSizeBase200);
    color: var(--colorNeutralForeground3);
    text-align: center;
    line-height: var(--lineHeightBase300);
}

/* ──────────────────────────────────────────────────────────────────────
 * Floating document-view layout (Task #F PR-1)
 *
 * Two-column grid: left panel renders the source document's page
 * image (one active at a time, sticky on scroll); right column
 * holds the existing peek-grid of source+target pairs.
 *
 * On mobile (<880px) the doc panel collapses to the top of the
 * page (non-sticky, scrolls with content). On <600px the panel is
 * hidden entirely — too narrow to be useful alongside text.
 * ────────────────────────────────────────────────────────────────────── */
.peek-layout {
    display: grid;
    grid-template-columns: 40% 1fr;
    gap: var(--spacingHorizontalL);
    /* IMPORTANT: do NOT set align-items: start here. Grid cells
     * default to ``stretch`` which gives the left column the full
     * row height. Without that, position: sticky on .peek-doc-panel__sticky
     * has no scrolling reference height to stick within and the
     * panel scrolls with the page instead of floating. */
    margin-top: var(--spacingVerticalL);
}
.peek-doc-panel {
    position: relative;
    min-width: 0;
    /* Ensure the doc-panel grid cell can host a sticky child.
     * Setting an explicit height isn't necessary — the implicit
     * grid-row height (driven by the right column's content) is
     * what sticky scrolls against. */
}
.peek-doc-panel__sticky {
    position: sticky;
    top: var(--spacingVerticalL);
    display: grid;
    gap: var(--spacingVerticalS);
}
.peek-doc-page {
    position: relative;
    background: var(--colorNeutralBackground2);
    border: 1px solid var(--colorNeutralStroke3);
    border-radius: var(--borderRadiusMedium);
    overflow: hidden;
    box-shadow: 0 4px 12px rgba(0, 0, 0, 0.06);
    /* The image fills its container; we constrain the overall
     * column-width via the grid. Aspect ratio comes from the image
     * itself (browser preserves it on load). */
}
.peek-doc-page__img {
    display: block;
    width: 100%;
    height: auto;
    user-select: none;
}
.peek-doc-page__highlight {
    position: absolute;
    pointer-events: none;
    border: 2px solid var(--colorBrandStroke1, #0078d4);
    background: rgba(0, 120, 212, 0.15);
    border-radius: 3px;
    transition: top 0.12s ease-out, left 0.12s ease-out,
                width 0.12s ease-out, height 0.12s ease-out,
                opacity 0.12s ease-out;
    box-shadow: 0 0 0 3px rgba(0, 120, 212, 0.10);
}

/* 4x Magnifier (Task #F polish). On hover of a segment row, the
 * Alpine handler positions this element below the highlighted bbox
 * (or above when bottom-clipped) and inline-styles the inner img
 * so the corresponding region of the page renders at 4x its in-panel
 * size. ``overflow: hidden`` clips the inner img to the magnifier
 * viewport. Pointer-events disabled so the magnifier can't itself
 * intercept hover and cause flicker. */
.peek-doc-page__magnifier {
    position: absolute;
    overflow: hidden;
    pointer-events: none;
    background: var(--colorNeutralBackground1, #fff);
    border: 2px solid var(--colorBrandStroke1, #0078d4);
    border-radius: 3px;
    box-shadow: 0 8px 24px rgba(0, 0, 0, 0.18);
    z-index: 2;
}
.peek-doc-page__magnifier-img {
    position: absolute;
    /* Suppress browser smoothing — the 4x upscale should look
     * pixel-precise on text. Vendor prefixes for older browsers. */
    image-rendering: -webkit-optimize-contrast;
    image-rendering: crisp-edges;
    max-width: none;
}
.peek-doc-panel__caption {
    margin: 0;
    text-align: center;
    font-size: var(--fontSizeBase200);
    color: var(--colorNeutralForeground3);
}
.peek-right {
    min-width: 0;
}

@media (max-width: 1023px) {
    .peek-layout {
        grid-template-columns: 35% 1fr;
    }
}
@media (max-width: 880px) {
    /* Stack: doc on top (small, non-sticky), text below. */
    .peek-layout {
        grid-template-columns: 1fr;
    }
    .peek-doc-panel__sticky {
        position: static;
        max-width: 360px;
        margin: 0 auto;
    }
}
@media (max-width: 600px) {
    /* Hide the doc panel entirely — too narrow to be useful. The
     * peek grid runs full-width. */
    .peek-doc-panel {
        display: none;
    }
}

/* ── Peek viewer — mobile-responsive segment grid (refinement 3) ────────
 *
 * wf_00e50379-e44 visual review, 2026-06-09. Below ~600px the previous
 * design forced the source↔target grid to stay 2-col (an explicit
 * earlier choice: "the design accepts cramped on phones"). On a 390px
 * phone that crushed each side into a ~170px column of wrapping text —
 * unreadable. This stacks the grid into a single readable column with
 * source-above-target pairs, ties the paper styling to the hero, and
 * guarantees no horizontal overflow.
 *
 * SURGICAL: this only restyles the EXISTING .peek-grid / .peek-row /
 * .peek-row__source / .peek-row__target structure. ``.peek-row`` keeps
 * ``display: contents`` (the 2026-05-15 paint fix + the hover-paint rule
 * below both depend on it — the css-paint-on-display-contents scanner
 * pins it). The source + target cells are the real grid items, so in a
 * single column they stack naturally source-above-target. No new box is
 * introduced around .peek-row (that would re-break the grid). Desktop +
 * tablet (>600px) are byte-for-byte unchanged. The doc panel stays
 * hidden (recommended low-risk path; peekDocView / magnifier / hover-bbox
 * untouched). Applies to all three peek bodies (guest_preview,
 * _peek_more_partial, peek_viewer_invitee / _peek_invitee_body) since
 * they share this markup. */
@media (max-width: 600px) {
    /* One column — source above target per pair. */
    .peek-grid {
        grid-template-columns: 1fr;
        /* Echo the hero paper sheets: a low resting lift ties the peek
         * visually to the hero's right pane (the grid already carries the
         * Background1 + Stroke2 + Large-radius card chrome). */
        box-shadow: var(--shadow2);
    }
    /* Readable phone body — Base300 is 14px; bump segment text to 16px so
     * source + target are comfortable on a small screen. word-break is
     * already set so long tokens can't overflow. */
    .peek-grid .peek-row__source,
    .peek-grid .peek-row__target {
        font-size: var(--fontSizeBase400);
        line-height: var(--lineHeightBase400);
    }
    /* Group each source/target pair: source gets the top corners, target
     * gets the bottom corners (logical start/end corner radii — RTL-safe).
     * The source keeps its tint; the target stays on Background1, so the
     * pair reads as one labelled block. */
    .peek-grid .peek-row__source {
        border-start-start-radius: var(--borderRadiusMedium);
        border-start-end-radius: var(--borderRadiusMedium);
        border-end-start-radius: 0;
        border-end-end-radius: 0;
    }
    .peek-grid .peek-row__target {
        border-start-start-radius: 0;
        border-start-end-radius: 0;
        border-end-start-radius: var(--borderRadiusMedium);
        border-end-end-radius: var(--borderRadiusMedium);
    }
    /* Separate consecutive pairs so segments don't blur together. The
     * ``.peek-row__target`` is the last cell of each logical pair; the
     * adjacency from the next row's source supplies the gap. The existing
     * last-child rule (which strips the final row's bottom border) keeps
     * the closing pair flush with the grid padding. */
    .peek-grid > .peek-row > .peek-row__target {
        margin-block-end: var(--spacingVerticalM);
        border-bottom: 0;
    }
    .peek-grid > .peek-row > .peek-row__source {
        /* Keep a hairline between source and its own target within a pair. */
        border-bottom: var(--strokeWidthThin) solid var(--colorNeutralStroke3);
    }
    .peek-grid > .peek-row:last-child > .peek-row__target {
        margin-block-end: 0;
    }
}

/* Hover affordance on EVERY .peek-row — tint the row in the same
 * brand-blue used by the document highlight overlay
 * (.peek-doc-page__highlight) so the visual relationship between
 * the segment row and the highlighted region on the page is
 * immediate. Rows that ALSO carry bbox data drive the doc-panel
 * locator on hover (handled in JS); rows without bbox data still
 * get the row-level highlight so the user gets consistent visual
 * feedback that something is interactive.
 *
 * Earlier iteration gated the rule on ``[data-bbox-x0]`` which
 * silently disabled the hover on text-extracted documents and on
 * any OCR segment that lost its region link — universalising the
 * rule is the correct fix.
 *
 * 2026-05-15 fix: ``.peek-row`` uses ``display: contents`` so the
 * source + target cells become direct children of the grid (line
 * 4144). An element with ``display: contents`` has NO box in the
 * visual tree — ``background-color`` set on it never paints, even
 * though ``getComputedStyle`` will happily report the value. The
 * earlier hover rule passed the Playwright "background changed"
 * assertion but rendered nothing to the user. Move the hover paint
 * to the two CHILD cells (``.peek-row__source`` /
 * ``.peek-row__target``) which DO have boxes; trigger them off
 * ``.peek-row:hover`` (the parent's :hover state still cascades
 * via the descendant combinator). */
.peek-row__source,
.peek-row__target {
    transition: background-color 0.12s ease-out;
}
.peek-row:hover > .peek-row__source,
.peek-row:hover > .peek-row__target {
    background: rgba(0, 120, 212, 0.12);
}

/* ──────────────────────────────────────────────────────────────────────
 * Workflow-lane stub pages (Task #2 / ADR 0032)
 *
 * Confirmation screen the user lands on after clicking "Get
 * certified" or "Turn into baked-in" on the peek viewer's
 * 4-button row + completing the claim flow. Pure placeholder UX —
 * the actual pipeline kick-off lives in future PRs.
 * ────────────────────────────────────────────────────────────────────── */

.workflow-stub {
    display: grid;
    gap: var(--spacingVerticalL);
    justify-items: center;
    text-align: center;
    padding: var(--spacingVerticalXXL) var(--spacingHorizontalL);
    background: linear-gradient(
        135deg,
        var(--colorBrandBackground2),
        var(--colorNeutralBackground1)
    );
    border: 1px solid var(--colorNeutralStroke2);
    border-radius: var(--borderRadiusLarge, 12px);
}
.workflow-stub__icon {
    width: 64px;
    height: 64px;
    border-radius: 50%;
    background: var(--colorBrandBackground2);
    display: inline-flex;
    align-items: center;
    justify-content: center;
}
.workflow-stub__icon .icon--large {
    width: 32px;
    height: 32px;
}
.workflow-stub__title {
    margin: 0;
    font-size: var(--fontSizeBase600);
    font-weight: var(--fontWeightSemibold);
    color: var(--colorNeutralForeground1);
    max-width: 600px;
}
.workflow-stub__lead {
    margin: 0;
    font-size: var(--fontSizeBase400);
    color: var(--colorNeutralForeground2);
    line-height: var(--lineHeightBase400);
    max-width: 600px;
}
.workflow-stub__notice {
    width: 100%;
    max-width: 540px;
    text-align: start;
    background: var(--colorNeutralBackground1);
    border: 1px solid var(--colorNeutralStroke3);
    border-radius: var(--borderRadiusMedium);
    padding: var(--spacingVerticalL) var(--spacingHorizontalL);
}
.workflow-stub__notice-title {
    margin: 0 0 var(--spacingVerticalS);
    font-size: var(--fontSizeBase400);
    font-weight: var(--fontWeightSemibold);
    color: var(--colorNeutralForeground1);
}
.workflow-stub__steps {
    margin: 0;
    padding-inline-start: var(--spacingHorizontalL);
    display: grid;
    gap: var(--spacingVerticalS);
    color: var(--colorNeutralForeground2);
    line-height: var(--lineHeightBase300);
}
.workflow-stub__actions {
    display: flex;
    gap: var(--spacingHorizontalM);
    flex-wrap: wrap;
    justify-content: center;
}
.workflow-stub__disclaimer {
    margin: 0;
    font-size: var(--fontSizeBase200);
    color: var(--colorNeutralForeground3);
    max-width: 540px;
}

/* ============================================================================
   Shared-partials CSS (PR 14) — Polytxt
   ----------------------------------------------------------------------------
   New classes backing the 4 net-new partials introduced in PR 14:
     - .loading-state            (wraps existing .skeleton-row)
     - .error-state              (new)
     - .breadcrumbs              (new)
     - .pagination               (new)
   Existing classes reused by partials, NOT re-defined here:
     - .empty-state    (already at line ~2828)
     - .skeleton-row   (already at line ~3501)
     - .stepper / .step-item   (already at lines ~2234/2261)
   ============================================================================ */

/* --- Loading state -------------------------------------------------------- */
.loading-state {
    display: flex;
    flex-direction: column;
    gap: var(--spacingVerticalS);
    padding: var(--spacingVerticalM) 0;
}
.loading-state__sr-only {
    position: absolute;
    width: 1px; height: 1px;
    overflow: hidden;
    clip: rect(0 0 0 0);
    white-space: nowrap;
}

/* --- Error state ---------------------------------------------------------- */
.error-state {
    display: flex;
    gap: var(--spacingHorizontalM);
    padding: var(--spacingVerticalL) var(--spacingHorizontalL);
    background: var(--colorNeutralBackground2);
    border: 1px solid var(--colorNeutralStroke2);
    border-inline-start: 4px solid var(--statusDangerForeground);
    border-radius: var(--borderRadiusMedium);
    color: var(--colorNeutralForeground1);
}
.error-state__icon {
    flex: 0 0 auto;
    width: 32px;
    height: 32px;
    border-radius: var(--borderRadiusCircular);
    background: var(--statusDangerBackground);
    color: var(--statusDangerForeground);
    display: inline-flex;
    align-items: center;
    justify-content: center;
    font-weight: var(--fontWeightBold);
    font-size: var(--fontSizeBase400);
}
.error-state__body {
    flex: 1 1 auto;
    min-width: 0;
}
.error-state__title {
    font-size: var(--fontSizeBase400);
    font-weight: var(--fontWeightSemibold);
    margin: 0 0 var(--spacingVerticalXS);
}
.error-state__message {
    color: var(--colorNeutralForeground2);
    margin: 0 0 var(--spacingVerticalM);
}
.error-state__details {
    margin-top: var(--spacingVerticalS);
    font-size: var(--fontSizeBase200);
}
.error-state__details summary {
    cursor: pointer;
    color: var(--colorNeutralForeground3);
}
.error-state__details pre {
    margin-top: var(--spacingVerticalXS);
    padding: var(--spacingVerticalS);
    background: var(--colorNeutralBackground3);
    border-radius: var(--borderRadiusSmall);
    overflow-x: auto;
    font-family: var(--prim-font-family-mono, monospace);
}

/* --- Breadcrumbs ---------------------------------------------------------- */
.breadcrumbs {
    margin: 0 0 var(--spacingVerticalM);
}
.breadcrumbs__list {
    display: flex;
    flex-wrap: wrap;
    align-items: center;
    gap: var(--spacingHorizontalXS);
    padding: 0;
    margin: 0;
    list-style: none;
    font-size: var(--fontSizeBase200);
}
.breadcrumbs__item {
    display: inline-flex;
    align-items: center;
    gap: var(--spacingHorizontalXS);
}
.breadcrumbs__link {
    color: var(--colorBrandForegroundLink);
    text-decoration: none;
}
.breadcrumbs__link:hover {
    text-decoration: underline;
}
.breadcrumbs__separator {
    color: var(--colorNeutralForeground3);
    font-weight: var(--fontWeightRegular);
}
.breadcrumbs__current {
    color: var(--colorNeutralForeground2);
    font-weight: var(--fontWeightMedium);
}

/* --- Pagination ----------------------------------------------------------- */
.pagination {
    display: flex;
    align-items: center;
    justify-content: center;
    gap: var(--spacingHorizontalL);
    padding: var(--spacingVerticalM) 0;
    font-size: var(--fontSizeBase300);
}
.pagination__prev,
.pagination__next {
    color: var(--colorBrandForegroundLink);
    text-decoration: none;
    padding: var(--spacingVerticalXS) var(--spacingHorizontalS);
    border-radius: var(--borderRadiusMedium);
}
.pagination__prev:hover,
.pagination__next:hover {
    background: var(--colorNeutralBackground3);
    text-decoration: underline;
}
.pagination__prev--disabled,
.pagination__next--disabled {
    color: var(--colorNeutralForegroundDisabled);
    cursor: not-allowed;
}
.pagination__current {
    color: var(--colorNeutralForeground2);
    font-variant-numeric: tabular-nums;
}

/* --- Step progress -------------------------------------------------------- */
/* `.step-progress` is a thin wrapper applied alongside the existing `.stepper`
   class. The visual chrome comes from `.stepper` + `.step-item`; these are
   BEM-aliased so the partial can render `.step-progress__*` consistently
   across both surfaces, and the existence scanner can pin every selector
   referenced from the manifest. */
.step-progress__step {
    /* Alias for `.step-item` — the partial applies both classes, so the
       visual rules come from `.step-item`. This selector exists so the
       manifest's existence scanner can verify the contract. */
}
.step-progress__number {
    /* Alias for `.step-item__number` — see above. */
}
.step-progress__label {
    /* Alias for `.step-item__title` — see above. */
}
.step-progress__step--upcoming .step-item__number {
    background: var(--colorNeutralBackground3);
    color: var(--colorNeutralForeground3);
}
.step-progress__step--current .step-item__title {
    color: var(--colorBrandForeground1);
    font-weight: var(--fontWeightBold);
}
.step-progress__step--complete .step-item__number {
    background: var(--statusSuccessBackground);
    color: var(--statusSuccessForeground);
}
.step-progress .visually-hidden {
    position: absolute;
    width: 1px; height: 1px;
    overflow: hidden;
    clip: rect(0 0 0 0);
    white-space: nowrap;
}

/* ============================================================================
   35. Foil landing redesign  —  Claude Design "Polytxt Landing Redesign"
   --------------------------------------------------------------------------
   The editorial-foil B2C landing (ADR 0021 landing redesign). Reuses the
   live token system; new colours come from the foil.* / prim.foil-*
   tokens (JSON-sourced). Iridescent treatment is the animated .foil-holo.
   All colours go through tokens (no bare hex); all directional spacing
   uses logical properties (RTL-safe). The certificate centrepiece renders
   as a faithful STATIC preview here; the generation/scramble/hover-link
   animation is layered on in a later pass (still aria-hidden + decorative).
   ============================================================================ */

/* — iridescent foil treatment (animated) — */
@keyframes foilHolo { 0% { background-position: 0% 50%; } 100% { background-position: 300% 50%; } }
.foil-holo {
    background: var(--gradientHolo);
    background-size: 300% 100%;
    animation: foilHolo 9s linear infinite;
}
.foil-rule { height: 4px; width: 100%; }
.foil-rule--thin { height: 3px; }

/* — page shell — */
.foil-landing {
    position: relative;
    background: var(--foil-canvas);
    /* `clip`, not `hidden`. Both stop the horizontal bleed this exists for
       (2026-06-30 mobile clipping incident), but `hidden` on ONE axis promotes
       the other from visible to auto — measured here as computed
       overflow ['hidden','auto'], i.e. this element was silently a scroll
       container 3937px tall, which also disables position:sticky inside it.
       `clip` is defined not to promote: the same element now computes
       ['clip','visible'] and is not a scrollport. A position:fixed descendant
       still escapes it (measured), and so does the top layer.

       This is now a NET, not the mechanism. The only element that actually
       overflowed was `.lb-cloud__word` — "Español (latinoamericano)" is
       404px wide at 30px and `white-space: nowrap`, which put document
       scrollWidth at 382 against a 360 client width. That is fixed at source
       below (§ language cloud). The net is kept because the bands below carry
       translated copy in 66 locales and a long unbreakable word in a locale
       this sweep cannot reach must clip rather than scroll the page. */
    overflow-x: clip;
    overflow-y: visible;
}
.foil-landing__glow {
    position: absolute;
    inset-block-start: 0;
    inset-inline: 0;
    block-size: 720px;
    pointer-events: none;
    background: radial-gradient(70% 60% at 82% 4%,
        color-mix(in srgb, var(--colorBrandBackground) 6%, transparent), transparent 55%);
}
.foil-landing > .container,
.foil-landing > .foil-section { position: relative; z-index: 1; }
/* THE hero's one rank above its siblings, and the whole of the layering fix.
 *
 * `.foil-landing` is position:relative + z-index:auto, so it is NOT a stacking
 * context — which means every band above is a direct child of the ROOT stacking
 * context, all four of them tied at z-index 1 (#hero-upload, #how-it-works,
 * #languages, .lb-band(why)). A tie is broken by DOM order, so the later band
 * painted straight over anything the hero hung below its own box: measured
 * 6.49% of the target-language panel stolen at 768x1024 and 2.63% at 1280x800,
 * thief `SECTION#how-it-works` in every instance.
 *
 * One declaration takes that to 0 stolen in 40/40 on-screen cells. Do NOT also
 * bump `.foil-hero__panel-wrap` — measured byte-identical to changing nothing
 * (4 bad cells, 650 stolen either way), because `container-type: inline-size`
 * does not seal it in this engine.
 *
 * Blast radius is exactly the three sibling bands. It cannot reach `.top-nav`
 * (50), `.modal__backdrop` (100), `.drawer` (101) or `.skip-link` (1000) — the
 * sticky header still wins, deliberately, which is why the placer clamps the
 * panel out of the header band rather than trying to outrank it.
 *
 * A separate rule, never an edit to the one above: retyping a shipped rule to
 * change part of it is the 2026-08-08 declaration-loss incident.
 *
 * `position: relative` is RESTATED here rather than inherited from the rule
 * above. It is a no-op at render time — the element already computes relative —
 * but a z-index only ranks anything on a positioned box, and stating the rank
 * without the thing that makes it real leaves the rule true only for as long as
 * the shared rule above keeps matching. `ops.tests_overlay_clipping_invariants`
 * reads one rule at a time and scored this band at rank -inf until the position
 * was restated, which is the same blind spot a human review has. */
.foil-landing > #hero-upload { position: relative; z-index: 2; }

/* — section scaffold — */
.foil-section { padding-block: clamp(56px, 9vw, 96px); }
.foil-section--surface { background: var(--colorNeutralBackground1); border-block: var(--strokeWidthThin) solid var(--colorNeutralStroke3); }
.foil-section--canvas  { background: var(--foil-canvas); }
/* The design's page grid: 1200px of content inside 120px gutters at
   1440 (Figma 233:1256 nav and 233:1277 hero are both px-120, and the
   hero grid inside them is exactly 1200 wide). */
.foil-section__inner { max-width: 1440px; margin-inline: auto; padding-inline: clamp(24px, 8.33vw, 120px); }
/* Between the stack breakpoint and the design's full width there is not
   enough room for a 120px-scale gutter AND the hero's two columns. The
   gutter gives way first — it is the element with the least to say. */
@media (min-width: 1100px) and (max-width: 1439px) {
    .foil-section__inner { padding-inline: clamp(40px, 4vw, 72px); }
}
/* At the design's own reference width the gutter is DEFINED as whatever is
   left over after the 1200px content box, rather than measured in vw. vw
   includes the scrollbar and a percentage does not, so on a platform that
   still draws a classic 15px scrollbar the vw form left only 1185px of
   content — enough to push the hero preview below its stack threshold and
   stack the documents at 1440px, which is where the design says they sit
   side by side. */
@media (min-width: 1440px) {
    .foil-section__inner { padding-inline: clamp(24px, calc((100% - 1200px) / 2), 120px); }
}

/* — eyebrow (dash + uppercase label) — */
.foil-eyebrow {
    display: inline-flex; align-items: center; gap: var(--spacingHorizontalS);
    font-size: var(--fontSizeBase200); font-weight: var(--fontWeightBold);
    letter-spacing: 0.1em; text-transform: uppercase;
    color: var(--colorBrandForeground2);
}
.foil-eyebrow__dash { inline-size: 22px; block-size: 2px; border-radius: 2px; flex: 0 0 auto; }
.foil-eyebrow--center { justify-content: center; }

/* — pill eyebrow (hero) — */

/* ── HERO ───────────────────────────────────────────────────────────────── */
/* Figma 267:5461 band padding is 88px; the clamp keeps the narrow-viewport
   behaviour that was measured earlier in the sprint. */
.foil-hero { padding-block: clamp(40px, 7vw, 88px); }
.foil-hero__grid { display: grid; grid-template-columns: 1fr; gap: 40px; align-items: start; }
/* Grid items must be allowed to shrink below their content's min-content,
   or the certificate's intrinsic width forces the whole column (incl. the
   h1) wider than the viewport — clipped by .foil-landing's overflow:hidden
   on mobile. (2026-06-30 mobile clipping incident — screenshot-caught.) */
.foil-hero__grid > * { min-inline-size: 0; }
@media (min-width: 1100px) {
    /* The design pins the copy column at 400px (Figma 248:742 / 248:2495).
       It is written as a minmax so the column can GIVE GROUND before the
       layout breaks: at 400px flat, the document side fell below the width
       the diptych needs everywhere under about 1370px, and because the card
       is end-aligned the overflow spilled back OVER the slogan and the
       uploader rather than off the side of the screen.

       The floor is 360px. Below the width where even that is not enough the
       @media block further down stacks the two columns instead. */
    .foil-hero__grid { grid-template-columns: minmax(320px, 400px) minmax(0, 1fr); gap: 56px; }
    .foil-hero__panel-wrap { justify-self: end; }
}
/* Figma 267:5461 puts the two hero columns on ONE vertical centre line, not on
   a shared top edge. Measured, the element that moves is the LEFT copy column:
   `.foil-hero__panel-wrap` is the taller grid item at every width where the
   diptych is side by side, so it already fills the row and `align-items:
   center` displaces it by exactly 0.00px.

   1300, NOT the 1100 where the two-column grid starts. The diptych's
   side-by-side/stacked decision is a CONTAINER query on the wrap at 738px, and
   the wrap is only `content - 400 - 56` wide. In the 1100-1439 gutter regime
   content = 0.92vw, so wrap >= 738 needs vw >= 1297.8 — below that the wrap
   STACKS and stands ~1281px tall, and centring a 502px copy column against it
   opened a ~470px void above the H1 (measured +389.27px displacement at 1280
   en-gb, +470.27px at 1100 ar; the badges, H1, intake tabs and dropzone all
   started below the fold). 1300 is the first round number clear of that
   crossover: 1298 gives a 738.16px wrap, 1297 gives 737.24px. At >=1440 the
   gutter rule pins content to 1200 and the wrap to 744, also side by side.

   Deliberately NOT `align-self: center` on `.foil-hero__panel-wrap` — that is
   the shape the warning above the z-index rule forbids, and it is also inert
   (measured 0.00px at every viewport, because the wrap IS the row height).
   The base rule's `align-items: start` survives untouched below 1300.

   THE HEIGHT CONDITION IS NOT DECORATION. Centring lifts the copy column by
   ~105px in English and ~134px in Arabic, which pushes the upload box off the
   first screen on ordinary laptops. Measured with centring on: en-gb 1366x660
   clears by -53, ar 1536x730 by -52, ar 1440x720 by -67, ar 1366x660 by -122.
   Arabic is consistently ~87px worse because its panel is taller, and its
   worst measured box bottom is 786.7 with about 45px of load-to-load
   variation — so the threshold comes from Arabic's worst case plus that
   variance, not from English's comfortable one. Below 860 the column falls
   back to `start`, which puts the box at 614.5 and clears every height
   tested. At 900 — the common case — centring still applies and still
   clears, by 113px in Arabic. */
@media (min-width: 1300px) and (min-height: 860px) {
    .foil-hero__grid { align-items: center; }
}
/* Figma 267:5461 rhythm: 22 badge->h1, 22 h1->intake, 16 intake->dropzone,
   22 dropzone->legal. A single `gap` cannot express four different steps, and
   the 24px one here was compounding with each child's own margin into a flat
   40/40/40. The steps now live on the children's margins instead — flex items
   never collapse margins, so each number is exactly what it says.
   The intake->dropzone 16 is deliberately the INTAKE's margin-block-end and
   not anything on .foil-hero__drop-wrap: HTMX replaces #hero-upload-card
   outerHTML with _start_confirm_form.html, which carries no such class, so a
   step hung there would vanish the moment a visitor uploads a file. */
.foil-hero__copy { display: flex; flex-direction: column; align-items: flex-start; gap: 0; }
.foil-hero__title {
    font-size: clamp(36px, 6vw, 52px); font-weight: var(--fontWeightBold);
    letter-spacing: -0.03em; line-height: 1.08; color: var(--colorNeutralForeground1); margin: 0;
}
/* Figma 267:5461: the accent word carries the same 90deg accent-to-rule-4
   gradient as the eyebrows and the word cloud, not a flat Fluent brand blue.
   background-clip: text needs a transparent fill; the colour is declared
   first so an engine that drops background-clip still paints readable ink
   (§16.7 step 2 — the no-support fallback must be usable). */
.foil-hero__title-accent {
    /* Its own line. The <br> that used to do this lived inside the
       translatable string, which put an English-chosen break point into 66
       languages. A block-level accent breaks correctly at every width and in
       both directions without either half of the headline knowing about the
       other. */
    display: block;
    color: var(--foil-hd-accent);
    background-image: linear-gradient(90deg, var(--foil-hd-accent) 25%, var(--foil-lb-rule-4) 75%);
    -webkit-background-clip: text;
    background-clip: text;
    -webkit-text-fill-color: transparent;
}
@supports not ((background-clip: text) or (-webkit-background-clip: text)) {
    .foil-hero__title-accent { -webkit-text-fill-color: currentColor; }
}

.foil-hero__drop-wrap { inline-size: 100%; max-width: 460px; }

/* — hero preview panel (holds the certificate) — */
/* The design has exactly ONE card here (Figma 233:2602 "cert content").
   It must NOT clip: the translation panel is positioned to overhang the
   card's outer edge, and that overhang is the whole visual idea. */
/* Transparent. The card look moved onto the SOURCE pane so that each pane
   hugs its own document instead of both stretching to the taller one
   (owner, 2026-08-07: "two panels, original and translated, both hugging the
   document, not each other"). This element is now only a positioning and
   container context. */
.foil-hero__panel-wrap {
    inline-size: 100%;
    display: flex;
    flex-direction: column;
    align-items: flex-end;
    /* Clearance under the document switcher. Declared here, not inside a
       media query: it applies at every width, which is the point — it used
       to be zero below 1100px because the whole rule stopped applying. */
    gap: 16px;
    /* The diptych sizes itself against THIS box. It has to be an element
       whose width comes from its parent — inline-size containment stops an
       element sizing to its content, so putting it on the card collapsed the
       card to zero. */
    container-type: inline-size;
    container-name: hero-preview;

    /* Containing block for the outgoing clone (see "document switch" below). */
    position: relative;

    /* ONE tempo. Every duration and delay in the switch is this number times a
       ratio, so "heavier" is a single-token edit and no clock can drift. */
    --hd-beat: var(--durationGentle);

    /* One step along the switcher. Deliberately CONSTANT: it means "one
       position through the set", not "this document is 40px narrower". The
       motion this replaces slid 80px because one sheet is 320 wide and the
       other 280 — a number nobody asked for, and 0px under RTL. */
    --hd-travel: 44px;

    /* +1 later in the switcher, -1 earlier, 0 for a language change. Written
       by hero_doc_picker.js at htmx:beforeSwap. */
    --hd-dir: 0;

    /* translate3d is a physical axis with no logical form, so the mirror has
       to be explicit. ops/tests_rtl_invariants.py matches margin/padding/
       border/text-align/float only — it CANNOT see a transform, so this one
       line is the whole of this motion's RTL correctness. */
    --hd-axis: 1;

    /* The row's height eases between two MEASURED pixel heights, driven by
       hero_doc_picker.js. A content-driven auto -> auto change cannot
       transition: the specified value is `auto` on both sides and CSS does not
       interpolate auto. Measured on this build before the change — the row
       moved 714 -> 499 (1440), 1283 -> 897 (1100) and 1510 -> 962 (390) in ONE
       frame, 2 distinct values, 100% of the delta in a single step. Explicit
       px -> px is the mechanism that works. The duration is written inline per
       swap because it is computed from the DISTANCE; 0s at rest so a window
       resize tracks live instead of easing. */
    --hd-height-duration: 0s;
    transition: block-size var(--hd-height-duration) var(--curveHeroHeight);
}
/* Only while a height ramp is in flight. The incoming document is laid out at
   its own full height from the first frame — it is the ROW that eases — so on
   a grow the sheet is taller than its box for the length of the ramp and would
   otherwise paint over the band below. `visible clip` is the one axis pair CSS
   does not promote to auto, and the inline axis must stay unclipped because
   the translation panel deliberately overhangs the card: the same pair, for
   the same reason, as .hd-stage--frozen. flex-shrink:0 guarantees the sheet is
   CLIPPED and never SQUASHED while the box is shorter than it. */
.foil-hero__panel-wrap.hd-height-ramp { overflow-x: visible; overflow-y: clip; }
.foil-hero__panel-wrap.hd-height-ramp > * { flex-shrink: 0; }

@media (prefers-reduced-motion: reduce) {
    /* Belt to hero_doc_picker.js, which does not pin the row at all. Instant
       is the honest reduced-motion answer to a height change, and a stale pin
       would freeze the hero at the previous document's height for that visitor
       permanently. */
    .foil-hero__panel-wrap { transition: none; }
}
.foil-hero__panel {
    position: relative;
    background: none;
    border: 0;
    box-shadow: none;
}
/* The diptych decides side-by-side vs stacked on ITS OWN width, not the
   window's — at 1024px the preview had 851px to play with and only needed
   688, yet a viewport breakpoint stacked it anyway. */
.foil-hero__panel-body { padding: 0; }

/* ── FOIL DROPZONE (wraps the real upload form) ───────────────────────────── */
.foil-drop {
    position: relative; display: block; padding: 2px; cursor: pointer;
    /* Figma 267:5461: 16px, not 8. The 2px padding is the holo ring, so the
       inner radius below is this minus 2 and the two curves stay concentric. */
    border-radius: var(--borderRadius3XLarge);
    transition: box-shadow var(--durationNormal) var(--curveDecelerate),
                transform var(--durationNormal) var(--curveDecelerate);
}
.foil-drop:hover { transform: translateY(-1px);
    box-shadow: 0 10px 26px -10px color-mix(in srgb, var(--colorBrandBackground) 45%, transparent); }
.foil-drop:focus-within { box-shadow: 0 0 0 3px color-mix(in srgb, var(--colorBrandBackground) 28%, transparent); }
/* The file input is a SIBLING of the .foil-drop label (not a descendant), so
   :focus-within never fires on keyboard focus — mirror the ring off the
   input's own focus so the dropzone shows a visible focus state (WCAG 2.4.7). */
.foil-drop__file-input:focus-visible + .foil-drop { box-shadow: 0 0 0 3px color-mix(in srgb, var(--colorBrandBackground) 28%, transparent); }
/* With JS, document_upload.js auto-submits on file-change / drop, so the no-JS
   "Continue" fallback submit is redundant (the design is drop-only). Hide it
   when JS is present (html.js-reveal is set in base_polytxt.html); no-JS keeps
   it so the form is still submittable. */
html.js-reveal #upload-submit { display: none; }
.foil-drop__inner {
    position: relative; z-index: 1; display: flex; align-items: center; gap: var(--spacingVerticalMNudge);
    /* Figma 267:5461: the dropzone is a CENTRED COLUMN — icon tile stacked
       above the copy — inside a 400x164 box, not the icon-beside-copy row this
       shipped as. `align-items: center` was already here and keeps earning its
       place: a column simply re-points it at the inline axis, which is what
       centres the tile and the copy horizontally. `text-align: center` covers
       the text runs themselves; `center` is direction-neutral, so it is
       RTL-safe and is deliberately not matched by ops/tests_rtl_invariants,
       which flags only left|right.
       The height is a MINIMUM, never `block-size: 164px`. This box measures
       400 wide only at >=1100px — 460 at 900 and 325 at 390 — and the title
       wraps at the narrow widths and in the longer locales, where a fixed
       height would clip the copy. 160 here + the 2px holo ring top and bottom
       is the design's 164. */
    flex-direction: column; justify-content: center;
    min-block-size: 160px; text-align: center;
    border-radius: calc(var(--borderRadius3XLarge) - 2px);
    padding: var(--spacingVerticalXL) var(--spacingHorizontalXXL);
    background: var(--colorNeutralBackground1);
}
/* Figma 267:5461: the tile is 48x48 at radius 12, not 52 at 8. Shrinking it
   by 4px each way lowers the dropzone's intrinsic height, so the 164px
   minimum on .foil-drop__inner must be re-derived AFTER this, not before. */
.foil-drop__icon {
    display: inline-flex; align-items: center; justify-content: center;
    inline-size: 48px; block-size: 48px; flex: 0 0 auto;
    border-radius: var(--borderRadiusXXLarge);
    background: var(--colorBrandBackgroundTint); color: var(--colorBrandForeground2);
}
.foil-drop__icon .icon { inline-size: 24px; block-size: 24px; }
/* `flex: 0 0 auto`, NOT `1 1 auto`. Once .foil-drop__inner became a column the
   main axis is vertical, and a grow factor would have eaten the ~60px of slack
   the 160px minimum creates — pushing the copy away from the tile while
   `gap` still computed as the design's 10px and every computed-style check
   still read clean. min-inline-size: 0 stays: it is what lets a long filename
   in the Alpine hasFile state shrink instead of forcing the box wider. */
.foil-drop__text { flex: 0 0 auto; min-inline-size: 0; }
/* Figma 267:5461: 16px, one line. At 18px in the old row layout the copy had
   only 280px and wrapped to two lines; the centred column gives it the full
   348px and the smaller size fits it on one. */
.foil-drop__title { display: block; font-size: var(--fontSizeBase400); font-weight: var(--fontWeightSemibold); color: var(--colorNeutralForeground1); }
/* 13px, per Figma 267:5461. A bare 13px is precedented in this file (see
   .lb-eyebrow__text) and is not a colour, so no token gate applies. */
.foil-drop__hint  { display: block; font-size: 13px; color: var(--colorNeutralForeground3); }
/* document_upload.js toggles .is-active on #upload-dropzone during drag. */
.foil-drop.is-active { box-shadow: 0 0 0 3px color-mix(in srgb, var(--colorBrandBackground) 28%, transparent); }
.foil-drop.is-active .foil-drop__inner { background: var(--colorBrandBackgroundTint); }
.foil-drop__file-input { position: absolute; width: 1px; height: 1px; opacity: 0; pointer-events: none; }
/* 22px, the last step of the hero rhythm. Adjacent block siblings inside the
   form, so this collapses with the label's 0 to exactly 22. */
.foil-drop__legal { font-size: var(--fontSizeBase200); color: var(--foil-lb-legal-ink); line-height: 1.5; margin-block: 22px 0; }
/* Figma 292:487/292:488 (round 15): the legal line is no longer blue-on-grey.
   The design INVERTS the old emphasis — the base run is the LIGHTER neutral at
   400 and the two links are the DARKER neutral at 500, so weight and depth
   carry what hue used to. Neither colour is retargeted at the token: the base
   gets its own --foil-lb-legal-ink (its value exists elsewhere only as
   --foil-lb-footer-divider, a 1px hairline — coupling body ink to a divider
   would make each rule's value hostage to the other), and the links reuse
   --colorNeutralForeground2, which already carries the design's link value and
   is left untouched for its 63 other call sites. --prim-blue-80 keeps its
   --brand80 consumer. The hover underline (global `a:hover`) and the focus
   ring are deliberately kept: with the blue gone they are the only non-weight
   affordance that these are links.
   (Values live in the JSON token pipeline, not here — a hex literal in this
   file counts against polytxt.tests_polytxt_token_aliases even in a comment.) */
.foil-drop__legal a { color: var(--colorNeutralForeground2); font-weight: var(--fontWeightMedium); }

/* ── CERTIFICATE DIPTYCH (static preview) ─────────────────────────────────── */

/* — the certificate document itself — */

/* ── HOW IT WORKS ─────────────────────────────────────────────────────────── */

@media (min-width: 760px) { }

/* colorNeutralStrokeAccessible (grey-38) not the hairline stroke token — the
   step numbers carry ordinal meaning, so they must clear the 3:1 large-text
   contrast bar (audit rank 7). */
.foil-how__num { font-size: var(--fontSizeBase600); font-weight: var(--fontWeightBold); color: var(--colorNeutralStrokeAccessible); font-variant-numeric: tabular-nums; }

.foil-card-body { font-size: var(--fontSizeBase400); color: var(--colorNeutralForeground2); line-height: 1.55; margin: 0; }

/* ── WHY (value strip) ───────────────────────────────────────────────────── */
@media (min-width: 760px) { .foil-why__grid { gap: 48px; } }

/* ── LANGUAGES ───────────────────────────────────────────────────────────── */

@media (min-width: 880px) { }

.foil-lang-pill {
    padding-block: var(--spacingVerticalS); padding-inline: var(--spacingHorizontalL);
    border-radius: var(--borderRadiusCircular); background: var(--foil-paper);
    border: var(--strokeWidthThin) solid var(--colorNeutralStroke1); box-shadow: var(--shadowSoft2);
    font-size: var(--fontSizeBase300); font-weight: var(--fontWeightMedium); color: var(--colorNeutralForeground2);
}
.foil-lang-pill--more { display: inline-flex; align-items: center; padding-inline: var(--spacingHorizontalM); background: none; border: none; box-shadow: none; color: var(--colorNeutralForeground3); }

/* ── CLOSING CTA ─────────────────────────────────────────────────────────── */

/* — narrow viewports: stack the certificate diptych (Original above
   Translation) so each paper gets the full column width and the document
   reflows legibly, instead of two cramped/clipped columns. Mirrors the
   design's w<560 stack. The AI beam becomes a short horizontal divider. — */
@media (max-width: 600px) {
}

/* ── Site-language menu — ADR 0065 region-grouped popover ─────────────────── */

/* COLUMN LADDER — the panel is a whole number of option columns wide, never a
   viewport fraction (owner, 2026-08-09: "I want the menu to hug the content …
   instead of filling up the screen width and creating this void").

   HISTORY, because this surface has now failed twice in opposite directions:
   v1 `inline-size: min(934px, 94vw)` stated a width in viewport terms while
   auto-fill fitted whole 220px tracks into it — the remainder painted as an
   empty column (22px on desktop, 212px at 960). v2 derived the width with
   round(down, ...) — exact in the container's Chromium, and in the owner's own
   browser the unparsed function dropped the declaration, the panel went
   indefinite, and auto-fill resolved to ONE column. Both menus, one column,
   reported by the owner within the hour.

   v3, below, states the column count outright:
     - `--lm-n` is an integer set by plain media queries (1/2/3/4, stepping at
       511 / 733 / 955 — worst-case classic scrollbar allowed for);
     - the grid is `repeat(var(--lm-n), 220px)`, so it is always definite;
     - the panel is `inline-size: max-content` and simply wraps the grid;
     - `scrollbar-gutter: stable` on the list reserves a classic scrollbar's
       lane so it can never eat the last column (overlay platforms reserve 0).
   Every piece is decade-old CSS. Nothing here can fail to parse, and if a
   future edit reintroduces derived width, the browser-support question is part
   of the change, not an afterthought.

   --lm-col / --lm-pad / --lm-sbw remain declared for the placer script, which
   still reads them for positioning arithmetic. */
:root {
    --lm-col: 222px;
    --lm-pad: 24px;
    --lm-sbw: 0px;
    /* Column COUNT, stated outright per viewport band rather than derived by
       arithmetic the browser may not speak. The round(down, ...) formula this
       replaces was correct in the container's Chromium and collapsed BOTH
       menus to a single column in the owner's own browser: an engine that
       cannot parse round() drops the whole inline-size declaration, the panel
       goes indefinite, and repeat(auto-fill, 220px) resolves to exactly one
       track against an indefinite size — the documented trap fired through
       its own guard. Media queries and repeat(var(), 220px) parse everywhere.
       Boundaries assume the worst classic scrollbar (17px): panel needs
       222N + 26 + sbw, plus a 12px gutter each side => vw >= 222N + 67. */
    --lm-n: 1;
}
@media (min-width: 511px) { :root { --lm-n: 2; } }
@media (min-width: 733px) { :root { --lm-n: 3; } }
@media (min-width: 955px) { :root { --lm-n: 4; } }

.lang-menu { position: relative; display: inline-flex; }
.lang-menu__trigger {
    display: inline-flex; align-items: center; gap: var(--spacingHorizontalSNudge);
    cursor: pointer; font-family: inherit; white-space: nowrap; border: none; background: none;
    color: var(--colorNeutralForeground2); font-size: var(--fontSizeBase300); font-weight: var(--fontWeightMedium);
    transition: color var(--durationFast) var(--curveEasyEase), background var(--durationFast), border-color var(--durationFast), filter var(--durationFast);
}
.lang-menu__trigger:hover { color: var(--colorNeutralForeground1); }
/* Direction-specific clocks, and both of them are the PANEL's opacity clock:
   the rest-state rule declares the exit, the open-state rule the entrance, so
   the chevron and the panel come to rest together whichever way they are
   going. Both ran 150ms in both directions while the panel's own leave ran 75,
   so the chevron rotated on alone after the panel had gone. */
.lang-menu__chevron { transition: transform var(--durationPanelOut) var(--curvePanelFade); }
.lang-menu__chevron.is-open {
    transform: rotate(180deg);
    transition: transform var(--durationPanelInFade) var(--curvePanelFade);
}

.lang-menu__trigger--footer {
    border: var(--strokeWidthThin) solid var(--colorNeutralStroke1); background: var(--colorNeutralBackground1);
    border-radius: var(--borderRadiusLarge); padding-block: var(--spacingVerticalSNudge); padding-inline: var(--spacingHorizontalMNudge);
    font-size: var(--fontSizeBase200);
}
.lang-menu__trigger--footer:hover { background: var(--colorNeutralBackground2); }

.lang-menu__trigger--link {
    gap: var(--spacingHorizontalS);
    background: var(--colorBrandBackgroundTint); border: var(--strokeWidthThin) solid var(--colorBrandStroke2);
    color: var(--colorBrandForeground2); border-radius: var(--borderRadiusCircular);
    padding-block: var(--spacingVerticalS); padding-inline: var(--spacingHorizontalL);
    font-size: var(--fontSizeBase300); font-weight: var(--fontWeightSemibold);
}
.lang-menu__trigger--link:hover { color: var(--colorBrandForeground2); filter: brightness(0.97); }

.lang-menu__pop {
    position: absolute; z-index: var(--z-drawer); inset-block-start: calc(100% + var(--spacingVerticalMNudge));
    inset-inline-end: 0;
    /* 100vw - 44px is the trigger-anchored allowance: above 880px the panel's
       inline-end edge sits on the trigger's, and the trigger's own end edge is
       measured at clientWidth - 20px (container padding) up to 1120px, further
       in above it, so this leaves a >=12px inline-start gutter at every width
       (measured popL: 24px at 956, 171px at 881, never below 12). The 4-column
       ceiling `--lm-col * 4` is the design's own maximum and binds from 956px up;
       max() keeps a single column alive on a viewport too narrow for the
       round() to return one. */
    /* The grid below is definite (repeat(var(--lm-n), 220px)), so the panel
       simply wraps it. scrollbar-gutter on the list keeps a classic scrollbar
       from eating the last column. The clamp is the same [12, 100vw-12]
       corridor the placer uses. */
    inline-size: max-content;
    max-inline-size: calc(100vw - 24px);
    background: var(--colorNeutralBackground1); border-radius: var(--borderRadius3XLarge);
    border: var(--strokeWidthThin) solid var(--colorNeutralStroke3); box-shadow: var(--shadowSoft28); overflow: hidden;
}
.lang-menu__pop--start { inset-inline-end: auto; inset-inline-start: 0; }

/* The preview picker's design width is two columns; the site menu goes to
   four. Same 511px boundary as the :root ladder so the two cannot disagree
   about when one column is all that fits. */
.lang-menu__pop--preview { --lm-n: 2; }
@media (max-width: 510px) { .lang-menu__pop--preview { --lm-n: 1; } }

/* ── panel motion — ONE mechanism for BOTH language panels ────────────────
   Three states, one job each:
     base         closed at rest. No transition on purpose: a panel that is
                  merely mounted-closed must not animate.
     .is-shown    open at rest + the ENTRANCE clock.
     .is-leaving  added by langMenu() for exactly the length of the exit +
                  the EXIT clock. The closed values come from the base rule.

   WHY THE EXIT IS DEFERRED IN JS. x-show writes `display:none` the instant
   its expression goes false, and a display change cancels a transition
   outright. So `shown` lags `open` by the length of the fade and x-show reads
   `shown`, which is what keeps the panel MOUNTED long enough for the exit to
   be seen. Measured before this change: the close painted ONE mid-fade frame
   at 1440 and stepped 0.92 in a single frame at 390.

   NO `display`/`overlay` allow-discrete here, deliberately. Neither panel
   carries a `popover` attribute any more (see the dismissal-ownership note in
   _language_menu.html), so `overlay` has nothing to animate, and `display` is
   written INLINE by x-show — a transition declared on `.is-shown` cannot see
   it, because the class and the inline display change in the same Alpine
   flush. An inert belt that reads as load-bearing is worse than none.

   will-change is not decoration: the site panel repaints 66 rows per frame. */
.lang-menu__pop { opacity: 0; }
.lang-menu__pop.is-shown {
    opacity: 1;
    will-change: opacity, transform;
    transition:
        opacity   var(--durationPanelInFade) var(--curvePanelFade),
        transform var(--durationPanelIn)     var(--curvePanelMotion);
}
.lang-menu__pop.is-leaving {
    will-change: opacity, transform;
    transition:
        opacity   var(--durationPanelOut) var(--curvePanelFade),
        transform var(--durationPanelOut) var(--curvePanelFade);
}
/* The element is display:none until it opens, so without this it has no
   before-change style to transition FROM. */
@starting-style {
    .lang-menu__pop.is-shown { opacity: 0; }
}

/* The travel. scaleY and NOT a uniform scale: the panel must read as drawn out
   of its trigger WITHOUT moving on the inline axis. x-transition's uniform
   scale(0.95) moved both inline edges by a measured 22.8px on open / 22.64px
   on close at 1440, and below 880px any inline movement fights the placer's
   viewport clamp.

   transform-origin has NO logical form, and ops/tests_rtl_invariants.py
   matches margin / padding / border / text-align / float only — it CANNOT see
   these four lines. The explicit :root[dir="rtl"] mirror is the whole of this
   motion's RTL correctness, exactly as --hd-axis is for the sheet swap. The
   placer anchors the panel's inline-END edge to the trigger in LTR
   (`left = t.right - pw`) and its physical LEFT edge in RTL (`left = t.left`),
   so the corner it is drawn out of mirrors with it.

   Every transform declaration sits inside no-preference so reduced motion
   computes `transform: none` rather than a zero-length animation of one. */
@media (prefers-reduced-motion: no-preference) {
    .lang-menu__pop {
        transform-origin: 100% 0;
        transform: scaleY(var(--panelScaleFrom));
    }
    /* The `link` variant opens from the START edge, so its anchor is the
       other corner — and mirrors the other way. */
    .lang-menu__pop--start { transform-origin: 0 0; }
    :root[dir="rtl"] .lang-menu__pop { transform-origin: 0 0; }
    :root[dir="rtl"] .lang-menu__pop--start { transform-origin: 100% 0; }
    .lang-menu__pop.is-shown { transform: scaleY(1); }
    @starting-style {
        .lang-menu__pop.is-shown { transform: scaleY(var(--panelScaleFrom)); }
    }
}
@media (prefers-reduced-motion: reduce) {
    /* Belt to langMenu(), which uses a zero exit delay here. Assert this by
       COUNTING distinct sampled opacity values, never by reading a duration
       string — Playwright's emulation reports '1e-06s', not '0s'. */
    .lang-menu__pop,
    .lang-menu__pop.is-shown,
    .lang-menu__pop.is-leaving { transition: none; }
}

/* The nav panel's x-transition keeps it PAINTED AND HIT-TESTABLE for ~114ms
   after close() returns — rAF-sampled: at t=96ms it still returned
   DIV.lang-menu__head from elementFromPoint at its own header, at a
   transform-scaled 887.6px wide. A click landing in that window is swallowed by
   a menu the user has already dismissed, which is the owner's "page masked even
   tho the popup is closed" verbatim. Alpine flips this class the instant `open`
   goes false, i.e. on the first frame of the leave, so the fade is seen and
   never felt. Not `visibility` and not `display` — either would cancel the
   leave transition outright. */
.lang-menu__pop--dismissing { pointer-events: none; }

/* Phones + tablets (≤880px): WIDTH ONLY. This block used to also pin the panel
 * to the viewport — `position: fixed; inset-block-start: 68px; inset-inline:
 * 12px; margin-inline: auto` — to stop a trigger-anchored panel overrunning the
 * edge (measured 204px off the LEFT in the ≡ drawer at 375px, 50px off at
 * 768px, first row unclickable).
 *
 * That cured the overflow by severing the panel from its button, and the cure
 * was worse: measured on this build, the drawer panel opened 144.4px ABOVE its
 * own trigger at 390x844 (gapBelow -144.4 LTR / -152.4 RTL), i.e. it covered
 * the control that opened it. Overflow and anchoring are two obligations, and a
 * viewport pin can only ever satisfy one of them.
 *
 * Both are now the placer's job (langMenuPlace in _language_menu.html): it
 * anchors to the trigger AND clamps the box into [12, clientWidth - 12], so the
 * panel is under its button and fully on screen at the same time. Nothing here
 * needs to move it. What survives is the column arithmetic, with the gutter
 * this branch actually uses (100vw - 24px) — the old literal 600px was a width
 * that was not a multiple of a column and left 132px of void across the whole
 * 640-880px band. `inline-size`, not `max-inline-size`: an absolutely
 * positioned box with one inset auto is shrink-to-fit, and auto-fill then
 * resolves to a single 222px track (measured: 246px panel instead of 490px).
 * Still guarded by FoilHeroBrowserTests.test_language_popover_stays_in_viewport_on_mobile. */
@media (max-width: 880px) {
    .lang-menu__pop,
    .lang-menu__pop--start {
        /* max-content + the --lm-n ladder already fit this band; only the
           tighter 12px gutter differs from desktop. */
        max-inline-size: calc(100vw - 24px);
    }
}
.lang-menu__pop-rule { block-size: 3px; }

.lang-menu__head {
    display: flex; align-items: baseline; justify-content: space-between; gap: var(--spacingHorizontalM);
    padding: var(--spacingVerticalL) var(--spacingHorizontalL) var(--spacingVerticalM);
    border-block-end: var(--strokeWidthThin) solid var(--colorNeutralStroke3);
}
.lang-menu__head-title { font-size: var(--fontSizeBase100); font-weight: var(--fontWeightBold); letter-spacing: 0.1em; text-transform: uppercase; color: var(--colorNeutralForeground3); white-space: nowrap; }
.lang-menu__head-count { font-size: var(--fontSizeBase100); color: var(--colorNeutralForeground3); font-variant-numeric: tabular-nums; white-space: nowrap; }

.lang-menu__search { position: relative; display: flex; align-items: center; padding: var(--spacingVerticalM) var(--spacingHorizontalL) 0; }
.lang-menu__search-icon { position: absolute; inset-inline-start: calc(var(--spacingHorizontalL) + var(--spacingHorizontalS)); color: var(--colorNeutralForeground3); pointer-events: none; }
.lang-menu__search-input {
    inline-size: 100%; box-sizing: border-box; font-family: inherit; font-size: var(--fontSizeBase300);
    color: var(--colorNeutralForeground1); background: var(--colorNeutralBackground2);
    border: var(--strokeWidthThin) solid var(--colorNeutralStroke1); border-radius: var(--borderRadiusLarge);
    padding-block: var(--spacingVerticalS); padding-inline-start: var(--spacingHorizontalXXXL); padding-inline-end: var(--spacingHorizontalM);
    outline: none; transition: border-color var(--durationFast), box-shadow var(--durationFast);
}
.lang-menu__search-input:focus { border-color: var(--colorBrandStroke1); box-shadow: 0 0 0 3px color-mix(in srgb, var(--colorBrandBackground) 22%, transparent); }

/* The cap must leave room for what sits ABOVE the list inside the popover —
   the popover's own 68px top offset when it is viewport-anchored, plus the
   title row and the search box, about 130px together. 72vh alone left the
   last row below the fold on a 568px-tall phone (measured 97.3% visible).
   dvh so a mobile browser's collapsing address bar cannot reopen the gap. */
/* overscroll-behavior: contain is what makes scroll-to-close safe to add at all.
   Without it, wheeling past the end of this list CHAINS to the document —
   measured 1568px of page movement from a wheel gesture whose pointer never left
   the list — which fires window 'scroll' and would close the menu while the user
   is scrolling INSIDE it (the owner's first named failure mode). With it:
   window scroll 0, page 0px, and the list still reaches scrollTop 542. */
.lang-menu__list { max-block-size: min(calc(100dvh - 210px), 560px); overflow-y: auto; overscroll-behavior: contain; scrollbar-gutter: stable; padding: var(--spacingVerticalS) var(--spacingHorizontalM) var(--spacingVerticalM); }
.lang-menu__group + .lang-menu__group { margin-block-start: var(--spacingVerticalS); }
.lang-menu__group-head { display: flex; align-items: center; gap: var(--spacingHorizontalMNudge); padding: var(--spacingVerticalS) var(--spacingHorizontalSNudge) var(--spacingVerticalXS); }
.lang-menu__group-label { font-size: var(--fontSizeBase100); font-weight: var(--fontWeightBold); letter-spacing: 0.09em; text-transform: uppercase; color: var(--colorNeutralForeground3); white-space: nowrap; }
.lang-menu__group-rule { flex: 1; block-size: 1px; background: var(--colorNeutralStroke3); }
/* A fixed 220px per option, and as many whole columns as the popover can
   hold. auto-fill degrades on its own — 4 columns, then 3, then 2, then 1 —
   so there are no breakpoints to keep in step with the popover width, and
   the site switcher's placements (nav, drawer) each get whatever their own
   width allows.
   The panel width above is now derived from this same 222px unit, so the two
   rules can no longer disagree about how much space a column needs. Change the
   220px here and --lm-col must move with it (220 + the 2px gap). */
/* repeat(var(--lm-n), ...) and NOT auto-fill: auto-fill against an indefinite
   inline size resolves to ONE track by spec, which is precisely the failure
   the owner saw. With an explicit count the grid is definite and the panel's
   max-content wraps it. */
.lang-menu__group-grid { display: grid; grid-template-columns: repeat(var(--lm-n), 220px); gap: 2px; justify-content: start; }
.lang-menu__form { margin: 0; }

.lang-menu__row {
    display: flex; align-items: center; gap: var(--spacingHorizontalMNudge); inline-size: 220px; text-align: start;
    padding: var(--spacingVerticalSNudge) var(--spacingHorizontalS); border: none; cursor: pointer;
    font-family: inherit; border-radius: var(--borderRadiusLarge); background: transparent;
    transition: background var(--durationFast) var(--curveEasyEase);
}
.lang-menu__row:hover, .lang-menu__row:focus-visible { background: var(--colorBrandBackgroundTint); outline: none; }
.lang-menu__row.is-selected { background: var(--colorBrandBackground); }
/* Keyboard-focusing the ALREADY-SELECTED row produced literally zero visual
   change: the rule above is (0,2,0) and later than the :focus-visible tint on
   the line above it, so it wins the background — measured rgb(15,108,189) at
   rest, on hover AND on keyboard focus, three identical readings — while the
   `outline: none` on that same line deletes the global ring from :2153.
   Fixed by ADDING rules rather than editing that line: the background fight is
   the wrong fight (a selected row that pales to the hover tint reads as
   DEselecting), and .is-selected declares no outline at all, so a ring paints
   regardless of who wins the background.
   The offset is INSET on purpose: .lang-menu__list is overflow-y:auto and
   .lang-menu__group-grid has a 2px gap, so the global +2px outward ring would
   be clipped on the first and last row and would touch the neighbours.
   TWICE the width, not once, and that is the whole difference between a ring
   and an optical illusion. At -2px the outline occupies depth 0-2, i.e. the
   row's own outer edge — and since every row sits on the popover's #ffffff,
   a WHITE ring there simply merges into the background and reads as the blue
   pill getting 2px smaller. Measured: unfocused = 4px white then blue;
   focused = 6px white then blue. Real, but not a focus indicator.
   At -4px the outline occupies depth 2-4, so the row's own colour survives
   OUTSIDE it as well as inside and the ring is inscribed ON the pill with
   its own colour on both sides. */
.lang-menu__row:focus-visible {
    outline: var(--strokeWidthThick) solid var(--colorStrokeFocus2);
    outline-offset: calc(-2 * var(--strokeWidthThick));
}
/* --colorStrokeFocus2 IS --colorBrandBackground (both #0f6cbd), so the default
   ring would be a blue ring on a blue row — invisible. The selected row rings
   in white instead: 5.36:1 against #0f6cbd. (0,3,0) beats .is-selected. */
.lang-menu__row.is-selected:focus-visible { outline-color: var(--colorNeutralForegroundInverted); }
.lang-menu__badge { flex: 0 0 auto; inline-size: 4px; block-size: 28px; border-radius: 99px; background: linear-gradient(180deg, var(--colorNeutralStroke2), var(--colorNeutralStroke1)); }
.lang-menu__row.is-selected .lang-menu__badge { background: linear-gradient(180deg, var(--colorNeutralForegroundInverted), rgba(255, 255, 255, 0.5)); }
.lang-menu__row-text { flex: 1 1 auto; min-inline-size: 0; display: flex; flex-direction: column; }
.lang-menu__row-native { font-size: 13px; line-height: 1.25; font-weight: var(--fontWeightSemibold); color: var(--colorNeutralForeground1); white-space: nowrap; overflow: hidden; text-overflow: ellipsis; unicode-bidi: isolate; }
.lang-menu__row-en { font-size: 10.5px; line-height: 1.25; color: var(--colorNeutralForeground3); white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }
.lang-menu__row.is-selected .lang-menu__row-native { color: var(--colorNeutralForegroundInverted); }
.lang-menu__row.is-selected .lang-menu__row-en { color: rgba(255, 255, 255, 0.78); }
.lang-menu__row.is-selected .lang-menu__row-check { filter: brightness(0) invert(1); }

/* — Feature F: document-only language rows + info dialogue (2026-07-24). — */
.lang-menu__row--doc .lang-menu__badge { background: var(--colorNeutralStroke2); opacity: 0.6; }
.lang-menu__doc-chip {
    flex: 0 0 auto; margin-inline-start: auto; padding-block: 1px; padding-inline: var(--spacingHorizontalSNudge);
    font-size: 9.5px; font-weight: var(--fontWeightSemibold); letter-spacing: 0.02em; text-transform: uppercase;
    color: var(--colorNeutralForeground3); background: var(--colorNeutralBackground4);
    border: 1px solid var(--colorNeutralStroke2); border-radius: 99px; white-space: nowrap;
}
.lang-menu__doc-modal { position: fixed; inset: 0; z-index: 1000; display: flex; align-items: center; justify-content: center; padding: var(--spacingHorizontalL); }
.lang-menu__doc-backdrop { position: absolute; inset: 0; background: rgba(0, 0, 0, 0.45); }
.lang-menu__doc-dialog {
    position: relative; z-index: 1; inline-size: min(360px, 100%); max-block-size: 80vh; overflow-y: auto;
    background: var(--colorNeutralBackground1); border: 1px solid var(--colorNeutralStroke2);
    border-radius: var(--borderRadiusXLarge); padding: var(--spacingVerticalL) var(--spacingHorizontalL);
    box-shadow: var(--shadow28); text-align: start;
}
.lang-menu__doc-native { font-size: 17px; font-weight: var(--fontWeightSemibold); color: var(--colorNeutralForeground1); margin-block: 0; unicode-bidi: isolate; }
.lang-menu__doc-en { font-size: 12px; color: var(--colorNeutralForeground3); margin-block: 2px 0; }
.lang-menu__doc-body { font-size: 13px; line-height: 1.5; color: var(--colorNeutralForeground2); margin-block: var(--spacingVerticalM) var(--spacingVerticalL); }
.lang-menu__doc-close {
    inline-size: 100%; padding-block: var(--spacingVerticalS); border: none; cursor: pointer;
    font-family: inherit; font-size: 13px; font-weight: var(--fontWeightSemibold);
    color: var(--colorNeutralForegroundInverted); background: var(--colorBrandBackground);
    border-radius: var(--borderRadiusLarge); transition: background var(--durationFast) var(--curveEasyEase);
}
.lang-menu__doc-close:hover, .lang-menu__doc-close:focus-visible { background: var(--colorBrandBackgroundHover); outline: none; }

/* — certificate field link + generation states (driven by foil_cert.js;
   progressive enhancement — no-JS/reduced-motion sees the settled text) — */

/* — reduced motion: freeze the iridescent animation + show settled text — */
@media (prefers-reduced-motion: reduce) {
    .foil-holo { animation: none; }
    .hero-doc .foil-gen-pending { filter: none; opacity: 1; }
}

/* ============================================================================
   36. Hero specimen-document diptych  —  the landing hero's live preview
   --------------------------------------------------------------------------
   Rebuilt 2026-08-06 straight from the design, node by node, after the
   previous hand-drawn approximation was rejected. The source nodes are:

     233:2602  the card the whole preview sits on
     233:2603  the diptych inside it
     233:2604  the ORIGINAL pane
     233:2814  the TRANSLATION panel (absolute, offset, blue drop shadow)
     233:316   the certificate sheet   (320px, paper gradient + guilloche)
     233:2608  the apostille sheet     (240px, flat, its own 1px border)

   SIZES ARE LITERAL. There is no transform and no scaling: a rule that says
   14px renders at 14px (owner, 2026-08-06 - "if the design is 14px you
   write font-size: 14px"). Nothing here derives a size from a scale factor,
   and ops.tests_hero_doc_stylesheet_invariants fails the build if it does.

   THE TWO DOCUMENTS DO NOT SHARE A SCALE, and that is not an oversight -
   the certificate sets its table in 8px with a 10px rank line, the
   apostille sets 9px numerals and labels against 8px values. Each follows
   its own node. A single shared type scale was one of the things that made
   the earlier version wrong.

   HOW THE DIPTYCH IS LAID OUT. The source pane is in normal flow at the
   document's natural width. The translation panel is taken OUT of flow and
   placed one sheet-width + 32px along, 16px higher, so it overhangs the
   card's outer edge - which is why only its outer corners are rounded and
   why the blue shadow falls back across the card. .hero-doc reserves the
   width the out-of-flow panel would not otherwise contribute. Below 900px
   the panel returns to flow and the two stack, and the sheets keep their
   literal sizes throughout.
   ========================================================================= */

/* ── mount + card ──────────────────────────────────────────────────────────
   The card the whole preview sits on (Figma 233:2602 "cert content"). It is
   also the HTMX swap target's parent, so it must NOT be replaced on a swap —
   only .hero-doc inside it is. */
.hero-doc-mount {
    /* Natural width of ONE sheet for the active document. The template
       publishes the real value per document on .hero-doc; this is only the
       fallback for the moment before the first render. */
    --hd-sheet-w: 320px;
    /* Plain container. The card chrome lives on .foil-hero__panel, which is
       the design's single card; a second one here was the "two stacked
       cards" mismatch. */
    inline-size: 100%;
}

/* The diptych. The source pane is in flow; the translation panel is taken
   out of flow and offset, exactly as the design does it — which is why the
   panel can overhang the card's right edge and keep square corners on the
   side that stays tucked behind it. min-inline-size reserves the width the
   out-of-flow panel would otherwise not contribute (the design reserves it
   with a duplicated, fully transparent copy of the source pane; a min-width
   does the same job without the second copy in the accessibility tree). */
.hero-doc {
    position: relative;
    /* ONE cell, both panes in it. A grid row is as tall as its tallest item,
       so the section always contains the translation panel however long the
       translation runs — while align-self: start on each pane means neither
       stretches to match the other. That is the combination every earlier
       round got two-thirds of. */
    display: grid;
    grid-template-columns: 1fr;
    align-items: start;
    /* source (sheet + 50 of chrome) - 2 of overlap + panel (sheet + 50).
       A floor, not a fixed width: the panel is meant to overhang the column
       by about 16px, so the row is deliberately allowed to be wider than the
       box it sits in. The container queries below use that same allowance. */
    min-inline-size: calc(2 * var(--hd-sheet-w) + 98px);
}

/* ── panes ────────────────────────────────────────────────────────────── */
.hero-doc__pane { display: flex; flex-direction: column; }

/* This pane IS the card now: same surface, border, radius and shadow the
   wrapper used to carry, and the same 16px/24px inset. It hugs the source
   document and nothing stretches it. */
/* Plain content inside the card again — no surface of its own. It takes the
   full width so the card's own edges are the only ones anyone sees, and
   align-self: start keeps the card's height tied to the ORIGINAL document
   rather than to the taller translation panel. */
.hero-doc__pane--source {
    gap: 12px;
    grid-area: 1 / 1;
    align-self: start;
    justify-self: start;
    /* Shrink-wrap the paper to the sheet instead of stretching it across the
       whole card. With stretch, the sheet's position inside the paper was
       decided by the paper's own dir — which is the SOURCE document's
       language, always ltr — so under an RTL interface the sheet stayed
       physically left while the panel's justify-self:end also moved left,
       and the original document was completely covered. flex-start resolves
       in THIS pane's direction, which follows the interface. */
    align-items: flex-start;
    inline-size: 100%;
    padding: 16px 24px;
    background: var(--foil-hd-paper-flat);
    border: 1px solid var(--foil-hd-card-border);
    border-radius: 16px;
    box-shadow: 0 4px 8px 0 var(--foil-hd-sheet-shadow);
    box-sizing: border-box;
}

/* Figma 233:2814 "Translation". Every value here is the design's own:
   left = sheet width + 32px, top = -16px, 24px padding, 16px gap, a
   400px floor, right-hand corners only, and the blue drop shadow the
   whole treatment hangs on. */
.hero-doc__pane--target {
    /* Same cell as the source, pinned to the far edge and to the TOP — the
       owner wants the two panels aligned there. The panel lies over the
       card's empty right-hand side, which is what makes the card read as one
       surface running behind it rather than as a second box beside it. */
    position: relative;
    grid-area: 1 / 1;
    align-self: start;
    justify-self: end;
    inline-size: calc(var(--hd-sheet-w) + 50px);
    min-block-size: 400px;
    gap: 16px;
    align-items: center;
    padding: 24px;
    background-color: var(--foil-hd-panel-bg);
    background-image: linear-gradient(
        90deg,
        var(--foil-holo-1) 0%,
        var(--foil-holo-2) 25%,
        var(--foil-holo-3) 50%,
        var(--foil-holo-4) 75%,
        var(--foil-holo-5) 100%
    );
    background-repeat: no-repeat;
    background-origin: border-box;
    background-size: 100% 8px;
    background-position: top;
    border: 1px solid var(--foil-hd-panel-border);
    /* A background paints UNDER the border, so the bar's top row would
       otherwise composite beneath the 20%-alpha border and shift tone. */
    border-block-start-color: transparent;
    border-start-end-radius: 16px;
    border-end-end-radius: 16px;
    box-shadow: -2px 16px 36px 0 var(--foil-hd-panel-shadow);
    /* NO overflow: clip. It was here only to stop the iridescent bar
       squaring off the panel's rounded corner — the bar now carries its own
       matching radius instead — and it was also cutting the language menu
       off at the panel's edge. */
}
/* The panel overhangs the card on the opposite side under RTL, so the
   shadow it casts back over the card has to follow it. box-shadow has
   no logical form — this is the documented escape, not a directional
   property slipping past the RTL scanner. */
[dir="rtl"] .hero-doc__pane--target {
    box-shadow: 2px 16px 36px 0 var(--foil-hd-panel-shadow);
}

/* RESTORED 2026-08-08. This base rule was deleted during one of the whole-
   block rewrites of this section, leaving only the two modifiers below. With
   no base rule the label computed to display: block, and THREE things broke
   at once from that one loss: both badges are sized with inline-size and
   flex:0 0 auto, which do nothing to an inline span, so they painted at
   width 0 and vanished; and the picker's margin-inline-start: auto, which
   only pushes inside a flex line, stopped pushing — so the dropdown sat
   against the words instead of at the far end of the row. */
.hero-doc__pane-label {
    /* Above the overlapping panel, so both labels stay legible where the
       translation panel covers the card. Recovered from the committed
       version 2026-08-08 — the first restoration of this rule put back
       display:flex and dropped these two, which is the same mistake twice
       over on the same rule. */
    position: relative;
    z-index: 4;
    display: flex;
    align-items: center;
    gap: 4px;
    font-size: 12px;
    font-weight: 500;
    letter-spacing: 0.24px;
    /* The design's label text is capitalised at source, but a translated
       "ORIGINAL" is not guaranteed to be — so the casing is enforced here
       rather than left to 66 catalogues. Also recovered from HEAD. */
    text-transform: uppercase;
    color: var(--foil-hd-label);
}
.hero-doc__pane--target .hero-doc__pane-label {
    inline-size: 100%;
    color: var(--foil-hd-accent);
}
/* The language button sits hard against the panel's far edge; the label
   group takes the rest (Figma 233:2815, 24px between the two).
   Both selectors: the preview picker was forked onto .lang-menu--preview on
   2026-08-08 and the old .hd-picker root no longer exists on it, so a rule
   naming only .hd-picker silently stopped applying. */
.hero-doc__pane--target .hero-doc__pane-label > .hd-picker,
.hero-doc__pane--target .hero-doc__pane-label > .lang-menu--preview {
    margin-inline-start: auto;
    padding-inline-start: 20px;
}

.hero-doc__pane-marker {
    flex: 0 0 auto;
    inline-size: 4px;
    block-size: 12px;
    /* Recovered from HEAD 2026-08-08 along with the label's z-index. The two
       SIZES are load-bearing — tests_playwright_mvp_hero asserts painted
       widths of exactly 4.0 and 8.0 in both directions, because a lost
       declaration on this rule's parent zeroed them once already. Only the
       corner moves: Figma 267:5461 draws these markers square. */
    border-radius: var(--borderRadiusNone);
    background: var(--foil-hd-label);
}
.hero-doc__pane-marker--target {
    inline-size: 8px;
    background: url("hero_docs/marker_translate.svg") center / contain no-repeat;
}

/* ── paper ────────────────────────────────────────────────────────────── */
.hero-doc__paper { position: relative; display: flex; }
.hero-doc__sheet { position: relative; display: flex; min-inline-size: 0; }

/* ── the "VERIFIED / True translation" rubber stamp ────────────────────
   The design's own exported artwork (Figma 245:710), sitting across the
   translated sheet's bottom outer corner. */
/* Anchored to the PANEL (Figma 248:2572), 12px in from its inner bottom and
   end edges, so it keeps its corner whichever document is showing.

   The rotation is a ROTATION, not a size recalculation: the owner's ban is on
   deriving a rendered size from a scale factor, and this changes no size. The
   artwork is 79x32 and renders at 79x32.

   transform has no logical form, so RTL is stated rather than inherited: the
   panel mirrors, the stamp mirrors with it, and the tilt keeps the same
   on-screen direction because a mirrored tilt reads as a different stamp. */
.hero-doc__stamp {
    position: absolute;
    inset-block-end: 12px;
    inset-inline-end: 12px;
    inline-size: 79px;
    block-size: 32px;
    transform: rotate(-3deg);
    transform-origin: center;
    z-index: 3;
    pointer-events: none;
    /* Hidden until the row-by-row translation finishes; foil_cert.js then
       stamps data-stamped on the root and the keyframes below run once. */
    opacity: 0;
}

/* The stamp lands like a rubber stamp: it fades in oversized and closes down
   onto its place. The scale here is NOT the banned kind — that ban is about
   deriving a rendered SIZE from a scale factor, and this is a time-based
   landing that ends at exactly 1, leaving the artwork at its literal 79x32.
   The rotation is repeated in every keyframe because a transform declaration
   REPLACES the property rather than adding to it, so omitting it would drop
   the -3deg tilt for the duration of the animation. */
@keyframes hd-stamp-on {
    0%   { opacity: 0; transform: rotate(-3deg) scale(2.4); }
    55%  { opacity: 1; transform: rotate(-3deg) scale(0.94); }
    75%  { transform: rotate(-3deg) scale(1.04); }
    100% { opacity: 1; transform: rotate(-3deg) scale(1); }
}
.hero-doc[data-stamped] .hero-doc__stamp {
    animation: hd-stamp-on 420ms var(--motion-easing-standard, cubic-bezier(0.2, 0, 0, 1)) forwards;
}

@media (prefers-reduced-motion: reduce) {
    /* No landing, but the stamp must still be THERE — the default state is
       invisible, so this has to put it back rather than merely shorten it. */
    .hero-doc__stamp { opacity: 1; }
    .hero-doc[data-stamped] .hero-doc__stamp { animation: none; }
}

/* ── the sheet: what both documents share ──────────────────────────────
   SIZES ARE LITERAL. Every px below is the number in the design; there is
   no transform, no scale factor and no derived "effective" size (owner,
   2026-08-06 — "if the design is 14px you write font-size: 14px"). */
.hd-doc {
    position: relative;
    inline-size: var(--hd-sheet-w);
    max-inline-size: 100%;
    /* NO font-family here. The per-language splice lives on the paper
       ([lang] at the top of this file, which prepends --fontFamilyCJK), and
       re-declaring the base family on this descendant has the same
       specificity but comes later, so it silently discards the splice.
       Verified 2026-08-06 in a real browser: with a family here, a ja page
       resolved "Noto Sans" and Japanese Han rendered in SIMPLIFIED forms,
       because --fontFamilyBase lists Noto Sans SC before TC and JP. */
    font-size: 9px;
    font-weight: 400;
    line-height: normal;
    color: var(--foil-hd-ink);
    overflow-wrap: break-word;
}

/* Rows, cells and the numeral gutter — the vocabulary both documents use. */
.hd-line {
    display: flex;
    align-items: flex-start;
    gap: 4px;
    inline-size: 100%;
}
.hd-num {
    flex: 0 0 auto;
    inline-size: 16px;
    /* The numeral gutter stays at 8px while the rest of the certificate
       moved to 9px — that separation is in the design (248:865 vs 248:867). */
    font-size: 8px;
    font-weight: 600;
    text-align: center;
}
.hd-num--blank { block-size: 12px; }
.hd-lbl { font-weight: 600; }
.hd-val { font-weight: 400; }
.hd-val--stack { display: flex; flex-direction: column; }
.hd-aux { flex: 1 1 0; min-inline-size: 0; text-align: end; }
.hd-aux--lbl { font-weight: 600; }

/* Horizontal rules. The design draws these as 1px (or 0.5px) ink lines
   inside a 2px or 4px spacer; a bordered box is the same picture. */
/* Rule spacers are literal: the certificate draws its rules inside 2px
   boxes (Figma 233:369 h-2) or hairline 0-height ones (233:421 h-0), while
   the apostille uses 4px (233:2612 h-4). Getting these wrong is worth ~16px
   of document height, which is how the sheet ended up too tall. */
.hd-rule {
    display: block;
    inline-size: 100%;
    block-size: 2px;
    border-block-start: 1px solid var(--foil-hd-ink);
    box-sizing: border-box;
}
.hd-rule--thin { block-size: 1px; }
.hd-rule--hair { block-size: 0.5px; border-block-start-width: 0.5px; }
.hd-doc--apostille .hd-rule { block-size: 4px; }
.hd-doc--apostille .hd-rule--tight { block-size: 2px; }

/* Vertical rule between the two halves of a rank-joined row: a 1px line
   centred in a 4px column. */
.hd-vrule {
    flex: 0 0 auto;
    align-self: stretch;
    inline-size: 1px;
    margin-inline: 2px 1px;
    background: var(--foil-hd-ink);
}
.hd-vrule--hair { opacity: 0.55; }

/* A rank-joined row: two equal halves separated by the vertical rule. */
.hd-split { display: flex; align-items: flex-start; inline-size: 100%; }
.hd-split > .hd-cell,
.hd-split > .hd-row { flex: 1 1 0; min-inline-size: 0; }

/* A section band ("CHILD", "FATHER", "Certified"): centred, full width. */
/* A section band carries no block padding of its own on the certificate
   (Figma 233:384); the apostille's "Certified" band does (233:2653). */
.hd-band {
    display: flex;
    align-items: center;
    justify-content: center;
    inline-size: 100%;
    font-size: 10px;
    font-weight: 600;
    text-align: center;
}
.hd-doc--apostille .hd-band { padding-block: 2px; font-weight: 700; }

/* Seals and signatures. These are the design's exported artwork, not type:
   a seal or a signature is not translated, so it has no twin in the other
   pane and no generation animation. */
.hd-mark {
    display: flex;
    align-items: center;
    justify-content: center;
    flex: 1 1 0;
    min-inline-size: 0;
    padding: 4px;
}
.hd-mark__img { display: block; inline-size: 100%; block-size: auto; }
/* On the certificate a signature is a fixed-size stamp of artwork, not a
   cell that grows: the design sets 74px for the informant and 33px for the
   registrar (Figma 233:527 and 233:556). Letting .hd-mark's flex:1 apply
   here blew the registrar's signature up to the full half-cell. */
.hd-mark--informant,
.hd-mark--registrar { flex: 0 0 auto; padding: 2px 0 0; }
.hd-mark--informant { inline-size: 75px; }
/* Pinned, not derived. .hd-mark is flex:1 and .hd-mark__img is 100% wide, so
   without this the seal grew 44% when the sheet went 240px -> 280px. */
.hd-mark--seal { flex: 0 0 auto; padding: 4px; inline-size: 54px; }
.hd-mark--seal .hd-mark__img { inline-size: 46px; block-size: 46px; }
.hd-mark--registrar { inline-size: 33px; }

/* ══ certificate ═══════════════════════════════════════════════════════
   Figma 233:316. Three-stop paper gradient, a guilloche that bleeds well
   past the sheet, 12/16 padding, an 8px stack gap. */
.hd-doc--certificate {
    display: flex;
    flex-direction: column;
    gap: 8px;
    padding: 12px 16px;
    background: linear-gradient(
        270deg,
        var(--foil-hd-paper-1),
        var(--foil-hd-paper-2) 50%,
        var(--foil-hd-paper-3)
    );
    box-shadow: 0 4px 8px 0 var(--foil-hd-sheet-shadow);
    overflow: clip;
}
/* The pattern's negative insets are the design's (Figma 233:317): it is far
   wider than the sheet and is clipped by it, which is what makes the weave
   read as continuous rather than as a framed tile. */
.hd-doc__pattern {
    position: absolute;
    inset-block-start: -16.5%;
    inset-inline-start: -57.19%;
    /* An <img> is a replaced element: four insets alone do NOT stretch it,
       so the size is stated outright. 100% + the two opposing insets is
       exactly the box the design's insets describe. */
    inline-size: 214.23%;   /* 100 + 57.19 + 57.04 */
    block-size: 139.64%;    /* 100 + 16.5  + 23.14 */
    max-inline-size: none;
    z-index: 0;
    pointer-events: none;
}
.hd-doc--certificate > *:not(.hd-doc__pattern) { position: relative; z-index: 1; }

/* Head: two text groups flanking the crest, the title split across it. */
.hd-head {
    display: flex;
    align-items: flex-start;
    gap: 10px;
    inline-size: 100%;
}
.hd-head__group {
    display: flex;
    flex: 1 1 0;
    min-inline-size: 0;
    flex-direction: column;
    justify-content: center;
}
/* The two head groups hug the crest from opposite sides. align-items places
   the BOX; text-align places the LINES inside it — and both are needed,
   because a translated subtitle wraps to two or three lines and without
   text-align those lines run away from the crest instead of towards it
   (owner, 2026-08-07: "Deaths Registration Act 1953" must align left to the
   seal and "Pursuant to the Births and" must align right to it). */
.hd-head__group--end   { align-items: flex-end;   text-align: end; }
.hd-head__group--start { align-items: flex-start; text-align: start; }
.hd-head__title { font-size: 11px; font-weight: 700; margin-block-end: -2px; }
.hd-head__sub   { font-size: 8px; font-weight: 400; }
.hd-head__crest { flex: 0 0 auto; inline-size: 24px; block-size: 24px; }

.hd-table {
    display: flex;
    flex-direction: column;
    align-items: center;
    inline-size: 100%;
    border: 1px solid var(--foil-hd-ink);
    box-sizing: border-box;
}

/* A stacked cell: label line, then value line, both past the gutter. */
.hd-cell {
    display: flex;
    flex-direction: column;
    inline-size: 100%;
    padding: 2px 4px;
    box-sizing: border-box;
}
.hd-cell--half { inline-size: auto; }
/* Row 1 is two separate px-only cells in the design (233:386 / 233:391) —
   no block padding between the label line and its values. */
.hd-cell--tight { padding-block: 0; }

/* Top rank row: "BIRTH" centred beside the entry number. */
.hd-rank {
    display: flex;
    flex: 1 1 0;
    min-inline-size: 0;
    align-items: center;
    justify-content: center;
    padding: 2px 4px;
    font-size: 10px;
    font-weight: 700;
}
.hd-entry {
    display: flex;
    flex: 0 0 auto;
    align-items: center;
    justify-content: center;
    gap: 4px;
    padding: 2px 4px;
}
.hd-lbl--entry { inline-size: 28px; }

/* District row: a fixed 120px pair of label/value lines, then the
   administrative area hard against the far edge. */
.hd-cell--district { flex-direction: row; align-items: flex-start; gap: 4px; }
.hd-district { display: flex; flex: 0 0 auto; flex-direction: column; inline-size: 120px; }
.hd-district .hd-line { gap: 8px; align-items: center; }
.hd-district .hd-val { flex: 1 1 0; min-inline-size: 0; }
.hd-admin {
    display: flex;
    flex: 1 1 0;
    min-inline-size: 0;
    flex-direction: column;
    align-items: flex-end;
    justify-content: center;
}
.hd-admin .hd-lbl { text-align: center; }

/* Footer: the 44px seal beside the two legal paragraphs at 6px. */
.hd-foot {
    display: flex;
    align-items: center;
    justify-content: center;
    gap: 10px;
    inline-size: 100%;
}
.hd-foot__seal { flex: 0 0 auto; inline-size: 44px; block-size: 44px; }
.hd-foot__text {
    display: flex;
    flex: 1 1 0;
    min-inline-size: 0;
    flex-direction: column;
    align-items: center;
    justify-content: center;
    gap: 2px;
    font-size: 8px;
    font-weight: 400;
}
.hd-foot__offence,
.hd-foot__specimen { inline-size: 100%; opacity: 0.8; }
.hd-foot__specimen { text-align: justify; }

/* ══ apostille ═════════════════════════════════════════════════════════
   Figma 233:2608. A flat sheet with its own 1px border — no gradient and
   no guilloche; that treatment belongs to the certificate alone. Its type
   scale also differs: 9px numerals and labels against 8px values. */
.hd-doc--apostille {
    display: flex;
    flex-direction: column;
    align-items: center;
    padding-block-start: 2px;
    background: var(--foil-hd-paper-flat);
    border: 1px solid var(--foil-hd-ink);
    box-shadow: 0 4px 4px 0 var(--foil-hd-sheet-shadow);
    box-sizing: border-box;
}
.hd-doc--apostille .hd-num { font-weight: 700; font-size: 9px; }
.hd-doc--apostille .hd-lbl { font-size: 9px; }

.hd-title {
    display: flex;
    flex-direction: column;
    align-items: center;
    justify-content: center;
    inline-size: 100%;
    padding-block: 2px;
    text-align: center;
}
.hd-title__main { font-size: 11px; font-weight: 700; }
.hd-title__sub  { font-size: 9px; font-weight: 400; }

/* An apostille row is a single line: gutter, fixed-width label, then the
   value taking whatever is left. */
.hd-row {
    display: flex;
    align-items: flex-start;
    gap: 4px;
    inline-size: 100%;
    padding: 2px 4px;
    box-sizing: border-box;
}
.hd-row > .hd-lbl { flex: 0 0 auto; inline-size: 64px; }
.hd-row > .hd-lbl--wide   { inline-size: 96px; }
.hd-row > .hd-lbl--narrow { inline-size: 32px; }
.hd-row > .hd-val { flex: 1 1 0; min-inline-size: 0; }
.hd-row--half { inline-size: auto; }

.hd-signed {
    display: flex;
    flex: 0 0 auto;
    flex-direction: column;
    align-items: flex-start;
    gap: 4px;
    inline-size: 54px;
}
.hd-signed__name { font-size: 8px; font-weight: 400; }
.hd-signed .hd-mark { inline-size: 100%; }

/* ── preview language picker ────────────────────────────────────────────
   A FORK of the site language switcher (owner, 2026-08-08), reusing its
   .lang-menu__* styling wholesale. Only the placement differs, so only the
   placement is overridden here. */
.lang-menu--preview { position: relative; margin-inline-start: auto; }
.lang-menu__trigger--preview { font-size: 12px; }
.lang-menu__pop--preview {
    /* TRIGGER-ANCHORED, like every other menu on the site. Neither viewport-
       pinned nor in the top layer any more, and both of those were the same
       mistake made twice.

       THE TOP LAYER IS GONE. `popover="manual"` did fix the occlusion — 0
       stolen pixels — but the top layer does not participate in layout, so the
       panel had nothing to be positioned against and its box had to be stated
       in viewport coordinates. The result is the owner's screenshot: measured
       on that build, `inset-block-start: 76px` held while the trigger's bottom
       edge ranged over 253-737px, i.e. gapBelow between -177 and -661 at all
       ten viewport x locale cells, and at eight of them the panel shared ZERO
       pixel columns with its own button (inline overlap down to -256). The
       occlusion it was reached for is now fixed where it actually lived, one
       rank on `.foil-landing > #hero-upload`.

       CSS ANCHOR POSITIONING WAS MEASURED AND REJECTED. anchor-name /
       position-anchor / position-area, still in the top layer, scored gapBelow
       10.0 and full trigger containment — and so does this rule, on the same 48
       cells, to 0.00px. It buys nothing here while costing a second code path
       for engines without it, so the platform's oldest positioning primitive
       wins on evidence.

       `inline-size` and not `max-inline-size`: with `inset-inline-start: auto`
       this box is shrink-to-fit, and `repeat(auto-fill, ...)` then resolves to a
       single track against an indefinite available size — measured 246px
       instead of 490px. This expression is the panel's ONLY width statement.

       The 10px is the designed air under the trigger and is restated in the
       placer (langMenuPlace, _language_menu.html) so the two cannot drift.
       Nothing here caps the height: the placer writes an explicit
       max-block-size onto the list, because the space below the trigger is not
       a viewport constant and CSS cannot see it. If the placer never runs, the
       list's own `min(calc(100dvh - 210px), 560px)` is the fallback. */
    position: absolute;
    inset-block-start: calc(100% + 10px);
    inset-block-end: auto;
    inset-inline-end: 0;
    inset-inline-start: auto;
    /* The SAME column arithmetic as `.lang-menu__pop`, at this panel's own
       ceiling of 2 columns. A flat `min(490px, ...)` was the void the owner
       reported, one panel over: 490 is not a whole number of columns (2 need
       468), so 22px painted as empty track at EVERY viewport from 600 to 1600,
       and 130px at 400 / 90px at 360 where only one column fits. Measured
       after: dead space 0.00 at 1600/1440/1200/1000/936/935/800/714/713/600/
       492/400/360, en-gb and ar alike, with the row still 220.0.
       Budget is `100vw - 24px` because the placer clamps this panel into
       [12, clientWidth - 12]; keep the two numbers in step. */
    /* Same hug as .lang-menu__pop, at this panel's design ceiling of two
       columns (--lm-n is re-capped just below). */
    inline-size: max-content;
    max-inline-size: calc(100vw - 24px);
    block-size: auto;
    margin: 0;
}
/* This rule must stay AFTER the shared `@media (max-width: 880px)
   .lang-menu__pop` block: a media query adds no specificity, so the two compute
   equal and source order alone decides which `inline-size` wins. */

/* ── document switch: a physical exchange, not a cross-fade ──────────────
   Two sheets on a desk. The outgoing one is taken away in the direction the
   switcher runs; the incoming one arrives from the opposite side and settles.
   They hand over — they are never in the same place at once.

   There is no View Transition here any more. The user-agent snapshot
   cross-fade put both documents at 50% opacity in ONE position, and Chromium
   composites those halves with plus-lighter, which ADDS ink to ink — white
   paper measured about 157/255 grey through the swap. It also stretched both
   sheets into a morphing group box, and none of it could be read by a browser
   test. Two real elements composite normally, keep their own size, and are
   measurable.

   Direction comes from the switcher's tab order (hero_doc_picker.js writes
   --hd-dir), never from either document's width — so a third and a fourth
   document need no new CSS. --hd-dir: 0 means a language change: same
   document, so no lateral travel. */
:root[dir="rtl"] .foil-hero__panel-wrap { --hd-axis: -1; }

/* Holds the slot at its outgoing height across the swap window, so the row
   does not step to the incoming document's height before HERO_DOC_HEIGHT can
   ease it. It is NOT a container-query mitigation — that reading was measured
   and refuted in round 14: the hero-preview query's input (.foil-hero__panel-wrap
   width) is constant at 744px through the whole swap at vw=1440 and its verdict
   never changes value in any frame, and a three-read synchronous probe at
   htmx:afterSwap returns the same height every time, i.e. layout converges in
   ONE pass. What it actually covers is the ~2-frame gap between htmx inserting
   the new frame and HERO_DOC_HEIGHT pinning the row for the ramp: measured
   with the freeze disabled and the round-14 fix in place, the row still
   flashes 497 -> 712 -> 497px at vw=1440 before the ramp starts. So it stays.
   `visible clip` is the one axis pair CSS does not promote to auto,
   and the inline axis must stay unclipped because the translation panel
   deliberately overhangs the card. */
.hd-stage--frozen { overflow-x: visible; overflow-y: clip; }

.hd-ghost {
    position: absolute;
    margin: 0;
    pointer-events: none;
    /* Above centre, so shrinking reads as receding rather than as squashing. */
    transform-origin: 50% 28%;
    will-change: transform, opacity;
    animation:
        hd-sheet-out-move calc(var(--hd-beat) * 0.72) var(--curveEasyEaseMax) both,
        hd-sheet-out-fade calc(var(--hd-beat) * 0.72) linear both;
}

.hd-stage--in > .hero-doc {
    animation:
        hd-sheet-in-move calc(var(--hd-beat) * 1.4) calc(var(--hd-beat) * 0.72) var(--curveDecelerate) both,
        hd-sheet-in-fade calc(var(--hd-beat) * 0.8) calc(var(--hd-beat) * 0.72) linear both;
}

/* The exit travels 55% of a step, the entrance a full one. That asymmetry is
   the weight: the departing sheet only has to clear the frame, the arriving
   one has to be SEEN to travel and then come to rest. The travel is composed
   inline rather than through an intermediate custom property — a custom
   property composed of other custom properties is inert in this project. */
@keyframes hd-sheet-out-move {
    from { transform: translate3d(0, 0, 0) scale(1); }
    to   { transform: translate3d(
               calc(var(--hd-dir) * var(--hd-axis) * var(--hd-travel) * -0.55), 5px, 0)
               scale(0.955); }
}
/* Reaches 0 at the same instant the incoming sheet leaves 0 — a handover,
   with neither an overlap nor a blank frame. */
@keyframes hd-sheet-out-fade {
    0%   { opacity: 1; }
    38%  { opacity: 1; }
    100% { opacity: 0; }
}
@keyframes hd-sheet-in-move {
    from { transform: translate3d(
               calc(var(--hd-dir) * var(--hd-axis) * var(--hd-travel)), 10px, 0)
               scale(0.985); }
    to   { transform: translate3d(0, 0, 0) scale(1); }
}
@keyframes hd-sheet-in-fade { from { opacity: 0; } to { opacity: 1; } }

@media (prefers-reduced-motion: reduce) {
    /* Belt to hero_doc_picker.js's braces — it creates no clone at all. */
    .hd-ghost { display: none; }
    .hd-stage--in > .hero-doc { animation: none; }
}

/* ── interactive state ──────────────────────────────────────────────────
   The per-row rolling highlight and the per-character generation effect,
   both re-armed by foil_cert.js on every swap. */
.hero-doc [data-seg] {
    border-radius: 2px;
    transition: background-color 220ms ease;
}
.hero-doc [data-seg].is-on {
    background-color: color-mix(in srgb, var(--foil-hd-accent) 14%, transparent);
}
.hero-doc .foil-gen-pending { filter: blur(4px); opacity: 0.32; user-select: none; }
.hero-doc .foil-gen-active { color: var(--foil-hd-accent); }

/* ── responsive ─────────────────────────────────────────────────────────
   Below the width two sheets need, the panel returns to normal flow and
   the two stack. Everything inside the sheets keeps its literal sizes. */
/* Stacked, and the threshold is per document because they are not the same
   width: the certificate needs 2x320 + 120 = 760, the apostille 2x280 + 120
   = 680. A container query condition cannot read a custom property, so the
   two documents get one rule each, selected by the data-doc the template
   already publishes. */
/* ONE rule for every document, now and in future. The owner guarantees that
   .foil-hero__panel never exceeds 744px for any demo document they design.
   The threshold is not that ceiling, though — it is the widest REGISTERED
   document's actual row width: 2 x 320 + 98 = 738 for the certificate. Using
   the ceiling left zero clearance, so any rounding tipped the layout into
   stacking. The apostille needs only 658 and therefore stacks a little
   earlier than it strictly must; that is the accepted cost of one universal
   rule instead of one per document. Range syntax rather than max-width: container widths are
   fractional (729.125px, 744.00px measured) and max-width: 743px is a
   different test. */
@container hero-preview (width < 738px) {
    /* Stacked, the two panes get a ROW EACH instead of sharing one cell.
       flex-direction does nothing here — .hero-doc is a grid — so the panes
       stayed overlaid on top of each other, with the translation panel
       covering the original's rows and making them unhoverable. */
    .hero-doc {
        grid-template-rows: auto auto;
        gap: 24px;
        inline-size: 100%;
        min-inline-size: 0;
    }
    .hero-doc__pane--source {
        grid-area: 1 / 1;
        inline-size: 100%;
        justify-self: stretch;
    }
    .hero-doc__pane--target {
        grid-area: 2 / 1;
        position: relative;
        justify-self: stretch;
        align-self: start;
        inline-size: 100%;
        min-block-size: 0;
        border-radius: 16px;
    }
    .hero-doc__paper,
    .hero-doc__sheet,
    .hd-doc { inline-size: 100%; }
}

@media (prefers-reduced-motion: reduce) {
    .hero-doc [data-seg] { transition: none; }
    .hero-doc .foil-gen-pending { filter: none; opacity: 1; }
}

/* ── hero preview controls: document switcher + language picker ────────── */
/* The switcher sits above the diptych, right-aligned, and is never swapped.
   The picker sits in the target pane's TRANSLATE TO row and IS swapped, so
   it always shows the language that actually rendered. */

.hd-switch {
    display: flex;
    justify-content: flex-end;
    /* Same track pair as .foil-intake — see there. */
    /* Figma 233:1309 "seg selector". */
    gap: 4px;
    padding: 2px;
    border: 1px solid var(--foil-lb-track-edge);
    border-radius: 8px;
    background: var(--foil-lb-track);
    inline-size: fit-content;
    margin-inline-start: auto;
}
.hd-switch__btn {
    display: flex;
    flex-direction: column;
    /* Figma 233:1310 / 233:1313. */
    gap: 1px;
    padding: 6px 10px;
    border: 1px solid transparent;
    border-radius: 6px;
    background: transparent;
    text-align: start;
    cursor: pointer;
    transition: background var(--durationFast) var(--curveEasyEase),
                border-color var(--durationFast) var(--curveEasyEase),
                opacity var(--durationFast) var(--curveEasyEase);
}
/* --foil-lb-track-hover, NOT --colorNeutralBackgroundHover. That token is
   --grey94 = #f0f0f0, which is EXACTLY --foil-lb-track, this control's own
   bed: the hover painted the track's colour and every chip had a measured
   delta of ZERO (pixel-sampled 240,240,240 at rest and on hover, 2026-08-10).
   The token is not wrong — 19 other rules use it correctly on the #ffffff
   page surface — it was being used on the wrong bed. See .hd-switch__btn
   .is-active:hover below, which is a SECOND, independent dead-hover. */
.hd-switch__btn:hover { background: var(--foil-lb-track-hover); }
/* NO SHADOW. Figma 267:5461 sampled straight down the active chip's outer
   edge reads white to y=147, one border row at rgb(238,238,238), then the
   rgb(240,240,240) track — flat. --shadow2 would have darkened those rows by
   12-14%, and does not appear. The border alone lifts the chip. */
.hd-switch__btn.is-active {
    background: var(--colorNeutralBackground1);
    border-color: var(--colorNeutralStroke2);
}
.hd-switch__btn:not(.is-active) { opacity: 0.6; }
/* The 0.6 dimmer above would composite the hover's #e0e0e0 against the
   #f0f0f0 track and paint ~#e6e6e6 — a declared delta of 16 arriving as 9.6,
   while getComputedStyle still reports the full 16. That is the §16.2 trap in
   a new costume, so the hover lifts the dimmer instead of fighting it: the
   chip's colour AND its label ink both resolve in one gesture. (0,3,0) beats
   the (0,2,0) dimmer regardless of source order. Same vocabulary as
   .hd-switch__btn.htmx-request below. */
.hd-switch__btn:not(.is-active):hover { opacity: 1; }
/* The ACTIVE chip's hover is dead for a SECOND, independent reason:
   .hd-switch__btn.is-active (0,2,0) is declared AFTER .hd-switch__btn:hover
   (0,2,0) and wins on source order, so the hover background never applied
   there at all — measured #ffffff at rest and #ffffff on hover. Fixing only
   the token would have repaired the inactive chip and left this one exactly
   as invisible. (0,3,0) applies regardless of order. Two channels move:
   background #ffffff -> #f5f5f5 and border #e0e0e0 -> #d1d1d1. The border is
   KEPT so the chip never reads as deselected, and there is no transform —
   ops.tests_hero_doc_stylesheet_invariants rejects one on a resting state. */
.hd-switch__btn.is-active:hover {
    background: var(--foil-lb-chip-hover);
    border-color: var(--colorNeutralStroke1);
}
/* Acknowledge the press at t=0. Measured on the previous build with the
   response held 400ms: 26 consecutive frames to t=428ms with no computed
   style change of any kind — the button looked dead until the document
   arrived. Deliberately NOT the .is-active look, which would briefly claim a
   document is showing while the request may still fail. */
.hd-switch__btn:active { transform: scale(0.965); }
.hd-switch__btn.htmx-request { opacity: 1; }
.hd-switch__country {
    font-size: 10px;
    color: var(--colorNeutralForeground3);
    white-space: nowrap;
}
.hd-switch__label {
    font-size: 12px;
    font-weight: 500;
    color: var(--colorNeutralForeground2);
    white-space: nowrap;
}
/* Design ink for the active label is rgb(66,66,66), sampled at the glyph core
   — a pure alias swap onto an existing token, measured at 10.05:1 against the
   chip's white, so it does not move the a11y gate. */
.hd-switch__btn.is-active .hd-switch__label { color: var(--colorNeutralForeground2); }

/* Picker. The trigger sits at the end of the TRANSLATE TO row. */
.hd-picker { position: relative; margin-inline-start: auto; }
.hd-picker__trigger {
    display: flex;
    align-items: center;
    /* Figma 233:2819 "Button". */
    gap: 4px;
    /* Figma 267:5461 measures this control at 91x28 in /en-gb/: left border
       x=1205, right x=1295, top y=195, bottom y=222. The 28 is expressed as
       5 + 5 padding plus the 1px borders plus the label's own line box, NEVER
       as block-size — the Arabic line box is 8px taller at the same viewport
       and a hard height would clip it. */
    padding: 5px 10px;
    border: 1px solid var(--foil-hd-control-border);
    border-radius: 6px;
    background: var(--colorNeutralBackground1);
    cursor: pointer;
    max-inline-size: 160px;
    font-size: 12px;
    /* Design weight is 400. Both this and .hd-picker__current below have to
       move: this element inherits 500 from .lang-menu__trigger, which the SITE
       switcher shares and which is therefore not editable from here. */
    font-weight: var(--fontWeightRegular);
    /* `end`, not the physical keyword — ops/tests_rtl_invariants is
       enforce-mode over this file and rejects the directional pair. (It
       scans comment text too, which is why this one talks around them.)
       Overrides the UA button default of `center`; visible when a long
       language name truncates. */
    text-align: end;
    /* Same interaction vocabulary as the site language switcher
       (.lang-menu__trigger) so the two read as one component: a colour and
       surface shift on hover, a pressed state, and a visible focus ring. */
    color: var(--colorNeutralForeground2);
    transition: color var(--durationFast) var(--curveEasyEase),
                background var(--durationFast) var(--curveEasyEase),
                border-color var(--durationFast) var(--curveEasyEase),
                box-shadow var(--durationFast) var(--curveEasyEase);
}
/* Both surface channels were 5/255 and the border moved the WRONG WAY:
   measured rest {bg #ffffff, border #cccccc} -> hover {bg #fafafa, border
   #d1d1d1}, i.e. the border got LIGHTER as the pointer arrived. Only the ink
   (delta 30) was doing any work. Background now steps to #f5f5f5 (delta 10)
   and the border to #bdbdbd (delta 15 DARKER), mirroring the
   --foil-hd-control-border token this control already uses at rest. */
.hd-picker__trigger:hover {
    border-color: var(--foil-hd-control-border-hover);
    background: var(--colorNeutralBackground3);
    color: var(--colorNeutralForeground1);
}
.hd-picker__trigger:active,
.hd-picker__trigger[aria-expanded="true"] {
    border-color: var(--colorBrandStroke2);
    background: var(--colorBrandBackgroundTint);
    color: var(--colorBrandForeground2);
}
.hd-picker__trigger:focus-visible {
    outline: none;
    border-color: var(--colorBrandStroke1);
    box-shadow: 0 0 0 3px var(--colorBrandBackgroundTint);
}
/* The chevron turns over when the menu is open, exactly as the site
   switcher's does.
   The 12px box is the whole of the trigger's width miss. It carries
   `.icon icon--small` (16px) in the markup, and 16 - 12 = 4 is exactly the
   residual after every other part of the design's 91px box was accounted for:
   1 border + 10 padding + 54 label + 4 gap + CHEVRON + 10 padding + 1 border.
   Measured on the design export, the caret's ink runs x=1275..1280 with 15px
   to the right border — a 12px box centred on that ink leaves 11px, i.e. the
   10px padding plus the 1px border, to the pixel. Scoped to .hd-picker__*,
   never to .icon--small or .lang-menu__chevron: the site language switcher
   shares both of those. */
.hd-picker__chevron {
    inline-size: 12px; block-size: 12px;
    /* Figma 267:5461 draws this at 16, and the trigger sets its own spacing
       with padding rather than a flex gap — a round-14 edit shrank it to 12
       and introduced a gap, which is a regression against a design value read
       directly off the node, not a judgement call. */
    inline-size: 16px; block-size: 16px;
    transition: transform var(--durationPanelOut) var(--curvePanelFade);
}
.hd-picker__trigger[aria-expanded="true"] .hd-picker__chevron {
    transform: rotate(180deg);
    transition: transform var(--durationPanelInFade) var(--curvePanelFade);
}
/* THIS is the element that paints the label. Round 13's audit read the
   button's computed style (500 / rgb(66,66,66)) and reported that as the
   defect; the visible ink was this override at 600 / rgb(36,36,36) the whole
   time, so changing only the button would have moved nothing. */
.hd-picker__current {
    font-size: var(--fontSizeBase200);
    font-weight: var(--fontWeightRegular);
    /* Sampled off the design export at the glyph core: rgb(28,32,36). It is
       prim.hd-ink rgb(36,36,36) nor prim.grey-12 rgb(31,31,31); a real token
       was added for it rather than a literal, which the FLOOR=0 bare-hex gate
       would reject outright — and it counts comment text too, which is why
       every colour in these notes is written as an rgb triple. */
    color: var(--foil-hd-trigger-ink);
    text-transform: none;
    letter-spacing: 0;
    overflow: hidden;
    text-overflow: ellipsis;
    white-space: nowrap;
}
.hd-picker__chevron { flex: 0 0 auto; opacity: 0.55; }

.hd-picker__popover {
    position: absolute;
    z-index: 40;
    inset-block-start: calc(100% + var(--spacingVerticalXS));
    inset-inline-end: 0;
    inline-size: 260px;
    max-inline-size: calc(100vw - var(--spacingHorizontalXXL));
    padding: var(--spacingVerticalXS);
    border: var(--strokeWidthThin) solid var(--colorNeutralStroke2);
    border-radius: var(--borderRadiusLarge);
    background: var(--colorNeutralBackground1);
    box-shadow: var(--shadow16);
}
.hd-picker__search {
    inline-size: 100%;
    margin-block-end: var(--spacingVerticalXS);
    padding: var(--spacingVerticalXS) var(--spacingHorizontalS);
    border: var(--strokeWidthThin) solid var(--colorNeutralStroke2);
    border-radius: var(--borderRadiusMedium);
    font-size: var(--fontSizeBase200);
}
.hd-picker__rows { max-block-size: 280px; overflow-y: auto; overflow-x: hidden; }
.hd-picker__row {
    display: flex;
    align-items: baseline;
    gap: var(--spacingHorizontalS);
    inline-size: 100%;
    padding: var(--spacingVerticalXS) var(--spacingHorizontalS);
    border: none;
    border-radius: var(--borderRadiusMedium);
    background: transparent;
    text-align: start;
    cursor: pointer;
}
.hd-picker__row:hover { background: var(--colorNeutralBackground1Hover); }
.hd-picker__row.is-selected { background: var(--colorBrandBackground2); }
.hd-picker__row-label {
    font-size: var(--fontSizeBase200);
    color: var(--colorNeutralForeground1);
    white-space: nowrap;
}
/* The endonym is secondary now. It is isolated because an RTL endonym
   (العربية, עברית, اردو) sitting in an LTR row otherwise drags neighbouring
   punctuation to the wrong side of the row. */
.hd-picker__row-endonym {
    font-size: var(--fontSizeBase100);
    color: var(--colorNeutralForeground3);
    margin-inline-start: auto;
    white-space: nowrap;
    unicode-bidi: isolate;
}

@media (max-width: 600px) {
    .hd-switch { margin-inline: auto 0; }
    .hd-picker__popover { inline-size: min(260px, calc(100vw - 32px)); }
}

/* Screen-reader-only. Used by #hero-doc-status, whose text must be announced
   but never seen — without this the announcement renders as body copy. */
.visually-hidden {
    position: absolute;
    inline-size: 1px;
    block-size: 1px;
    margin: -1px;
    padding: 0;
    overflow: hidden;
    clip-path: inset(50%);
    white-space: nowrap;
    border: 0;
}

/* ── hero badge row + intake mode selector ─────────────────────────────── */
.foil-badge-row {
    display: flex;
    flex-wrap: wrap;
    /* Figma 267:5461, measured off the export: the two badges sit 12px apart
       (badge 1 ends x=236, badge 2 starts x=249), not 8. */
    gap: var(--spacingHorizontalM);
    /* Rhythm, not decoration — see the .foil-hero__copy note. 22px to the H1. */
    margin-block-end: 22px;
}
.foil-badge {
    display: inline-flex;
    align-items: center;
    /* 26px tall in the design: 4px padding each side over an 18px line box at
       13px. The inherited 20px line box made it 28. */
    line-height: 18px;
    gap: var(--spacingHorizontalXS);
    /* Figma 267:5461, all four measured off the export at 1x: the badge's left
       edge is x=120 and its icon's is x=128, so padding-inline is 8, not 12;
       the corner samples (fill inset 5,3,2,1,1,0 down the first six rows) fit
       an 8px radius to the pixel and rule out the shipped pill. */
    padding-inline: var(--spacingHorizontalS);
    padding-block: var(--spacingVerticalXS);
    border-radius: var(--borderRadiusXLarge);
    /* The design's fill is rgba(78,159,255,.08) — measured rgb(233,241,249)
       over the rgb(246,247,249) canvas, which is that alpha to within 1/255.
       No 8% alpha of the accent exists as a token, so it is mixed from the
       accent itself rather than minted as a new colour. §16.7 step 2: a
       browser without color-mix drops the SECOND declaration whole and keeps
       the first, i.e. the opaque tint this shipped with — a usable badge, and
       in fact the higher-contrast one. */
    background: var(--colorBrandBackgroundTint);
    background: color-mix(in srgb, var(--foil-hd-accent) 8%, transparent);
    font-size: 13px;
    font-weight: var(--fontWeightSemibold);
    /* Design ink is rgb(78,159,255) (sampled at the glyph core), not the
       Fluent brand blue. An ALIAS swap onto the accent already used by the
       headline
       and the TRANSLATE TO label — --colorBrandForeground1 has 41 other call
       sites and is not retargeted. */
    color: var(--foil-hd-accent);
    white-space: nowrap;
}
/* The badges' <img> carry width=16 height=16, but the GLOBAL `.icon` rule
   (0,1,0) sets 20x20 and wins over HTML presentational attributes, so they
   painted at 20. Won back at (0,2,0) and scoped to the badge — editing `.icon`
   itself would resize every icon on every portal. Logical properties, per the
   precedent two sections up at `.foil-drop__icon .icon`.
   16px is the design's icon box (Figma 292:465 / 292:468, both 16x16 at y=5 in
   a 26px badge). It stays under the 18px line box, so the badge is still
   4+18+4 = 26 tall — 20px was what made it 26+2. */
.foil-badge .icon { inline-size: 16px; block-size: 16px; }

.foil-intake {
    display: inline-flex;
    /* Figma 267:5461 gives both segmented tracks the same treatment: a
       #f0f0f0 bed inside a 1px #e6e6e6 hairline, and NO shadow. The doc
       switcher below already lost its shadow, so leaving one here would make
       two identical controls look different. */
    /* Figma 267:5461: 4px between the two chips, 8px on the track. */
    gap: var(--spacingHorizontalXS);
    /* 22px down from the H1, 16px on to the dropzone — the two middle steps of
       the hero rhythm, carried here so they survive the HTMX confirm swap. */
    margin-block: 22px 16px;
    padding: var(--spacingVerticalXXS);
    border: var(--strokeWidthThin) solid var(--foil-lb-track-edge);
    border-radius: var(--borderRadiusXLarge);
    background: var(--foil-lb-track);
}
.foil-intake__btn {
    display: inline-flex;
    align-items: center;
    /* Figma 267:5461: radius 6, 12px inline padding — the 4/16 pair here was
       inherited from an earlier control. */
    gap: var(--spacingHorizontalXS);
    padding: var(--spacingVerticalS) var(--spacingHorizontalM);
    border: var(--strokeWidthThin) solid transparent;
    border-radius: 6px;
    background: transparent;
    font-size: var(--fontSizeBase300);
    /* Figma 267:5461: both labels are weight 500 on rgb(66,66,66), and the inactive
       one is that same ink at opacity .6 — sampled rgb(136,136,136) on the
       rgb(240,240,240) track, which is 66 + (240-66)*0.4 to the unit. So the
       base carries the colour and the dim is a single opacity, rather than two
       unrelated greys. */
    font-weight: var(--fontWeightMedium);
    color: var(--colorNeutralForeground2);
    cursor: pointer;
}
.foil-intake__btn.is-active {
    background: var(--colorNeutralBackground1);
    border-color: var(--colorNeutralStroke2);
    color: var(--colorNeutralForeground2);
    /* No shadow — Figma 292:475, and the doc switcher's active chip already
       matches. Two identical controls must not diverge. */
    box-shadow: none;
}

/* The paste panel. Sized and spaced off the dropzone it replaces, so the two
   tabs do not make the card jump when you switch between them. */
.foil-paste { display: block; }
.foil-paste__box {
    inline-size: 100%;
    min-block-size: 164px;
    padding: var(--spacingVerticalM) var(--spacingHorizontalM);
    background: var(--colorNeutralBackground1);
    border: var(--strokeWidthThin) solid var(--colorNeutralStroke2);
    border-radius: var(--borderRadius3XLarge);
    font-family: inherit;
    font-size: var(--fontSizeBase300);
    line-height: 1.5;
    color: var(--colorNeutralForeground1);
    resize: vertical;
    box-sizing: border-box;
}
.foil-paste__box:focus-visible {
    outline: var(--strokeWidthThick) solid var(--colorStrokeFocus2);
    outline-offset: 2px;
    border-color: transparent;
}
.foil-paste__meter {
    display: flex;
    gap: var(--spacingHorizontalXS);
    margin-block: var(--spacingVerticalS) 0;
    font-size: var(--fontSizeBase200);
    color: var(--colorNeutralForeground3);
}
.foil-paste__meter.is-over { color: var(--colorPaletteRedForeground1); }
.foil-paste__send {
    margin-block-start: var(--spacingVerticalM);
    padding: var(--spacingVerticalS) var(--spacingHorizontalL);
    background: var(--colorBrandBackground);
    border: var(--strokeWidthThin) solid transparent;
    border-radius: 6px;
    color: var(--colorNeutralForegroundInverted);
    font-size: var(--fontSizeBase300);
    font-weight: var(--fontWeightMedium);
    cursor: pointer;
    transition: background var(--durationFast) var(--curveEasyEase),
                transform var(--durationFaster) var(--curveEasyEase);
}
.foil-paste__send:hover { background: var(--colorBrandBackgroundHover); }
.foil-paste__send:active { transform: scale(0.98); }
.foil-paste__send:disabled {
    background: var(--colorNeutralBackgroundDisabled);
    color: var(--colorNeutralForegroundDisabled);
    cursor: not-allowed;
    transform: none;
}

/* The intake chips' own states. The bed and the stock hover token BOTH resolve
   to #f0f0f0, so the default hover paints the track's own colour and changes
   nothing — and on the active chip the .is-active background wins on source
   order and the hover never applies at all. Two dedicated tokens, one per
   chip, are the fix; the switcher above has the identical pair for the
   identical reason. */
.foil-intake__btn:not(.is-active):hover { background: var(--foil-lb-track-hover); opacity: 1; }
.foil-intake__btn.is-active:hover { background: var(--foil-lb-chip-hover); }
.foil-intake__btn:active { transform: scale(0.97); }
.foil-intake__btn:focus-visible {
    outline: var(--strokeWidthThick) solid var(--colorStrokeFocus2);
    outline-offset: 2px;
}
/* Not a control: the feature it names is not built. Rendered as a plain
   chip so it cannot be clicked, focused, or mistaken for something broken. */
.foil-intake__btn--soon { cursor: default; opacity: 0.6; }
.chip-soon {
    margin-inline-start: var(--spacingHorizontalXS);
    padding-inline: var(--spacingHorizontalXS);
    border-radius: var(--borderRadiusCircular);
    background: var(--colorNeutralBackground4);
    font-size: var(--fontSizeBase100);
    font-weight: var(--fontWeightRegular);
    color: var(--colorNeutralForeground3);
}

/* ============================================================================
   SECTION 37 — LANDING BANDS BELOW THE HERO
   Figma 267:4497 → bands 3 (How it works), 5 (Languages), 4 (Why), 7 (Footer).
   Rebuilt 2026-08-09. Numbers here are the design's own; where a value looks
   arbitrary it is measured, not chosen.
   ========================================================================= */

.lb-band {
    background: var(--foil-lb-surface);
    /* The horizontal 120px gutter and the 1200px column both come from
       .foil-section__inner, which every other band already uses. */
    padding-block: 96px;
}

/* ── Eyebrow: gradient word over a gradient hairline ─────────────────────── */
.lb-eyebrow {
    display: flex;
    flex-direction: column;
    gap: 1px;
    margin: 0;
    inline-size: fit-content;
}
.lb-eyebrow--center { margin-inline: auto; }
.lb-eyebrow__text {
    font-size: 13px;
    font-weight: 600;
    line-height: 18px;
    letter-spacing: 0.52px;
    background-image: linear-gradient(90deg, var(--foil-lb-grad-from) 25%, var(--foil-lb-rule-4) 75%);
    -webkit-background-clip: text;
    background-clip: text;
    color: transparent;
}
.lb-eyebrow__rule {
    display: block;
    block-size: 2px;
    border-radius: 1px;
    background-image: linear-gradient(90deg, var(--foil-lb-rule-1) 0%, var(--foil-lb-rule-2) 25%,
                                      var(--foil-lb-rule-3) 50%, var(--foil-lb-rule-4) 75%, var(--foil-lb-rule-5) 100%);
}

/* ── Band 3 · How it works ───────────────────────────────────────────────── */
.lb-how__title {
    margin: 16px auto 0;
    max-inline-size: 760px;
    font-size: 32px;
    font-weight: 700;
    line-height: 1.18;
    letter-spacing: -0.64px;
    color: var(--foil-lb-ink);
    text-align: center;
}
.lb-how__grid {
    display: grid;
    grid-template-columns: repeat(3, 1fr);
    align-items: start;
    gap: 24px;
    margin-block-start: 44px;
}
.lb-card {
    position: relative;
    display: flex;
    flex-direction: column;
    gap: 14px;
    /* Figma measures its 26px padding from the frame edge and draws the 1px
       stroke inside it, so matching the design's 332px text column means 25
       here — at 26 the first card's body wraps to a third line. */
    padding: 30px 25px 28px;
    background: var(--foil-lb-surface);
    border: 1px solid var(--foil-lb-border);
    border-radius: 12px;
    box-shadow: 0 2px 8px 0 var(--foil-lb-card-shadow);
    overflow: hidden;
}
.lb-card__accent {
    position: absolute;
    inset-block-start: 0;
    inset-inline: 0;
    block-size: 4px;
    background-image: linear-gradient(90deg, var(--foil-lb-rule-1) 0%, var(--foil-lb-rule-2) 25%,
                                      var(--foil-lb-rule-3) 50%, var(--foil-lb-rule-4) 75%, var(--foil-lb-rule-5) 100%);
}
.lb-card__top {
    display: flex;
    align-items: center;
    justify-content: space-between;
    block-size: 44px;
}
.lb-card__icon {
    display: flex;
    align-items: center;
    justify-content: center;
    flex: 0 0 auto;
    inline-size: 44px;
    block-size: 44px;
    border-radius: 10px;
    background: var(--foil-lb-accent-wash);
}
.lb-card__tick {
    flex: 0 0 auto;
    display: block;
    /* The design's tick sits in a 36px icon frame whose visible mark is
       31.5px wide, so the mark itself stops 2.25px short of the frame edge. */
    margin-inline-end: 2.25px;
}
.lb-card__title {
    margin: 0;
    font-size: 20px;
    font-weight: 600;
    line-height: 27px;
    letter-spacing: -0.2px;
    color: var(--foil-lb-ink);
}
.lb-card__body {
    margin: 0;
    font-size: 14px;
    font-weight: 400;
    line-height: 1.52;
    color: var(--foil-lb-ink-2);
}

/* ── Band 5 · Languages ──────────────────────────────────────────────────── */
.lb-langs {
    background: var(--foil-lb-surface-alt);
    padding-block: 80px;
}
.lb-langs__inner {
    display: grid;
    grid-template-columns: 460px minmax(0, 1fr);
    align-items: center;
    gap: 56px;
}
.lb-langs__copy { display: flex; flex-direction: column; align-items: flex-start; }
.lb-langs__title {
    margin: 20px 0 0;
    font-size: 32px;
    font-weight: 700;
    line-height: 1.16;
    letter-spacing: -0.64px;
    color: var(--foil-lb-ink);
}
.lb-langs__body {
    margin: 20px 0 0;
    font-size: 16px;
    font-weight: 400;
    line-height: 1.56;
    color: var(--foil-lb-ink-2);
}
.lb-pill {
    display: inline-flex;
    align-items: center;
    gap: 8px;
    margin-block-start: 20px;
    /* 39px tall in the design, and that 39 INCLUDES the 1px border on each
       edge — so the block padding is 9, not the 10 the spacing reads as. */
    padding: 9px 18px 9px 16px;
    background: var(--foil-lb-surface);
    border: 1px solid var(--foil-lb-border-2);
    border-radius: 9999px;
    font-size: 14px;
    font-weight: 500;
    line-height: 19px;
    color: var(--foil-lb-accent);
    text-decoration: none;
    transition: border-color 180ms ease, box-shadow 220ms ease,
                transform 220ms cubic-bezier(0.2, 0.8, 0.2, 1);
}
.lb-pill img {
    flex: 0 0 auto;
    display: block;
    transition: transform 220ms cubic-bezier(0.2, 0.8, 0.2, 1);
}
/* Hover lifts the pill a little and turns the globe, so the control reads as
   something that goes somewhere. Press returns it flat — the whole feedback
   loop lives in transform and box-shadow, which the compositor can animate
   without touching layout. */
.lb-pill:hover {
    border-color: var(--foil-lb-accent);
    box-shadow: 0 2px 10px 0 var(--foil-lb-card-shadow);
    transform: translateY(-1px);
}
.lb-pill:hover img { transform: rotate(20deg); }
.lb-pill:active {
    transform: translateY(0);
    box-shadow: none;
}
.lb-pill:focus-visible {
    outline: 2px solid var(--foil-lb-accent);
    outline-offset: 2px;
}
@media (prefers-reduced-motion: reduce) {
    .lb-pill, .lb-pill img { transition: none; }
    .lb-pill:hover { transform: none; }
    .lb-pill:hover img { transform: none; }
}

/* The cloud is decorative: every word carries the SAME gradient, and the size
   hierarchy alone conveys nothing a screen reader needs, so the container is
   aria-hidden and the words are plain spans. */
.lb-cloud {
    display: flex;
    flex-wrap: wrap;
    align-items: center;
    align-content: center;
    justify-content: center;
    gap: 0;
    --w-scale: 1;
}
/* THE page's only real horizontal overflow, and the sole reason `.foil-landing`
   needs an overflow net at all. Every word is `white-space: nowrap` (a language
   name must not break mid-name), and the widest — "Español (latinoamericano)" at
   --w-size:30px — is 404px, against a cloud content box of 300px at a 360px
   viewport. Measured: document scrollWidth 382 vs clientWidth 360.
   Scaling the whole cloud by one factor keeps the size hierarchy intact, which
   capping font-size against vw would flatten (it would shrink the 36px words,
   which are not the ones overflowing). 0.68 takes the widest word to 276px, and
   0.6 takes it to 244px, which clears the 267px box at 320px. The band is
   decorative and aria-hidden, so the type scale is free to move here. */
@media (max-width: 520px) { .lb-cloud { --w-scale: 0.68; } }
@media (max-width: 340px) { .lb-cloud { --w-scale: 0.6; } }
.lb-cloud__word {
    padding: 2px 6px;
    font-size: calc(var(--w-size) * var(--w-scale));
    font-weight: var(--w-weight);
    line-height: 1.45;
    white-space: nowrap;
    background-image: linear-gradient(90deg, var(--foil-lb-grad-from) 25%, var(--foil-lb-rule-4) 75%);
    -webkit-background-clip: text;
    background-clip: text;
    color: transparent;
}

/* ── Band 4 · Why ────────────────────────────────────────────────────────── */
.lb-why {
    position: relative;
    padding-block: 80px;
    overflow: hidden;
}
.lb-why__glow {
    position: absolute;
    inset-block-start: -304px;
    inset-inline-start: 50%;
    inline-size: 1100px;
    block-size: 608px;
    margin-inline-start: -550px;
    background-image: radial-gradient(ellipse 550px 304px at center,
                                      var(--foil-lb-glow-from) 0%, var(--foil-lb-glow-to) 100%);
    opacity: 0.6;
    pointer-events: none;
}
.lb-why__grid {
    position: relative;
    display: grid;
    grid-template-columns: repeat(3, 1fr);
    align-items: start;
    gap: 44px;
}
.lb-why__item { display: flex; flex-direction: column; }
.lb-why__tile {
    display: flex;
    align-items: center;
    justify-content: center;
    inline-size: 48px;
    block-size: 48px;
    background: var(--foil-lb-surface);
    /* Figma 292:532/537/542: rgba(78,159,255,0.16), NOT the grey --foil-lb-border-2
       the Band 5 pill uses. Its own token, so repointing one never moves the other. */
    border: 1px solid var(--foil-lb-tile-border);
    border-radius: 10px;
}
/* Every icon here is a Figma export carrying preserveAspectRatio="none", so it
   distorts to whatever box it is given instead of refusing. Each one therefore
   gets BOTH axes pinned to its own natural size — never a single shared rule,
   and never the global `img { height: auto }` reset, which would resolve the
   height from a ratio the export does not honour. Figma 267:5339 draws each
   leaf inside a 24px icon box centred in the 48px tile; centring the leaf
   directly in the tile lands on the same pixel, so the box needs no element. */
.lb-why__icon { display: block; }
/* nicon/slippagepercentageratiosplit, 18x18 leaf (24px box, inset 12.5%).
   The design mirrors it horizontally; verified against the Figma render, where
   the unmirrored glyph scores RMS 63 and the mirrored one RMS 4. */
.lb-why__icon--split   { inline-size: 18px; block-size: 18px; transform: scaleX(-1); }
/* nicon/aifourpointedstarsparkles_mid, 19.5x19.5 leaf (24px box, inset 9.38%). */
.lb-why__icon--sparkle { inline-size: 19.5px; block-size: 19.5px; }
/* nicon/padlockedfrozen, 16x20 leaf (24px box, inset 16.67% inline / 8.33% block). */
.lb-why__icon--padlock { inline-size: 16px; block-size: 20px; }
.lb-why__title {
    margin: 14px 0 0;
    font-size: 19px;
    font-weight: 600;
    line-height: 26px;
    letter-spacing: -0.19px;
    color: var(--foil-lb-ink);
}
.lb-why__body {
    margin: 14px 0 0;
    font-size: 14px;
    font-weight: 400;
    line-height: 1.52;
    color: var(--foil-lb-ink-2);
}

/* ── Band 7 · Footer ─────────────────────────────────────────────────────── */
.site-footer {
    background: var(--foil-lb-footer-bg);
    padding-block: 48px 40px;
}
.site-footer__inner { display: flex; flex-direction: column; gap: 32px; }
.site-footer__top { display: flex; align-items: center; gap: 12px; }
/* This anchor's colour DOES change on hover — measured rgb(17,94,163) ->
   rgb(15,84,140), inherited from a generic anchor rule — and it paints exactly
   ZERO pixels, because its only child is an <img> and images do not inherit
   colour. A test asserting "the computed colour changed" passes on a state
   that paints nothing. Opacity is the property that actually composites an
   image, so that is what carries the feedback here. Keyboard is covered by
   the global :focus-visible ring at :2153. */
.site-footer__mark {
    display: block; flex: 0 0 auto; line-height: 0;
    transition: opacity var(--durationFast) var(--curveEasyEase);
}
.site-footer__mark:hover { opacity: 0.75; }
.site-footer__brand { display: flex; flex-direction: column; }
.site-footer__wordmark {
    font-size: 18px;
    font-weight: 700;
    line-height: 25px;
    letter-spacing: -0.18px;
    color: var(--foil-lb-surface);
}
.site-footer__tagline {
    font-size: 14px;
    font-weight: 400;
    line-height: 21px;
    color: var(--foil-lb-footer-ink);
}
.site-footer__divider { block-size: 1px; background: var(--foil-lb-footer-divider); }
.site-footer__bottom {
    display: flex;
    flex-wrap: wrap;
    align-items: center;
    gap: 10px;
    font-size: 13px;
    line-height: 18px;
    color: var(--foil-lb-footer-ink);
}
.site-footer__bottom a { color: var(--foil-lb-footer-ink); text-decoration: none; }
.site-footer__bottom a:hover { color: var(--foil-lb-surface); text-decoration: underline; }
.site-footer__sep { color: var(--foil-lb-footer-sep); }

/* ── Narrow viewports ────────────────────────────────────────────────────── */
@media (max-width: 1000px) {
    .lb-langs__inner { grid-template-columns: minmax(0, 1fr); gap: 40px; }
    .lb-langs__copy { align-items: center; text-align: center; }
    .lb-how__grid { grid-template-columns: repeat(2, 1fr); }
    .lb-why__grid { grid-template-columns: repeat(2, 1fr); gap: 32px; }
}
@media (max-width: 680px) {
    .lb-band { padding-block: 64px; }
    .lb-how__title, .lb-langs__title { font-size: 26px; }
    .lb-how__grid, .lb-why__grid { grid-template-columns: minmax(0, 1fr); }
}

/* ============================================================================
   SECTION 38 — SEO PAGES: /languages/ and /documents/
   Additive only. Nothing above this line is edited: every selector here is new
   and used by exactly two templates (languages.html, documents.html).

   The page furniture is deliberately borrowed rather than reinvented — both
   pages sit on .lb-band / .foil-section__inner / .lb-eyebrow / .lb-card /
   .cta-band, so they inherit the landing's 1440 column, its 120px gutter, its
   gradient eyebrow and its card treatment for free. Only the parts the landing
   has no equivalent for (a page H1, a stat row, a 66-cell language grid, a
   format chip strip, a family catalogue) are new.

   Colours are the existing --foil-lb-* semantic tokens; no new token, no hex.
   Every inline offset is a logical property (ops.tests_rtl_invariants runs in
   ENFORCE mode) so both pages mirror on the 7 RTL locales without a second
   stylesheet.

   Browser-support note: each clamp()'d font-size declares its small end FIRST,
   so an engine that drops the clamp lands on the design's mobile value rather
   than on the browser default.
   ========================================================================= */

/* ── Shared page furniture ───────────────────────────────────────────────── */
.lp-head, .dp-head { padding-block-end: 72px; }

/* ONE column, and the aside spans the full content width beneath the copy.
   This was a 560px + 1fr two-column head, and it measured perfectly — two
   columns, no overflow, correct mirror — while reading badly on the page. The
   aside is SHORT (85px of stat tiles on /languages/, 106px of format chips on
   /documents/) and the copy column is tall (483px / 344px), so top-aligning
   them left a ~400px hole in the band's bottom-right quadrant. Centring the
   aside only moves the hole; the fix is to stop putting a short block beside a
   tall one. The copy keeps its reading measure via .lp-title/.lp-lead's own
   max-inline-size, and the stat row below now uses the whole 1200px. */
.lp-head__grid {
    display: grid;
    grid-template-columns: minmax(0, 1fr);
    align-items: start;
    gap: 0;
}
/* Centred, like the landing's own How-it-works and Why bands. A left-aligned
   head only works when something substantial sits to its right; with a short
   aside there is nothing to balance it, and every variant that kept the copy
   on one side left a void on the other — bottom-right when the aside was a
   column, top-right when it moved below. Centring has no side to leave empty,
   and it mirrors for free. */
.lp-head__copy { display: flex; flex-direction: column; align-items: center; text-align: center; }

.lp-title {
    margin: 16px 0 0;
    max-inline-size: 20ch;
    font-size: 30px;
    font-size: clamp(30px, 3.2vw, 44px);
    font-weight: 700;
    line-height: 1.12;
    letter-spacing: -0.9px;
    color: var(--foil-lb-ink);
    /* Centred with the rest of the head. This is the LAST text-align in the
       rule, which is the one that wins — a value added above it would have
       been inert. */
    text-align: center;
}
.lp-title--sub {
    font-size: 26px;
    font-size: clamp(26px, 2.4vw, 32px);
    letter-spacing: -0.64px;
    max-inline-size: 24ch;
}

.lp-lead {
    margin-block-start: 20px;
    max-inline-size: 62ch;
    /* The BLOCK is centred by the head's flex centring; the TEXT inside it is
       not. This is the landing's own pattern — centred display type over
       left-aligned body copy (.lb-card__body under a centred .lb-how__title).
       Three centred paragraphs give every line a different start position,
       which is exactly the edge the eye uses to find the next line.
       `start`, not `left`, so it flips with the page. */
    text-align: start;
}
.lp-lead p {
    margin: 0 0 14px;
    font-size: 16px;
    font-weight: 400;
    line-height: 1.56;
    color: var(--foil-lb-ink-2);
}
.lp-lead p:last-child { margin-block-end: 0; }

/* ── Stat row (languages / regions / right-to-left) ──────────────────────── */
.lp-stats {
    display: flex;
    flex-wrap: wrap;
    gap: 12px;
    /* The row sits UNDER the copy and spans the full content width, so it
       needs its own top margin — it is no longer a column aligning with the
       eyebrow. Same value at every viewport; nothing below zeroes it. */
    margin: 28px 0 0;
    padding: 0;
    list-style: none;
}
/* `1 1 0` not `1 1 128px`: the tiles divide the whole row equally instead of
   sizing to content and leaving a tail of empty track. Three tiles across the
   1200px content band come out at ~392px each. */
.lp-stat { flex: 1 1 0; }
.lp-stat {
    display: flex;
    flex-direction: column;
    /* The tiles are ~392px wide once they divide the content band, so their
       content is centred too — left-hugging text in a box that wide reads as
       an unfinished box rather than a stat. */
    align-items: center;
    gap: 2px;
    min-inline-size: 128px;
    padding: 16px 20px;
    background: var(--foil-lb-surface);
    border: 1px solid var(--foil-lb-border);
    border-radius: 12px;
    box-shadow: 0 2px 8px 0 var(--foil-lb-card-shadow);
}
.lp-stat__num {
    font-size: 28px;
    font-weight: 700;
    line-height: 1.1;
    letter-spacing: -0.5px;
    color: var(--foil-lb-accent);
    font-variant-numeric: tabular-nums;
}
.lp-stat__label {
    font-size: 13px;
    font-weight: 500;
    line-height: 18px;
    color: var(--foil-lb-ink-2);
}

/* ── Jump-to-region chips ────────────────────────────────────────────────── */
.lp-jump {
    display: flex;
    flex-wrap: wrap;
    justify-content: center;
    gap: 8px;
    margin-block-start: 32px;
}
.lp-jump__chip {
    display: inline-flex;
    align-items: center;
    padding: 7px 14px;
    background: var(--foil-lb-surface);
    border: 1px solid var(--foil-lb-border-2);
    border-radius: 9999px;
    font-size: 13px;
    font-weight: 500;
    line-height: 18px;
    color: var(--foil-lb-accent);
    text-decoration: none;
    transition: border-color 180ms ease, box-shadow 220ms ease;
}
.lp-jump__chip:hover {
    border-color: var(--foil-lb-accent);
    box-shadow: 0 2px 10px 0 var(--foil-lb-card-shadow);
}
.lp-jump__chip:focus-visible {
    outline: 2px solid var(--foil-lb-accent);
    outline-offset: 2px;
}
@media (prefers-reduced-motion: reduce) {
    .lp-jump__chip { transition: none; }
}

/* ── The language catalogue ──────────────────────────────────────────────── */
.lp-catalogue { background: var(--foil-lb-surface-alt); }

.lp-region { margin-block-start: 56px; }
.lp-region:first-child { margin-block-start: 0; }
.lp-region__head {
    display: flex;
    flex-wrap: wrap;
    align-items: baseline;
    gap: 12px;
    padding-block-end: 12px;
    border-block-end: 1px solid var(--foil-lb-border);
}
.lp-region__title {
    margin: 0;
    font-size: 22px;
    font-weight: 700;
    line-height: 1.2;
    letter-spacing: -0.4px;
    color: var(--foil-lb-ink);
}
.lp-region__count {
    margin: 0;
    font-size: 13px;
    font-weight: 500;
    line-height: 18px;
    color: var(--foil-lb-legal-ink);
    font-variant-numeric: tabular-nums;
}

/* 5 columns at 1440, 3 near 1000, 2 near 680, 1 on a phone — the step-downs
   are the media queries at the foot of this section, not auto-fill guesswork,
   so the count is a stated design value at every viewport the CSS branches on. */
.lp-grid {
    display: grid;
    grid-template-columns: repeat(5, minmax(0, 1fr));
    gap: 12px;
    margin: 20px 0 0;
    padding: 0;
    list-style: none;
}
.lp-lang {
    display: flex;
    flex-direction: column;
    gap: 3px;
    min-inline-size: 0;
    padding: 14px 16px;
    background: var(--foil-lb-surface);
    border: 1px solid var(--foil-lb-border);
    border-radius: 10px;
}
/* The endonym is the primary label and may be Arabic, Hebrew or Pashto inside
   an English page. `unicode-bidi: isolate` — the same choice
   .lang-menu__row-native makes — keeps an RTL name from reordering the Latin
   text around it, without the geometry shift an explicit dir flip causes. */
.lp-lang__native {
    font-size: 16px;
    font-weight: 600;
    line-height: 1.35;
    color: var(--foil-lb-ink);
    unicode-bidi: isolate;
    overflow-wrap: anywhere;
}
.lp-lang__name {
    font-size: 13px;
    font-weight: 400;
    line-height: 1.35;
    color: var(--foil-lb-ink-2);
    overflow-wrap: anywhere;
}
.lp-lang__chip {
    align-self: flex-start;
    margin-block-start: 4px;
    padding: 2px 8px;
    background: var(--foil-lb-accent-wash);
    border-radius: 9999px;
    font-size: 11px;
    font-weight: 500;
    line-height: 16px;
    color: var(--foil-lb-accent);
}

/* ── /documents/ — accepted-format chips ─────────────────────────────────── */
/* Sits under the copy at full content width — see the .lp-head__grid note. */
.dp-formats-wrap { display: flex; flex-direction: column; align-items: center; gap: 12px; margin-block-start: 28px; }
.dp-formats__caption {
    margin: 0;
    font-size: 13px;
    font-weight: 600;
    line-height: 18px;
    letter-spacing: 0.2px;
    color: var(--foil-lb-legal-ink);
}
.dp-formats {
    display: flex;
    flex-wrap: wrap;
    justify-content: center;
    gap: 8px;
    /* The wrap above carries the top margin; this list adds none of its own. */
    margin: 0;
    padding: 0;
    list-style: none;
}
.dp-formats__chip {
    padding: 7px 14px;
    background: var(--foil-lb-track);
    border: 1px solid var(--foil-lb-track-edge);
    border-radius: 8px;
    font-size: 13px;
    font-weight: 600;
    line-height: 18px;
    color: var(--foil-lb-ink-2);
    /* Extensions are ASCII tokens; isolate so ".pdf" keeps its leading dot on
       the correct side inside an RTL page. */
    unicode-bidi: isolate;
}
.dp-formats__chip--text {
    background: var(--foil-lb-accent-wash);
    border-color: var(--foil-lb-tile-border);
    font-weight: 500;
    color: var(--foil-lb-accent);
    unicode-bidi: normal;
}

/* ── /documents/ — the four routes ───────────────────────────────────────── */
.dp-routes { background: var(--foil-lb-surface-alt); }
.dp-note {
    margin-block-start: 24px;
    padding: 24px 26px;
    background: var(--foil-lb-surface);
    border: 1px solid var(--foil-lb-border);
    border-inline-start: 3px solid var(--foil-lb-accent);
    border-radius: 12px;
}
.dp-note__title {
    margin: 0 0 8px;
    font-size: 17px;
    font-weight: 700;
    line-height: 1.25;
    color: var(--foil-lb-ink);
}
.dp-note__body {
    margin: 0;
    max-inline-size: 78ch;
    font-size: 14px;
    font-weight: 400;
    line-height: 1.55;
    color: var(--foil-lb-ink-2);
}

/* ── /documents/ — limits ────────────────────────────────────────────────── */
.dp-limits__grid {
    display: grid;
    grid-template-columns: 380px minmax(0, 1fr);
    align-items: start;
    gap: 56px;
}
.dp-limits__copy { display: flex; flex-direction: column; align-items: flex-start; }
.dp-limits__list {
    display: grid;
    grid-template-columns: repeat(2, minmax(0, 1fr));
    gap: 12px;
    margin: 0;
    padding: 0;
    list-style: none;
}
/* Five tiles in a two-column grid leave the last row half empty, and the hole
   lands in the bottom inline-end corner — the last thing the eye reaches in the
   band, and the same void the head grid above was rebuilt to avoid. Measured
   before this rule: an empty 376 x 88.8 cell at x=944 (en-gb) / x=120 (ar).
   A lone final tile spans the row instead.

   `:last-child:nth-child(odd)` makes it self-correcting — it fires only when
   the count is ODD, so adding a sixth limit restores a clean 2x3 with no rule
   left behind to remember. `1 / -1` is direction-agnostic (grid line 1 is the
   inline-start edge in both LTR and RTL), so it mirrors with no second
   declaration; and at <=680px, where the list is already one column, it spans
   that single column and changes nothing. */
.dp-limits__list > .dp-limit:last-child:nth-child(odd) { grid-column: 1 / -1; }
.dp-limit {
    display: flex;
    flex-direction: column;
    gap: 4px;
    min-inline-size: 0;
    padding: 18px 20px;
    background: var(--foil-lb-surface);
    border: 1px solid var(--foil-lb-border);
    border-radius: 12px;
    box-shadow: 0 2px 8px 0 var(--foil-lb-card-shadow);
}
.dp-limit__value {
    font-size: 26px;
    font-weight: 700;
    line-height: 1.1;
    letter-spacing: -0.5px;
    color: var(--foil-lb-accent);
    font-variant-numeric: tabular-nums;
}
.dp-limit__label {
    font-size: 13px;
    font-weight: 400;
    line-height: 1.4;
    color: var(--foil-lb-ink-2);
}

/* ── /documents/ — the family catalogue ──────────────────────────────────── */
.dp-catalogue { background: var(--foil-lb-surface-alt); }
.dp-fams {
    display: grid;
    grid-template-columns: repeat(3, minmax(0, 1fr));
    align-items: start;
    gap: 20px;
    margin-block-start: 36px;
}
.dp-fam {
    display: flex;
    flex-direction: column;
    gap: 12px;
    min-inline-size: 0;
    padding: 24px 24px 26px;
    background: var(--foil-lb-surface);
    border: 1px solid var(--foil-lb-border);
    border-radius: 12px;
    box-shadow: 0 2px 8px 0 var(--foil-lb-card-shadow);
}
.dp-fam__head {
    display: flex;
    flex-wrap: wrap;
    align-items: baseline;
    gap: 10px;
    padding-block-end: 10px;
    border-block-end: 1px solid var(--foil-lb-border);
}
.dp-fam__title {
    margin: 0;
    font-size: 17px;
    font-weight: 700;
    line-height: 1.25;
    letter-spacing: -0.2px;
    color: var(--foil-lb-ink);
}
.dp-fam__count {
    margin: 0;
    font-size: 12px;
    font-weight: 500;
    line-height: 16px;
    color: var(--foil-lb-legal-ink);
    font-variant-numeric: tabular-nums;
}
.dp-fam__list {
    margin: 0;
    padding: 0;
    list-style: none;
    display: flex;
    flex-direction: column;
    gap: 6px;
    font-size: 13.5px;
    line-height: 1.45;
    color: var(--foil-lb-ink-2);
}

/* ── Narrow viewports ────────────────────────────────────────────────────── */
@media (max-width: 1200px) {
    .lp-grid { grid-template-columns: repeat(4, minmax(0, 1fr)); }
}
@media (max-width: 1000px) {
    .lp-grid { grid-template-columns: repeat(3, minmax(0, 1fr)); }
    .dp-fams { grid-template-columns: repeat(2, minmax(0, 1fr)); }
    .dp-limits__grid { grid-template-columns: minmax(0, 1fr); gap: 32px; }
    /* .lp-head__grid needs no override here — it is one column at every
       viewport now. The `.lp-stats, .dp-formats-wrap { margin-block-start: 0 }`
       rule that used to sit here has gone WITH it: left behind, it would have
       silently zeroed the aside's new top margin on every screen under 1000px
       while the wide layout looked correct. */
}
@media (max-width: 680px) {
    .lp-head, .dp-head { padding-block-end: 48px; }
    .lp-grid { grid-template-columns: repeat(2, minmax(0, 1fr)); }
    .dp-fams { grid-template-columns: minmax(0, 1fr); }
    .dp-limits__list { grid-template-columns: minmax(0, 1fr); }
    .lp-region { margin-block-start: 40px; }
}
@media (max-width: 460px) {
    .lp-grid { grid-template-columns: minmax(0, 1fr); }
    /* One tile per row. Three do not fit across a phone, and `1 1 auto` let
       them size to their labels — which put two on the first row and left the
       third stretched across the whole width on its own, reading as a mistake
       rather than a choice. A full-width stack is the deliberate version. */
    .lp-stat { min-inline-size: 0; flex: 1 1 100%; }
}

/* The How-it-works band's pill (R3, → /documents/). The band is centred, and
   .lb-pill is inline-flex, so it needs a centring row of its own; the Languages
   band's copy column is flex-start and needs none. */
.lb-how__cta {
    display: flex;
    justify-content: center;
    margin-block-start: 12px;
}
