/*
 ================================================================================
 GDH Master CSS — v3.15.2
 ================================================================================
 A brand-agnostic, framework-agnostic design-system starter for modern websites,
 with optional CMS / vendor compatibility layers.

 Author:   Gareth Esson — Guess Design House
 Licence:  Proprietary (internal use)

 This file contains the complete v3 foundation, atom and component layers,
 page-composition patterns, interaction components and native semantic polish.

 Brand-specific values should be overridden in a separate brand.css file using
 the `brand` layer. Project-specific exceptions should live in the `overrides`
 layer. Optional vendor / CMS compatibility rules (Divi reset, WordPress
 pagination markup, etc.) are isolated in the `vendor` layer and can be removed
 if not required.

 Companion documentation:
   • DECISIONS-v3.md         — every architectural decision and the reasoning
   • JS-HOOKS.md             — state-class / ARIA / custom-property contracts
                                that the system reads from
   • templates/brand-starter.css — copy-paste starter for a new project's
                                brand override file

 What's new in v3 vs v2:
   1.  HSL component decomposition replaced with Relative Color Syntax (RCS)
   2.  OKLCH for all derived colours — perceptually-uniform variants
   3.  hsla(...0.X) literals replaced with color-mix()
   4.  CSS @layer architecture — no specificity wars
   5.  Container queries on every grid that should respond to its own width
   6.  Z-index scale expressed as tokens (no invented call-site values)
   7.  accent-color set globally for native form controls
   8.  text-wrap: balance / pretty applied to headings + paragraphs
   9.  :user-invalid replaces :invalid for forms (live validation)
  10.  Motion tokens with named easings + duration scale
  11.  Logical properties throughout (margin-inline-*, padding-block-*, etc.)
  12.  Single fluid type scale (one base + one ratio); display/prose/card alias
  13.  field-sizing: content for textareas (auto-grow without JS)
  14.  light-dark() — dark mode shipping by default; brands inherit
  15.  Subgrid (opt-in via .card-grid--aligned) for cross-card row alignment
  16.  :has() for declarative variants where it cleanly replaces a modifier
  17.  scrollbar-gutter: stable (prevents content shift when scrollbars appear)
  18.  prefers-contrast: more / forced-colors: active honoured
  19.  Concentric radii rule codified
  20.  Native <dialog> for modals (free focus trap, ESC, top-layer rendering)

 Browser baseline: Chrome 119+, Safari 17+, Firefox 128+ (all shipped Q3 2024
 or earlier — universally safe in 2026).

 ================================================================================
   SECTION MAP
 ================================================================================
   A.   @layer order
   B.   Reset + global hooks

   FOUNDATION
   1.   BRAND INPUTS                  ← override these in brand.css
   2.   Type scale (derived)
   3.   Spacing / layout
   4.   Radius (single-unit, concentric)
   5.   Colour derivations (OKLCH from brand inputs)
   6.   Background / surface (light-dark)
   7.   Border tokens
   8.   Shadow tokens
   9.   Motion tokens
  10.   Z-index scale
  11.   Breakpoint reference (literals — for @media)
  12.   State / feedback colours
  13.   Focus ring tokens
  14.   Form tokens
  15.   Button tokens
  16.   Reduced-contrast / forced-colours overrides

   ATOMS
  17.   Focus-visible (global rule)
  18.   Buttons (.btn family)
   V.   Vendor (Divi reset, kept dormant)
  19.   Links (inline + action)
  20.   Utility text (eyebrow / signpost / preheader / lede / subheading / micro)
  21.   Forms (with :user-invalid)
  22.   Surfaces (color-scheme scoping)
  23.   Cards (container queries, :has(), concentric radii)
  24.   Glassmorphism
  25.   Spot icons
  26.   Accordion (native <details>)
  27.   Alerts
  28.   Badges
  29.   Tables
  30.   Pagination
  31.   Document row
  32.   Legal page
  33.   System pages — 404 + search
  34.   Skip-to-content
  35.   Touch targets
  36.   Split layout
  37.   Sticky page nav
  38.   Mega menu
  39.   Scroll-reveal animation

   PAGE COMPOSITION
  40.   Layout patterns — hero-split, split-section, hero-badge, intro-section
  41.   Editorial — pull-quote, breadcrumb, cta-section, related-grid
  42.   Card systems — tile / news / person / team / story / testimonial
  43.   Archive layouts
  44.   Page sections — contact, ways/way-card, faq, mission, timeline
  45.   Accessibility-first — easy-read, status-pill, eligibility-panel,
        referral-cta, funder-logo-strip, report-feature
  46.   BEM site nav + dropdown + mobile menu + footer

   INTERACTION COMPONENTS
  47.   Toast notifications
  48.   Modal / dialog (native <dialog>)
  49.   Progress bar
  50.   Interactive chip
  51.   Hamburger
  52.   Nav tabs
  53.   Scroll-shrink header helper

   UI PRIMITIVES
  54.   Tabs + panels (role="tablist")
  55.   Switch / toggle
  56.   Slider / range input
  57.   Tooltip ([data-tooltip])
  58.   Dropdown menu / kebab menu
  59.   Avatar
  60.   Code block + inline code

   LOADING + NATIVE SEMANTIC POLISH
  61.   Skeleton loader
  62.   Spinner
  63.   Native semantic elements (dl, figure, kbd, mark)
  64.   Truncation utilities (truncate, line-clamp-N)
 ================================================================================
*/


/* ================================================================================
   A. @LAYER ORDER
   --------------------------------------------------------------------------------
   First declared = lowest priority. Later declarations are unaffected.
   Brand override files declare into `brand`. Project one-offs into `overrides`.
   The Divi reset block lives in `vendor` and can be removed if you never use Divi.
   ================================================================================ */
@layer reset, tokens, base, components, utilities, vendor, brand, overrides;


/* ================================================================================
   B. RESET + GLOBAL HOOKS
   --------------------------------------------------------------------------------
   Modern minimal reset. Lives in `reset` so brand/component code can never
   accidentally undercut it.
   ================================================================================ */
@layer reset {

  *, *::before, *::after { box-sizing: border-box; }
  body {
    margin: 0;
    font-family: var(--font-body);
    font-weight: var(--body-weight);
    background-color: var(--bg-page);
    color: var(--text-body);
  }
  h1, h2, h3, h4, h5, h6 {
    margin: 0;
    font-weight: var(--heading-weight);
  }
  /* Default heading sizes — PROSE scale.
     Most uses of an <h1> are body / document content (articles,
     docs, app pages). Hero and marketing surfaces opt UP to the
     display scale via their own class rules (.hero h1, etc.) which
     beat these on specificity.

     Before this rule, master left raw <h1>..<h6> at browser defaults
     (h1 = 2em, h2 = 1.5em, h3 = 1.17em ...) which read as anaemic
     in any content authored as raw HTML / markdown / MDX / CMS-
     imported. Raw selectors (specificity 0,0,1) so any class rule
     in a component, brand override, or project wins. */
  h1 { font-size: var(--text-prose-h1); line-height: var(--leading-tight, 1.15); }
  h2 { font-size: var(--text-prose-h2); line-height: var(--leading-snug, 1.2); }
  h3 { font-size: var(--text-prose-h3); line-height: var(--leading-snug, 1.25); }
  h4 { font-size: var(--text-prose-h4); line-height: var(--leading-snug, 1.3); }
  h5 { font-size: var(--text-prose-h5); line-height: var(--leading-normal, 1.4); }
  h6 { font-size: var(--text-prose-h6); line-height: var(--leading-normal, 1.4); }
  /* Browser default <p> has margin-block: 1em — usually unwanted for
     tight component layouts (cards, lists). Zero by default; opt back
     in via prose / legal-body / etc. that style their own rhythm. */
  p { margin: 0; }
  img, svg, video, canvas { display: block; max-inline-size: 100%; block-size: auto; }

  /* Global root behaviours.
     - color-scheme: light dark — opts every brand into automatic dark mode
       (via light-dark() in tokens). A brand wanting light-only sets
       :root { color-scheme: light; } in their brand file.
     - accent-color — colours native checkboxes/radios/range/progress.
     - scrollbar-gutter: stable — prevents content shift when modals open
       and the page scrollbar disappears.
     - text-size-adjust — prevents iOS bumping body text in landscape. */
  :root {
    color-scheme: light dark;
    accent-color: var(--brand-accent);
    scrollbar-gutter: stable;
    -webkit-text-size-adjust: 100%;
    text-size-adjust: 100%;
  }

  /* Free typographic polish. balance for headings (browsers balance line lengths
     visually), pretty for body (avoids orphans/widows on the last line). */
  h1, h2, h3, h4 { text-wrap: balance; }
  p, li, dd, blockquote { text-wrap: pretty; }

  /* Anchor-link offset — when sticky headers are present, jumping to #section
     would otherwise land behind the header. --scroll-margin defaults to 6rem;
     override in a brand file if your sticky header is taller. :where() gives
     zero specificity so per-page overrides are trivial. */
  :where(h1, h2, h3, h4, h5, h6) {
    scroll-margin-block-start: var(--scroll-margin, 6rem);
  }

  /* Native auto-grow textareas (Chrome 123+, Safari 18+, Firefox 134+).
     Scoped to .form-input (master's textarea opt-in) so bare <textarea>
     elements outside master's form classes keep browser defaults — chat
     inputs, code editor textareas, third-party widgets etc. weren't
     consenting to auto-grow.
     Pair with .form-input's inline-size: 100% and textarea.form-input's
     min-block-size: 5rem (defined later in this file) — those handle the
     "empty textarea collapses to zero" case in both axes. */
  textarea.form-input { field-sizing: content; }

  /* Honour reduced motion globally. Component-level transitions can opt back in. */
  @media (prefers-reduced-motion: reduce) {
    *, *::before, *::after {
      animation-duration: 0.01ms !important;
      animation-iteration-count: 1 !important;
      transition-duration: 0.01ms !important;
      scroll-behavior: auto !important;
    }
  }
}


/* ================================================================================
   TOKENS
   --------------------------------------------------------------------------------
   Everything brand-tunable lives here. Two zones:
     1. BRAND INPUTS  — the small set a brand file overrides.
     2. DERIVED       — computed from inputs; rarely overridden.
   ================================================================================ */
@layer tokens {

  /* ============================================================
     1. BRAND INPUTS
     ------------------------------------------------------------
     These are the only tokens a brand file needs to override.
     Everything else cascades from these.

     Colour can be expressed as either hex or oklch — both work,
     because every derivation uses `oklch(from var(--brand-x) ...)`
     which accepts either input format.

       --brand-primary: #2463eb;             ← hex (designer-friendly)
       --brand-primary: oklch(55% 0.18 250); ← oklch (system-native)

     OKLCH is recommended because L (perceptual lightness) is
     directly comparable: a brand-primary at L 55% will always feel
     equally bright across hues. HSL doesn't give you that.
     ============================================================ */
  :root {
    /* — Brand colours (the three tokens 90% of brand work touches) —
       Three roles. See DECISIONS-v3.md for the full mental model.

       --brand-primary    "This is the brand": big visual statements.
                          Hero panel fills, footer, brand surfaces, nav
                          chrome, timeline progress. The colour you'd
                          paint a whole section in.

       --brand-secondary  "The counterpoint": usually a dark neutral;
                          sometimes a real second hue. Card titles,
                          easy-read border stripe, eyebrow text on
                          a11y patterns, dense text panels. The colour
                          for "important, not brand-coloured."

       --brand-accent     "The action colour": every button, link,
                          focus ring, badge, native form control,
                          status pill. The colour the user clicks on.
                          Equals primary for mono-brand identities;
                          differs when brand-presence and CTAs should
                          feel visually separate. */
    --brand-primary:   #2463eb;
    --brand-secondary: #3f3f46;
    --brand-accent:    #2463eb;

    /* — Typography inputs — */
    --font-body:    system-ui, -apple-system, "Segoe UI", Roboto, sans-serif;
    --font-display: var(--font-body);  /* override per brand for display face  */
    --font-mono:    ui-monospace, "SF Mono", Menlo, Consolas, monospace;

    /* — Type scale knobs —
       All four propagate through the entire scale (--text-_1
       through --text-9) via pow(--type-ratio, n) and a fluid
       clamp formula. See Section 2 for the derivation block. */
    --type-base-min:   1rem;      /* body size at narrowest viewport          */
    --type-base-max:   1.125rem;  /* body size at widest viewport             */
    --type-ratio:      1.2;       /* minor third — increase for more drama    */
    --type-vp-min:     320;       /* unitless px — narrowest fluid viewport   */
    --type-vp-max:     1400;      /* unitless px — widest fluid viewport      */

    /* — Radius personality — */
    --radius-unit:     0.25rem;   /* the brand's radius "feel". Soft = 0.5rem,
                                     sharp = 0.125rem, geometric = 0.        */

    /* — Spacing personality —
       The brand's rhythm "feel". Drives the t-shirt scale below
       (Section 3) via a calc-multiplier table — same shape as
       --radius-unit drives --radius-1..8. Override to scale the
       whole spacing system proportionally; the relative-gap
       rhythm of the t-shirt scale is preserved.                */
    --space-base:      1rem;      /* default t-shirt scale derives from this */

    /* — Layout widths — */
    --layout-max:        1400px;  /* page-level container                     */
    --layout-max-narrow:  880px;  /* prose / long-form                        */
  }


  /* ============================================================
     2. TYPE SCALE — DERIVED
     ------------------------------------------------------------
     Single fluid scale. Eleven steps (--text-_1 through --text-9),
     all DERIVED at use-time from the four type knobs in Section 1
     (--type-base-min, --type-base-max, --type-ratio, --type-vp-*)
     via CSS pow() and a fluid clamp formula.

     Per step, n = the step's distance from --text-1 (the body
     baseline). For --text-_1 / --text-_2 this is negative; for
     --text-2 through --text-9 it's positive.

       step-min = type-base-min × pow(type-ratio, n)
       step-max = type-base-max × pow(type-ratio, n)

     The fluid middle is a linear interpolation between step-min
     and step-max as the viewport widens from --type-vp-min to
     --type-vp-max. clamp() pins the static endpoints at min and
     max so the scale doesn't grow infinitely below 320px or above
     1400px.

     Brand override of --type-ratio now propagates through the
     entire scale — change one input, every step retunes. Same
     for --type-base-min/max (anchor sizes) and --type-vp-* (fluid
     range). Pre-v3.6.0 these tokens were declared but unused; the
     scale was pre-baked at ratio 1.2. v3.6.0 wires them up.

     pow() browser baseline: Chrome 112+, Safari 16.4+, Firefox 118+.
     Comfortably within master's v3 floor (Chrome 119+, Safari 17+,
     Firefox 128+). See AUDIT-NOTES.md Pass-11 for the calibration
     story; DECISIONS-v3.md for the architectural rationale.

     Semantic aliases follow at the bottom — that's what components
     should reference (--text-card-h1, --text-prose-body, etc.).
     ============================================================ */
  :root {
    /* Fluid progress: 0 at viewport vp-min, 1 at vp-max, unbounded
       outside. clamp() in each --text-N pins the endpoints, so the
       unbounded-ness is not a hazard. */
    --_type-fluid: calc(
      (100vw - var(--type-vp-min) * 1px)
      / ((var(--type-vp-max) - var(--type-vp-min)) * 1px)
    );

    /* Per-step minima and maxima. Private (leading underscore) —
       components should reference --text-N (or, preferably, the
       semantic aliases below). */
    --_text-_1-min: calc(var(--type-base-min) * pow(var(--type-ratio), -2));
    --_text-_1-max: calc(var(--type-base-max) * pow(var(--type-ratio), -2));
    --_text-0-min:  calc(var(--type-base-min) * pow(var(--type-ratio), -1));
    --_text-0-max:  calc(var(--type-base-max) * pow(var(--type-ratio), -1));
    --_text-1-min:  var(--type-base-min);
    --_text-1-max:  var(--type-base-max);
    --_text-2-min:  calc(var(--type-base-min) * pow(var(--type-ratio), 1));
    --_text-2-max:  calc(var(--type-base-max) * pow(var(--type-ratio), 1));
    --_text-3-min:  calc(var(--type-base-min) * pow(var(--type-ratio), 2));
    --_text-3-max:  calc(var(--type-base-max) * pow(var(--type-ratio), 2));
    --_text-4-min:  calc(var(--type-base-min) * pow(var(--type-ratio), 3));
    --_text-4-max:  calc(var(--type-base-max) * pow(var(--type-ratio), 3));
    --_text-5-min:  calc(var(--type-base-min) * pow(var(--type-ratio), 4));
    --_text-5-max:  calc(var(--type-base-max) * pow(var(--type-ratio), 4));
    --_text-6-min:  calc(var(--type-base-min) * pow(var(--type-ratio), 5));
    --_text-6-max:  calc(var(--type-base-max) * pow(var(--type-ratio), 5));
    --_text-7-min:  calc(var(--type-base-min) * pow(var(--type-ratio), 6));
    --_text-7-max:  calc(var(--type-base-max) * pow(var(--type-ratio), 6));
    --_text-8-min:  calc(var(--type-base-min) * pow(var(--type-ratio), 7));
    --_text-8-max:  calc(var(--type-base-max) * pow(var(--type-ratio), 7));
    --_text-9-min:  calc(var(--type-base-min) * pow(var(--type-ratio), 8));
    --_text-9-max:  calc(var(--type-base-max) * pow(var(--type-ratio), 8));

    /* Public scale — fluid clamp between min and max for each step. */
    --text-_1: clamp(var(--_text-_1-min), calc(var(--_text-_1-min) + (var(--_text-_1-max) - var(--_text-_1-min)) * var(--_type-fluid)), var(--_text-_1-max));  /* small caption */
    --text-0:  clamp(var(--_text-0-min),  calc(var(--_text-0-min)  + (var(--_text-0-max)  - var(--_text-0-min))  * var(--_type-fluid)), var(--_text-0-max));   /* meta / micro */
    --text-1:  clamp(var(--_text-1-min),  calc(var(--_text-1-min)  + (var(--_text-1-max)  - var(--_text-1-min))  * var(--_type-fluid)), var(--_text-1-max));   /* body baseline */
    --text-2:  clamp(var(--_text-2-min),  calc(var(--_text-2-min)  + (var(--_text-2-max)  - var(--_text-2-min))  * var(--_type-fluid)), var(--_text-2-max));
    --text-3:  clamp(var(--_text-3-min),  calc(var(--_text-3-min)  + (var(--_text-3-max)  - var(--_text-3-min))  * var(--_type-fluid)), var(--_text-3-max));
    --text-4:  clamp(var(--_text-4-min),  calc(var(--_text-4-min)  + (var(--_text-4-max)  - var(--_text-4-min))  * var(--_type-fluid)), var(--_text-4-max));
    --text-5:  clamp(var(--_text-5-min),  calc(var(--_text-5-min)  + (var(--_text-5-max)  - var(--_text-5-min))  * var(--_type-fluid)), var(--_text-5-max));
    --text-6:  clamp(var(--_text-6-min),  calc(var(--_text-6-min)  + (var(--_text-6-max)  - var(--_text-6-min))  * var(--_type-fluid)), var(--_text-6-max));
    --text-7:  clamp(var(--_text-7-min),  calc(var(--_text-7-min)  + (var(--_text-7-max)  - var(--_text-7-min))  * var(--_type-fluid)), var(--_text-7-max));
    --text-8:  clamp(var(--_text-8-min),  calc(var(--_text-8-min)  + (var(--_text-8-max)  - var(--_text-8-min))  * var(--_type-fluid)), var(--_text-8-max));
    --text-9:  clamp(var(--_text-9-min),  calc(var(--_text-9-min)  + (var(--_text-9-max)  - var(--_text-9-min))  * var(--_type-fluid)), var(--_text-9-max));

    /* — Semantic aliases: the only names components should reference — */

    /* Card scale (densest — for tight UI, list items, compact cards).
       Calibrated to the premium-editorial convention (Stripe, Linear,
       Vercel, Apple, FT, New Yorker): card body sits ONE STEP BELOW page
       body so cards read as a different content register (compact
       secondary unit) rather than tiny articles. Pre-v3.14.0 card body
       defaulted to var(--text-1) — equal to prose body — and cards felt
       article-sized. v3.14.0 dropped card body to --text-0 and card meta
       to --text-_1 to restore the visual register separation. Accessibility-
       first brands wanting larger card text override one line in @layer
       brand: { --text-card-body: var(--text-1); --text-card-meta: var(--text-0); }
       to recover the v3.0–v3.13 sizing. */
    --text-card-h6:    var(--text-0);
    --text-card-h5:    var(--text-1);
    --text-card-h4:    var(--text-2);
    --text-card-h3:    var(--text-3);
    --text-card-h2:    var(--text-3);
    --text-card-h1:    var(--text-4);
    --text-card-body:  var(--text-0);     /* v3.14.0: one step below page body — compact secondary unit */
    --text-card-meta:  var(--text-_1);    /* v3.14.0: one step below card body — captions, timestamps, micro-meta */

    /* Prose scale (mid — long-form, blog posts, reading content) */
    --text-prose-h6:    var(--text-1);
    --text-prose-h5:    var(--text-2);
    --text-prose-h4:    var(--text-3);
    --text-prose-h3:    var(--text-4);
    --text-prose-h2:    var(--text-5);
    --text-prose-h1:    var(--text-6);
    --text-prose-body:  var(--text-1);
    --text-prose-quote: var(--text-3);
    --text-prose-lede:  var(--text-2);

    /* Display scale (largest — heroes, marketing, landing pages) */
    --text-display-h6:    var(--text-1);
    --text-display-h5:    var(--text-2);
    --text-display-h4:    var(--text-3);
    --text-display-h3:    var(--text-4);
    --text-display-h2:    var(--text-6);
    --text-display-h1:    var(--text-7);
    --text-display-body:  var(--text-2);
    --text-display-quote: var(--text-4);

    /* Utility text */
    --text-eyebrow:   var(--text-0);
    --text-lede:      1.1em;            /* relative to context */
    --text-micro:     var(--text-_1);

    /* — Font-family roles (added v3.15.0) —
       Semantic mid-layer between font inputs (--font-body, --font-display,
       etc.) and component consumers. Components reference the ROLE
       (--type-card-heading), not the input (--font-body / --font-display).
       Brands wanting fine-grained per-role control retune one knob site-wide
       instead of editing every consumer.

       Defaults preserve v3.0–v3.14 visual behaviour:
         --type-display-heading → --font-display  (was hardcoded in components)
         --type-prose-heading   → --font-body     (was inherited from body)
         --type-card-heading    → --font-body     (was inherited from body)
         --type-eyebrow         → --font-body     (was inherited from body)

       A brand wanting display-font card titles flips one line in their
       brand file:  --type-card-heading: var(--font-display).
       A brand wanting display-font eyebrows:  --type-eyebrow: var(--font-display).
       A brand introducing a serif for prose:  --type-prose-heading: var(--font-serif).

       Not all heading-like rules route through these tokens — some
       components keep raw font inputs deliberately. .hero-split__eyebrow
       uses --font-display directly (component-specific eyebrow flair),
       not --type-eyebrow. Pull-quote marks, avatar initials, and similar
       decorative-typography uses keep raw --font-display. The role layer
       is for the genuine "page section heading," "card title," "eyebrow
       base" cases — not every font-family declaration. */
    --type-display-heading: var(--font-display);
    --type-prose-heading:   var(--font-body);
    --type-card-heading:    var(--font-body);
    --type-eyebrow:         var(--font-body);

    /* Type weights — tunable per brand */
    --weight-light:    300;
    --weight-regular:  400;
    --weight-medium:   500;
    --weight-semibold: 600;
    --weight-bold:     700;

    /* Body / heading weight roles. Convention: body lighter than heading.
       Override either token in a brand file to retune the body→heading
       relationship. Some brands deliberately set them equal (then carry
       the visual hierarchy via size alone) — that's a deliberate quirk,
       not the default. */
    --body-weight:    var(--weight-regular);
    --heading-weight: var(--weight-bold);

    /* Type tracking */
    --tracking-tight:   -0.02em;
    --tracking-normal:  0;
    --tracking-wide:    0.05em;
    --tracking-eyebrow: 0.15em;

    /* Type leading. The four canonical leadings cover the prose-side
       of typography:
         --leading-tight   prose-tight (raw h1, dense card titles)
         --leading-snug    standard heading (h2-h4 prose)
         --leading-normal  body / general
         --leading-loose   long-form prose with eased rhythm

       --lh-display is the display-side peer. At hero scale (h1 / h2 /
       h3 sized via --text-display-*), the prose-tight 1.15 reads
       loose — display type wants ~1.0-1.05 to read as a confident
       single block. Applied via the master's display heading rules
       alongside letter-spacing: var(--tracking-tight). See
       DECISIONS-v3.md → "Why display headings use --tracking-tight
       and --lh-display by default" for the calibration story. */
    --leading-tight:  1.15;
    --leading-snug:   1.3;
    --leading-normal: 1.5;
    --leading-loose:  1.7;
    --lh-display:     1.02;
  }


  /* ============================================================
     3. SPACING / LAYOUT
     ------------------------------------------------------------
     T-shirt sizes for global rhythm; fluid clamps for structural
     section padding. The "global" scale is rem-anchored (consistent
     regardless of font-size); the "structural" scale is fluid
     against viewport.

     Global scale derives from --space-base (Section 1). Multipliers
     reflect the t-shirt rhythm — geometric-ish but with deliberate
     rounding for visual coherence at the default base of 1rem.
     Override --space-base in the brand layer to scale the whole
     system proportionally; the relative-gap rhythm is preserved
     because the multipliers are baked into the calcs, not into
     the values.

     Structural and gutter spacing stay as fluid clamp() expressions.
     They're viewport-responsive concerns rather than steps in the
     t-shirt scale and don't derive from --space-base.

     One personality knob (--space-base, in BRAND INPUTS).
     Concentric multiplier rule applied below.
     ============================================================ */
  :root {
    /* Global spacing scale — derived from --space-base. At the
       default base of 1rem, the resolved values are byte-identical
       to the previous hand-baked literals. */
    --space-2xs:   calc(var(--space-base) * 0.25);   /*  4px @ default */
    --space-xs:    calc(var(--space-base) * 0.5);    /*  8px */
    --space-sm:    var(--space-base);                /* 16px (the base position) */
    --space-md:    calc(var(--space-base) * 1.5);    /* 24px */
    --space-lg:    calc(var(--space-base) * 2);      /* 32px */
    --space-xl:    calc(var(--space-base) * 3);      /* 48px */
    --space-2xl:   calc(var(--space-base) * 5);      /* 80px */
    --space-3xl:   calc(var(--space-base) * 7);      /* 112px */

    /* Structural / section spacing (fluid) — viewport-responsive,
       not derived from --space-base. */
    --space-struct-x:        clamp(1rem, 5vw, 15vw);
    --space-struct-y-base:   clamp(4rem, 5vw + 2rem,  6rem);
    --space-struct-y-hero:   clamp(6rem, 8vw + 3rem, 10rem);

    /* Grid gutter (fluid) */
    --space-gutter:          clamp(0.75rem, 2vw, 2rem);
  }


  /* ============================================================
     4. RADIUS — SINGLE-UNIT + CONCENTRIC
     ------------------------------------------------------------
     One personality knob (--radius-unit, in BRAND INPUTS).
     Steps multiply that unit. Components express their own
     --*-radius from these steps; nested children use the
     concentric helper to avoid the "lopsided rounded corner"
     look (parent radius minus padding = child radius).

     Concentric pattern (use inside any component with padding):
       .card {
         --card-radius: var(--radius-4);
         --card-padding: var(--space-md);
         border-radius: var(--card-radius);
         padding: var(--card-padding);
       }
       .card .btn {
         border-radius: max(0px, calc(var(--card-radius) - var(--card-padding)));
       }

     The max(0px, ...) clamps to zero so an inner element never
     gets a negative radius if the parent is small.
     ============================================================ */
  :root {
    --radius-1: calc(var(--radius-unit) * 1);   /*  4px @ default */
    --radius-2: calc(var(--radius-unit) * 2);   /*  8px           */
    --radius-3: calc(var(--radius-unit) * 3);   /* 12px           */
    --radius-4: calc(var(--radius-unit) * 4);   /* 16px           */
    --radius-6: calc(var(--radius-unit) * 6);   /* 24px           */
    --radius-8: calc(var(--radius-unit) * 8);   /* 32px           */

    /* Off-scale (intentional — these aren't part of the personality knob) */
    --radius-none:   0;
    --radius-pill:   9999px;
    --radius-circle: 50%;
  }


  /* ============================================================
     5. COLOUR DERIVATIONS
     ------------------------------------------------------------
     Every variant is derived from --brand-primary / -secondary /
     -accent via `oklch(from ...)`. Brand override only sets the
     three inputs; soft/strong/text/border variants follow.

     Why OKLCH: lightness in OKLCH is perceptually uniform.
     Two colours at L 55% feel equally bright across hues —
     unlike HSL where yellow at 50% feels much brighter than
     blue at 50%. Hover/soft/strong derivations stay coherent
     across rebrand.

     The text-on-* tokens are clamped to fixed lightness values
     (40% for light mode, 80% for dark) which guarantee WCAG AA
     contrast on the corresponding backgrounds, regardless of
     how dark/light the input brand colour is set.
     ============================================================ */
  :root {
    /* — Primary derivations — */
    --brand-primary-soft:    oklch(from var(--brand-primary) 92% calc(c * 0.35) h);
    --brand-primary-strong:  oklch(from var(--brand-primary) 35% calc(c * 1.05) h);
    --brand-primary-text:    light-dark(
                               oklch(from var(--brand-primary) 38% c h),
                               oklch(from var(--brand-primary) 82% c h)
                             );

    /* — Secondary derivations — */
    --brand-secondary-soft:   oklch(from var(--brand-secondary) 88% calc(c * 0.6) h);
    --brand-secondary-strong: oklch(from var(--brand-secondary) 22% c h);
    --brand-secondary-text:   light-dark(
                                oklch(from var(--brand-secondary) 30% c h),
                                oklch(from var(--brand-secondary) 88% c h)
                              );

    /* — Accent derivations — */
    --brand-accent-soft:      oklch(from var(--brand-accent) 92% calc(c * 0.35) h);
    --brand-accent-strong:    oklch(from var(--brand-accent) 35% calc(c * 1.05) h);
    /* Guaranteed-readable accent for text on bg-page. Light mode caps L at 40%
       (dark enough on white). Dark mode caps L at 80% (light enough on near-black). */
    --brand-accent-text:      light-dark(
                                oklch(from var(--brand-accent) 40% c h),
                                oklch(from var(--brand-accent) 80% c h)
                              );

    /* — Translucent variants (replace v2's hsla literals) — */
    --brand-accent-tint-08: color-mix(in oklch, var(--brand-accent)  8%, transparent);
    --brand-accent-tint-12: color-mix(in oklch, var(--brand-accent) 12%, transparent);
    --brand-accent-tint-20: color-mix(in oklch, var(--brand-accent) 20%, transparent);
    --brand-accent-tint-35: color-mix(in oklch, var(--brand-accent) 35%, transparent);
  }


  /* ============================================================
     6. BACKGROUNDS / SURFACES — light-dark()
     ------------------------------------------------------------
     Dark mode ships by default. Every surface and text token uses
     light-dark() — the user's OS preference (or any ancestor with
     `color-scheme: dark`) flips them.

     Dark-mode neutrals are slightly tinted with the brand-primary
     hue (chroma 0.012) so dark mode picks up brand cohesion for
     free, without the brand file having to set any dark tokens.

     Brand overrides:
       Light-only brand:   :root { color-scheme: light; }
       Dark-only brand:    :root { color-scheme: dark; }
       Custom dark colour: override --bg-page-dark etc. directly.
     ============================================================ */
  :root {
    /* Page surfaces */
    --bg-page:        light-dark(
                        #ffffff,
                        oklch(from var(--brand-primary) 14% 0.012 h)
                      );
    --bg-surface:     light-dark(
                        #f9fafb,
                        oklch(from var(--brand-primary) 19% 0.014 h)
                      );
    --bg-surface-alt: light-dark(
                        #f3f4f6,
                        oklch(from var(--brand-primary) 24% 0.016 h)
                      );
    --bg-subtle:      light-dark(
                        oklch(94% 0 0),
                        oklch(from var(--brand-primary) 30% 0.018 h)
                      );

    /* Body + heading text */
    --text-body:    light-dark(oklch(15% 0 0), oklch(95% 0 0));
    --text-heading: light-dark(oklch(10% 0 0), oklch(98% 0 0));
    --text-muted:   light-dark(oklch(45% 0 0), oklch(70% 0 0));

    /* On-brand text (for use directly on --brand-primary fills) */
    --text-on-brand:        light-dark(#ffffff, oklch(98% 0 0));
    --text-on-brand-muted:  color-mix(in oklch, var(--text-on-brand) 70%, transparent);

    /* Aliases (kept for migration ergonomics — point components at the names above) */
    --text-main: var(--text-body);
  }


  /* ============================================================
     7. BORDERS
     ============================================================ */
  :root {
    --border-width-xs: 1px;
    --border-width-sm: 2px;
    --border-width-md: 4px;

    --border-subtle: light-dark(oklch(92% 0 0), oklch(28% 0 0));
    --border-medium: light-dark(oklch(82% 0 0), oklch(40% 0 0));
    --border-strong: light-dark(oklch(60% 0 0), oklch(60% 0 0));
  }


  /* ============================================================
     8. SHADOWS
     ------------------------------------------------------------
     Two scales: component (cards, modals) and button (tighter
     spread). Dark mode uses higher alpha because shadows are
     less visible on dark surfaces.
     ============================================================ */
  :root {
    --shadow-color-light: 0 0 0;
    --shadow-color-dark:  0 0 0;

    --shadow-none: none;
    --shadow-xs:   0 1px 2px  light-dark(rgb(0 0 0 / 0.05), rgb(0 0 0 / 0.4));
    --shadow-sm:   0 1px 3px  light-dark(rgb(0 0 0 / 0.08), rgb(0 0 0 / 0.5)),
                   0 1px 2px  light-dark(rgb(0 0 0 / 0.04), rgb(0 0 0 / 0.3));
    --shadow-md:   0 4px 12px light-dark(rgb(0 0 0 / 0.08), rgb(0 0 0 / 0.5)),
                   0 1px 3px  light-dark(rgb(0 0 0 / 0.06), rgb(0 0 0 / 0.4));
    --shadow-lg:   0 12px 32px light-dark(rgb(0 0 0 / 0.10), rgb(0 0 0 / 0.6)),
                   0 4px 12px  light-dark(rgb(0 0 0 / 0.06), rgb(0 0 0 / 0.4));
    --shadow-xl:   0 20px 48px light-dark(rgb(0 0 0 / 0.12), rgb(0 0 0 / 0.7)),
                   0 8px 20px  light-dark(rgb(0 0 0 / 0.06), rgb(0 0 0 / 0.4));

    --btn-shadow-sm: 0 1px 2px light-dark(rgb(0 0 0 / 0.06), rgb(0 0 0 / 0.4));
    --btn-shadow-md: 0 2px 6px light-dark(rgb(0 0 0 / 0.08), rgb(0 0 0 / 0.5));
    --btn-shadow-lg: 0 4px 10px light-dark(rgb(0 0 0 / 0.10), rgb(0 0 0 / 0.5));
  }


  /* ============================================================
     9. MOTION
     ------------------------------------------------------------
     Named easings + a duration scale. Components reference these
     by name, never by raw bezier — so a brand can change "feel"
     in one place (e.g. --ease-out → spring for a playful brand).
     ============================================================ */
  :root {
    /* Durations */
    --duration-instant: 100ms;
    --duration-fast:    150ms;
    --duration-base:    250ms;
    --duration-slow:    400ms;
    --duration-slower:  600ms;

    /* Easings */
    --ease-linear:    linear;
    --ease-out:       cubic-bezier(0.22, 1, 0.36, 1);          /* default for entries */
    --ease-in:        cubic-bezier(0.55, 0, 1, 0.45);          /* default for exits   */
    --ease-in-out:    cubic-bezier(0.65, 0, 0.35, 1);
    --ease-spring:    cubic-bezier(0.5, 1.5, 0.5, 1);          /* gentle overshoot    */
    --ease-back:      cubic-bezier(0.34, 1.56, 0.64, 1);       /* stronger overshoot  */

    /* Composed transition shortcuts — components opt in by property */
    --transition-base:  var(--duration-base) var(--ease-out);
    --transition-fast:  var(--duration-fast) var(--ease-out);
    --transition-slow:  var(--duration-slow) var(--ease-out);
  }


  /* ============================================================
    10. Z-INDEX SCALE
     ------------------------------------------------------------
     Documented stacking order. Never invent z-indexes at the
     call site — pick one of these and the layering stays sane
     across the whole app.
     ============================================================ */
  :root {
    --z-below:    -1;
    --z-base:      0;
    --z-raised:    1;     /* hover lifts, lifted cards         */
    --z-dropdown:  100;   /* dropdowns, comboboxes             */
    --z-sticky:    200;   /* sticky page nav                   */
    --z-fixed:     300;   /* fixed headers, mobile menu        */
    --z-overlay:   400;   /* modal backdrops                   */
    --z-modal:     500;   /* modals, dialogs                   */
    --z-popover:   600;   /* tooltips, popovers, mega menus    */
    --z-toast:     700;   /* notifications                     */
    --z-skip:      1000;  /* skip-to-content always wins       */
  }


  /* ============================================================
    11. BREAKPOINTS — REFERENCE ONLY
     ------------------------------------------------------------
     CSS custom properties cannot be used inside @media queries.
     Until @custom-media or the env() proposal lands, the
     breakpoints below are documented literals. Keep these in
     sync with any @media (max-width: ...) in this file.

     Standard breakpoints:
       --bp-xs:  20rem   (320px)   smallest target
       --bp-sm:  40rem   (640px)   phone landscape
       --bp-md:  48rem   (768px)   tablet portrait
       --bp-lg:  64rem   (1024px)  tablet landscape / small laptop
       --bp-xl:  80rem   (1280px)  desktop
       --bp-2xl: 90rem   (1440px)  wide desktop

     Container query equivalents (for cards, components):
       --cq-sm:  20rem   (320px)   compact card
       --cq-md:  28rem   (448px)   standard card
       --cq-lg:  40rem   (640px)   wide card / horizontal layout

     Container queries DO accept var() in their bounds, but only
     via container-name + size queries — so cards reflow based on
     their own container, not the viewport. That's the right model
     and Phase 2 wires it up per-component.
     ============================================================ */


  /* ============================================================
    12. STATES / FEEDBACK
     ------------------------------------------------------------
     Static colours — not derived from brand. Success/warning/
     error/info read consistently across all brands.
     ============================================================ */
  :root {
    --state-success:       light-dark(oklch(58% 0.18 145), oklch(72% 0.18 145));
    --state-success-soft:  light-dark(oklch(95% 0.05 145), oklch(28% 0.08 145));
    --state-success-text:  light-dark(oklch(35% 0.15 145), oklch(85% 0.10 145));

    --state-warning:       light-dark(oklch(75% 0.16 75),  oklch(82% 0.16 75));
    --state-warning-soft:  light-dark(oklch(97% 0.04 75),  oklch(28% 0.08 75));
    --state-warning-text:  light-dark(oklch(45% 0.13 75),  oklch(88% 0.10 75));

    --state-error:         light-dark(oklch(58% 0.22 25),  oklch(68% 0.22 25));
    --state-error-soft:    light-dark(oklch(95% 0.05 25),  oklch(28% 0.10 25));
    --state-error-text:    light-dark(oklch(38% 0.18 25),  oklch(85% 0.12 25));

    --state-info:          light-dark(oklch(58% 0.18 250), oklch(72% 0.18 250));
    --state-info-soft:     light-dark(oklch(96% 0.04 250), oklch(28% 0.08 250));
    --state-info-text:     light-dark(oklch(38% 0.15 250), oklch(88% 0.10 250));
  }


  /* ============================================================
    13. FOCUS RING
     ------------------------------------------------------------
     2px outline at brand-accent. Dark-mode auto-flips via
     light-dark() on the colour, so no on-dark variant needed.
     ============================================================ */
  :root {
    --focus-ring-color:  var(--brand-accent);
    --focus-ring-width:  2px;
    --focus-ring-offset: 2px;
  }


  /* ============================================================
    14. FORM TOKENS
     ------------------------------------------------------------
     Tokens only — component CSS lives in Phase 2.
     ============================================================ */
  :root {
    --form-bg:                light-dark(#ffffff, oklch(20% 0.005 0));
    --form-text:              var(--text-body);
    --form-placeholder:       var(--text-muted);
    --form-border:            var(--border-subtle);
    --form-border-hover:      var(--border-medium);
    --form-border-focus:      var(--brand-accent);
    --form-border-error:      var(--state-error);
    --form-radius:            var(--radius-2);
    --form-padding-y:         0.65em;
    --form-padding-x:         0.85em;
    --form-font-size:         var(--text-1);

    --form-select-arrow-size:    1rem;
    --form-select-arrow-offset:  0.75rem;
    --form-select-padding-end:   2.5rem;
  }


  /* ============================================================
    15. BUTTON TOKENS
     ------------------------------------------------------------
     Every button style derives from --brand-accent via OKLCH
     and color-mix. Brand override sets --brand-accent — primary,
     tint, outline, white-* variants all rebrand automatically.
     ============================================================ */
  :root {
    /* Sizing */
    --btn-radius:        var(--radius-1);
    --btn-radius-sm:     var(--radius-1);  /* small buttons can scale down for proportion */
    --btn-font-size:     clamp(0.9rem, 0.867rem + 0.167vw, 1rem);
    --btn-font-weight:   var(--weight-bold);
    --btn-ls:            0.01em;
    --btn-lh:            1;

    --btn-padding-y:     0.8em;
    --btn-padding-x:     1.6em;
    --btn-padding-icon:  2.5em;   /* end-side that holds the icon */
    --btn-padding-bal:   1.2em;   /* opposite end-side, for balance */

    --btn-padding-y-sm:    0.6em;
    --btn-padding-x-sm:    1.4em;
    --btn-padding-icon-sm: 2.2em;
    --btn-padding-bal-sm:  1em;

    --btn-icon-nudge:    3px;     /* hover slide distance */

    /* — Button icon glyphs (added v3.7.0) —
       SVG data-URIs masked in currentColor on the button's
       ::before / ::after. Default shapes are arrow-right (end) and
       arrow-left (start) — the master's v3.0–v3.6 baked-in arrows.
       Override per-brand (`@layer brand { :root { --btn-icon-end: …; }`)
       or per-button-instance (`<button style="--btn-icon-end: …">`)
       to swap to a chevron, plus, x, custom glyph, etc. The icon
       inherits the button's text colour automatically (mask + currentColor)
       so brand overrides don't need to think about palettes. */
    --btn-icon-end:   url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24' fill='none' stroke='%23000' stroke-width='1.5' stroke-linecap='round' stroke-linejoin='miter'%3E%3Cline x1='5' y1='12' x2='19' y2='12'/%3E%3Cpolyline points='12 5 19 12 12 19'/%3E%3C/svg%3E");
    --btn-icon-start: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24' fill='none' stroke='%23000' stroke-width='1.5' stroke-linecap='round' stroke-linejoin='miter'%3E%3Cline x1='19' y1='12' x2='5' y2='12'/%3E%3Cpolyline points='12 19 5 12 12 5'/%3E%3C/svg%3E");

    /* — Primary (solid, brand fill) — */
    --btn-primary-bg:        var(--brand-accent);
    --btn-primary-bg-hover:  oklch(from var(--brand-accent) calc(l - 0.08) c h);
    --btn-primary-text:      var(--text-on-brand);
    --btn-primary-border:    var(--btn-primary-bg);
    --btn-primary-border-hover: var(--btn-primary-bg-hover);

    /* — Tint (translucent brand) — */
    --btn-tint-bg:           var(--brand-accent-tint-20);
    --btn-tint-bg-hover:     var(--brand-accent-tint-12);
    --btn-tint-text:         var(--brand-accent-text);
    --btn-tint-border:       transparent;
    --btn-tint-border-hover: transparent;

    /* — Outline (bordered, transparent fill) — */
    --btn-outline-bg:           transparent;
    --btn-outline-bg-hover:     var(--brand-accent-tint-08);
    --btn-outline-text:         var(--brand-accent-text);
    --btn-outline-border:       var(--brand-accent-tint-35);
    --btn-outline-border-hover: var(--brand-accent);
    --btn-outline-border-width: var(--border-width-xs);

    /* — White variants (for use ON brand backgrounds, e.g. inside hero with --brand-accent fill) — */
    --btn-white-primary-bg:       #ffffff;
    --btn-white-primary-bg-hover: oklch(98% 0 0);
    --btn-white-primary-text:     var(--brand-accent-strong);

    --btn-white-tint-bg:          color-mix(in oklch, white 40%, transparent);
    --btn-white-tint-bg-hover:    color-mix(in oklch, white 25%, transparent);
    --btn-white-tint-text:        #ffffff;

    --btn-white-outline-bg:           transparent;
    --btn-white-outline-bg-hover:     color-mix(in oklch, white 12%, transparent);
    --btn-white-outline-text:         #ffffff;
    --btn-white-outline-border:       color-mix(in oklch, white 60%, transparent);
    --btn-white-outline-border-hover: #ffffff;

    /* — Text (no fill, no border) — */
    --btn-text-color:        var(--brand-accent-text);
    --btn-text-color-hover:  var(--brand-accent-strong);
    --btn-text-bg-hover:     var(--brand-accent-tint-08);

    /* — Disabled — */
    --btn-disabled-bg:    light-dark(oklch(92% 0 0), oklch(30% 0 0));
    --btn-disabled-text:  light-dark(oklch(65% 0 0), oklch(50% 0 0));
  }


  /* ============================================================
    16. ACCESSIBILITY OVERRIDES
     ------------------------------------------------------------
     Honour user contrast preferences. WCAG 2.2 expectation.
     Forced-colours mode (Windows High Contrast) lets the OS
     override colours — we only insist on system-coloured borders
     so structure remains visible.
     ============================================================ */
  @media (prefers-contrast: more) {
    :root {
      --border-subtle:       var(--border-strong);
      --border-medium:       var(--border-strong);
      --text-muted:          var(--text-body);
      --brand-accent-text:   var(--brand-accent-strong);
      --btn-outline-border:  var(--brand-accent);
    }
  }

  @media (forced-colors: active) {
    :root {
      --border-subtle: CanvasText;
      --border-medium: CanvasText;
    }
  }
}


/* ================================================================================
   17. FOCUS-VISIBLE — GLOBAL RULE
   --------------------------------------------------------------------------------
   Keyboard focus only. Mouse/touch never get the ring.
   Outline (not box-shadow) — renders consistently on every element type.
   Dark mode auto-handled because --brand-accent is mode-agnostic.
   ================================================================================ */
@layer base {
  :focus-visible {
    outline: var(--focus-ring-width) solid var(--focus-ring-color);
    outline-offset: var(--focus-ring-offset);
  }

  :focus:not(:focus-visible) {
    outline: none;
  }
}


/* ================================================================================
   18. BUTTONS
   --------------------------------------------------------------------------------
   The .btn family — worked example demonstrating every v3 pattern:
     • Logical properties (padding-block / padding-inline / margin-inline)
     • Logical icon naming (.has-icon-start / .has-icon-end / .has-icon-hover)
     • OKLCH-derived hover states
     • color-mix() tints
     • Motion tokens (named easings)
     • light-dark() text/bg
     • Concentric radii (.btn--pill modifier)

   STRUCTURE
     .btn                – base (font, cursor, transition, radius)
     .btn--std           – standard size
     .btn--sm            – small size
     .btn--{style}       – primary / tint / outline / text / disabled / white-*
     .btn--pill          – overrides radius to pill shape
     .has-icon-start     – persistent left/start arrow
     .has-icon-end       – persistent right/end arrow
     .has-icon-hover     – right/end arrow that fades in on hover

   Example:
     <a class="btn btn--std btn--primary has-icon-hover">Get started</a>

   ICON MODEL
     Icons are inline SVG data URIs masked with currentColor. Rendered via
     ::before (start side) or ::after (end side). They sit absolutely, so they
     don't push text — instead, asymmetric padding makes room. Without an
     icon class, padding is symmetric. With one, the icon side gets
     --btn-padding-icon and the opposite side gets --btn-padding-bal.

   Naming change vs v2:
     .has-icon-left  → .has-icon-start
     .has-icon-right → .has-icon-end
   (Logical properties — works automatically in RTL.)
   ================================================================================ */
@layer components {

  /* ── BASE ──
     Default is .btn--std sizing baked in. .btn--std modifier kept for
     backward compat / explicit clarity but is now a no-op. .btn--sm
     overrides for compact contexts.

     align-self: start prevents flex/grid contexts (like a column-flex
     card body) from stretching buttons to fill cross-axis. Buttons
     are inline-block conceptually — they should size to their content
     unless explicitly told otherwise. */
  .btn {
    position: relative;
    display: inline-block;
    align-self: start;
    font-family: inherit;
    font-size: var(--btn-font-size);
    font-weight: var(--btn-font-weight);
    letter-spacing: var(--btn-ls);
    line-height: var(--btn-lh);
    text-decoration: none;
    cursor: pointer;
    border-style: solid;
    border-width: var(--border-width-xs);
    border-radius: var(--btn-radius);
    padding-block: var(--btn-padding-y);
    padding-inline: var(--btn-padding-x);
    transition:
      background-color  var(--transition-base),
      border-color      var(--transition-base),
      color             var(--transition-base),
      padding           var(--transition-base);
    white-space: nowrap;
  }
  .btn.has-icon-end   { padding-inline-end: var(--btn-padding-icon); padding-inline-start: var(--btn-padding-bal); }
  .btn.has-icon-start { padding-inline-start: var(--btn-padding-icon); padding-inline-end: var(--btn-padding-bal); }
  .btn.has-icon-hover:hover { padding-inline-end: var(--btn-padding-icon); padding-inline-start: var(--btn-padding-bal); }

  /* ── SIZE: STANDARD (no-op — kept for explicit markup readability) ── */
  .btn--std { /* same as .btn defaults */ }

  /* ── SIZE: SMALL ── */
  .btn--sm {
    padding-block: var(--btn-padding-y-sm);
    padding-inline: var(--btn-padding-x-sm);
    font-size: calc(var(--btn-font-size) * 0.9);
    border-radius: var(--btn-radius-sm);
  }
  .btn--sm.has-icon-end   { padding-inline-end: var(--btn-padding-icon-sm); padding-inline-start: var(--btn-padding-bal-sm); }
  .btn--sm.has-icon-start { padding-inline-start: var(--btn-padding-icon-sm); padding-inline-end: var(--btn-padding-bal-sm); }
  .btn--sm.has-icon-hover:hover { padding-inline-end: var(--btn-padding-icon-sm); padding-inline-start: var(--btn-padding-bal-sm); }

  /* ── SHAPE: PILL ── */
  .btn--pill { border-radius: var(--radius-pill); }

  /* ── BLOCK: full-width on mobile ──
     Opt-in modifier for buttons that should fill their container at
     mobile sizes — most commonly form submit buttons and prominent
     page CTAs. Stays content-width above the mobile breakpoint.

     Example:
       <a class="btn btn--primary btn--block">Submit</a>
       <button class="btn btn--primary btn--block">Get in touch</button>

     If you want full-width at ALL viewport sizes (rare — usually only
     for checkout flows or single-action mobile-first apps), use both
     modifiers + a custom selector, or override via inline style. */
  @media (max-width: 640px) /* --bp-sm */ {
    .btn--block {
      display: block;
      align-self: stretch;
      inline-size: 100%;
      text-align: center;
    }
  }

  /* ── ICONS: END (::after) ── */
  .btn.has-icon-end::after,
  .btn.has-icon-hover::after {
    content: "";
    display: block;
    position: absolute;
    inset-block-start: 50%;
    inset-inline-end: calc(var(--btn-padding-x) * 0.5);
    transform: translateY(-50%);
    inline-size: 1em;
    block-size: 1em;
    background: currentColor;
    /* Icon glyph from --btn-icon-end token. Defaults to arrow-right;
       override per-brand or per-button-instance to swap (see token
       comment at Section 18 and DECISIONS-v3.md → "Why button +
       action-link icons are tokenised"). */
    -webkit-mask-image: var(--btn-icon-end);
            mask-image: var(--btn-icon-end);
    -webkit-mask-size: contain;
            mask-size: contain;
    -webkit-mask-repeat: no-repeat;
            mask-repeat: no-repeat;
    transition:
      opacity   var(--transition-base),
      transform var(--transition-base);
  }
  .btn.has-icon-end::after        { opacity: 1; }
  .btn.has-icon-end:hover::after  { transform: translateY(-50%) translateX(var(--btn-icon-nudge)); }

  /* ── ICONS: START (::before) ── */
  .btn.has-icon-start::before {
    content: "";
    display: block;
    position: absolute;
    inset-block-start: 50%;
    inset-inline-start: calc(var(--btn-padding-x) * 0.5);
    transform: translateY(-50%);
    inline-size: 1em;
    block-size: 1em;
    background: currentColor;
    /* Icon glyph from --btn-icon-start token. Defaults to arrow-left;
       override per-brand or per-button-instance to swap. */
    -webkit-mask-image: var(--btn-icon-start);
            mask-image: var(--btn-icon-start);
    -webkit-mask-size: contain;
            mask-size: contain;
    -webkit-mask-repeat: no-repeat;
            mask-repeat: no-repeat;
    transition:
      opacity   var(--transition-base),
      transform var(--transition-base);
  }
  .btn.has-icon-start::before       { opacity: 1; }
  .btn.has-icon-start:hover::before { transform: translateY(-50%) translateX(calc(var(--btn-icon-nudge) * -1)); }

  /* ── ICONS: HOVER-REVEAL (end-side, hidden at rest) ── */
  .btn.has-icon-hover::after        { opacity: 0; }
  .btn.has-icon-hover:hover::after  { opacity: 1; transform: translateY(-50%) translateX(var(--btn-icon-nudge)); }

  /* Small-size icon positioning */
  .btn--sm.has-icon-end::after,
  .btn--sm.has-icon-hover::after  { inset-inline-end: calc(var(--btn-padding-x-sm) * 0.5); }
  .btn--sm.has-icon-start::before { inset-inline-start: calc(var(--btn-padding-x-sm) * 0.5); }

  /* ── STYLE: PRIMARY ── */
  .btn--primary {
    background-color: var(--btn-primary-bg);
    color: var(--btn-primary-text);
    border-color: var(--btn-primary-border);
  }
  .btn--primary:hover {
    background-color: var(--btn-primary-bg-hover);
    border-color: var(--btn-primary-border-hover);
  }

  /* ── STYLE: TINT ── */
  .btn--tint {
    background-color: var(--btn-tint-bg);
    color: var(--btn-tint-text);
    border-color: var(--btn-tint-border);
  }
  .btn--tint:hover {
    background-color: var(--btn-tint-bg-hover);
    border-color: var(--btn-tint-border-hover);
  }

  /* ── STYLE: OUTLINE ── */
  .btn--outline {
    background-color: var(--btn-outline-bg);
    color: var(--btn-outline-text);
    border-color: var(--btn-outline-border);
    border-width: var(--btn-outline-border-width);
  }
  .btn--outline:hover {
    background-color: var(--btn-outline-bg-hover);
    border-color: var(--btn-outline-border-hover);
  }

  /* ── STYLE: WHITE PRIMARY (for brand backgrounds) ── */
  .btn--white-primary {
    background-color: var(--btn-white-primary-bg);
    color: var(--btn-white-primary-text);
    border-color: var(--btn-white-primary-bg);
  }
  .btn--white-primary:hover {
    background-color: var(--btn-white-primary-bg-hover);
    border-color: var(--btn-white-primary-bg-hover);
    opacity: 0.9;
  }

  /* ── STYLE: WHITE TINT (for brand backgrounds) ── */
  .btn--white-tint {
    background-color: var(--btn-white-tint-bg);
    color: var(--btn-white-tint-text);
    border-color: transparent;
  }
  .btn--white-tint:hover {
    background-color: var(--btn-white-tint-bg-hover);
  }

  /* ── STYLE: WHITE OUTLINE (for brand backgrounds) ── */
  .btn--white-outline {
    background-color: var(--btn-white-outline-bg);
    color: var(--btn-white-outline-text);
    border-color: var(--btn-white-outline-border);
    border-width: var(--btn-outline-border-width);
  }
  .btn--white-outline:hover {
    background-color: var(--btn-white-outline-bg-hover);
    border-color: var(--btn-white-outline-border-hover);
  }

  /* ── STYLE: TEXT (minimal — no fill, no border) ──
     Belongs in button rows where a bordered button reads too heavy
     (modal secondary action, dense action toolbar). For inline /
     prose / column-end CTAs (the "Read more →" pattern), reach for
     .link-action (Section 19) — a different atom, not a button.

     Block axis: padding-block inherited from .btn / .btn--sm so the
     button's height matches its filled siblings exactly. With
     align-items: center on the parent flex row, text-Y aligns
     automatically — no negative-margin tricks needed. Border-radius
     also inherited from .btn for the same reason: concentric with
     adjacent filled buttons.

     Inline axis: padding-inline: 0 at REST so the text sits flush
     with the button frame's content edge (no hidden bg-paint zone
     extending past it). On hover, padding-inline snaps in to a
     symmetric 0.75em — deliberately tighter than a filled button's
     full padding so the hover bg reads as "snaps around the text,"
     not "inflates into a full button." The snap-in is the signature
     animation of the text-button pattern.

     The .btn base already declares `transition: ... padding ...`,
     so padding-inline rest→hover animates smoothly.

     History: pre-v3.3.1 this rule used padding-block 0.5em +
     margin-block -0.5em + border-radius --radius-2. Those
     overrides were trying to make .btn--text also work in prose
     flow (kept the layout box at text-height to avoid bumping
     line-height). That dual mandate caused .btn--text to misalign
     with filled buttons in action rows by ~0.8em on the block
     axis. v3.3.0 restored .link-action for the prose-flow case;
     v3.3.1 dropped the overrides here so action-row alignment
     works by construction. See AUDIT-NOTES.md v3.3.1 entry. */
  .btn--text {
    background-color: transparent;
    color: var(--btn-text-color);
    border-color: transparent;
    padding-inline: 0;
  }
  /* Icon-end variant: icon is visible at REST, so we need permanent
     padding-inline-end to make room for it (icon is absolutely-
     positioned via inset-inline-end: 0.5em from the button's right
     edge). Only padding-inline-start grows on hover — that pushes
     text rightward into the button frame, keeping icon position
     stable relative to the button's right edge. */
  .btn--text.has-icon-end,
  .btn--text.has-icon-hover {
    padding-inline-start: 0;
    padding-inline-end:   2em;       /* room for the 1em icon + breathing room */
  }
  /* Icon position adjusts inward to match the smaller padding-end. */
  .btn--text.has-icon-end::after,
  .btn--text.has-icon-hover::after {
    inset-inline-end: 0.5em;
  }

  .btn--text:hover {
    /* Symmetric 0.75em both sides. Total inline padding (1.5em)
       is the same as the pre-3.3.2 asymmetric 0.6em / 0.9em — just
       balanced. The 0.6/0.9 leftover came from when text buttons
       were "growing rightward into empty space" at the trailing
       end of left-aligned columns; .btn--text now lives in
       button rows alongside filled buttons where asymmetric
       hover-padding read as a lopsided pill. */
    padding-inline: 0.75em;
    color: var(--btn-text-color-hover);
    background-color: var(--btn-text-bg-hover);
  }
  /* On hover with an end-icon, padding-inline-start grows to match
     the base hover (0.75em); padding-inline-end stays at 2em so the
     absolutely-positioned icon's offset from the button's right
     edge is unchanged through the rest→hover transition. */
  .btn--text.has-icon-end:hover,
  .btn--text.has-icon-hover:hover {
    padding-inline-start: 0.75em;
    padding-inline-end:   2em;
  }

  /* ── STYLE: DISABLED ── three triggers, one rule
     - .btn--disabled       (explicit modifier class)
     - :disabled            (native attribute)
     - [aria-disabled]      (ARIA pattern for non-button elements like <a>)
     Combined so the disabled appearance is consistent regardless of
     which trigger applies. Including hover/focus pseudo-classes on
     the modifier so a JS toggling .btn--disabled mid-interaction
     stays disabled-looking. */
  .btn--disabled,
  .btn--disabled:hover,
  .btn--disabled:focus,
  .btn:disabled,
  .btn[aria-disabled="true"] {
    background-color: var(--btn-disabled-bg);
    color: var(--btn-disabled-text);
    border-color: var(--btn-disabled-bg);
    cursor: not-allowed;
    pointer-events: none;
    opacity: 0.7;
  }
}


/* ================================================================================
   V. VENDOR — DIVI RESET (DORMANT ON NON-DIVI SITES)
   --------------------------------------------------------------------------------
   Selectors only match if Divi is loaded. On non-Divi projects this block is
   inert text — no specificity cost, no rendering cost. Kept as a safety hedge
   in case a project ever drops back to Divi. Sits in @layer vendor (below
   components) so it can never accidentally undercut a v3 component style.
   ================================================================================ */
@layer vendor {
  .et_pb_button.btn {
    background: none;
    color: inherit;
    border: none;
    padding: 0;
    font-size: inherit;
    font-weight: inherit;
    letter-spacing: inherit;
    line-height: inherit;
    text-transform: none;
    border-radius: var(--radius-none);
  }
  .et_pb_button.btn::after,
  .et_pb_button.btn::before {
    display: none;
  }

  /* Force Divi rows inside Post Content to span full width.
     Note: !important is fine here — vendor layer + only fires on Divi pages. */
  .et-db #et-boc .et-l .et-l--post .et_builder_inner_content .et_pb_row {
    inline-size: 100% !important;
  }
}


/* ================================================================================
   ████████████████████████████████████████████████████████████████████████████████
   ATOMS + UTILITIES
   ████████████████████████████████████████████████████████████████████████████████

   Sections 19–39. Component CSS lives in @layer components, utility classes
   in @layer utilities. Patterns used throughout this group:

     • Logical properties throughout (margin-inline-*, padding-block-*, etc.)
     • :user-invalid for live form validation (no JS error class needed)
     • Container queries on the card grid
     • :has() for declarative card variants (image vs. no-image, with-link)
     • Concentric radii on inner buttons (parent-radius − padding)
     • color-scheme scoping on surfaces (.surface--dark { color-scheme: dark; }
       auto-flips every light-dark() inside the subtree)
     • color-mix() everywhere translucent colour was hsla() in v2

   ================================================================================ */


/* ================================================================================
   19. LINKS — INLINE + ACTION
   --------------------------------------------------------------------------------
   v3.3+ has TWO link atoms (plus a niche button-family member for completeness):

     .link-inline  → within paragraphs. Subtle underline at rest, full on hover.
     .link-action  → standalone CTA ("Read more →"). Link-coloured + bold, with
                     an arrow that nudges on hover. Sits inline / at column-end /
                     in card footers without bumping line-height.
     .btn--text    → button-shaped, fill-free CTA. Belongs to the .btn family
                     (Section 18). Use only inside button rows where a bordered
                     button reads too heavy (e.g. modal secondary action). NOT
                     for "Read more"-style CTAs — that's .link-action's job.

   Story: .link-action shipped in v3.0, was retired in v3.1 (the choice between
   it and .btn--text felt arbitrary), and was restored in v3.3 once the cost of
   the conflation became clear. .btn--text was being asked to do two distinct
   jobs at once — slot into prose flow without bumping line-height (the link
   case) AND match filled buttons in height for action-row alignment (the
   button case). Negative-margin tricks couldn't honour both.

   Splitting the atoms by use case dissolves the tension: action links live in
   prose / column-flow contexts where they need to feel weightless; .btn--text
   lives in button rows where it's already height-matched. See DECISIONS-v3.md
   → "Why .link-action is its own atom" for the full rationale.
   ================================================================================ */
@layer tokens {
  :root {
    --link-color:           var(--brand-accent-text);
    --link-color-hover:     var(--brand-accent-strong);
    --link-color-visited:   oklch(from var(--brand-accent) calc(l - 0.05) calc(c * 0.6) calc(h + 25));
    --link-underline-rest:  color-mix(in oklch, var(--brand-accent) 30%, transparent);
    --link-underline-hover: var(--brand-accent);

    /* Action link — same brand colour as inline link by default. Override
       in the brand layer if a project wants a louder action-link colour
       (a common move: pin action links to --brand-primary while inline
       links stay on --brand-accent). */
    --link-action-color:          var(--link-color);
    --link-action-color-hover:    var(--link-color-hover);
    --link-action-gap:            0.3em;
    --link-action-arrow-nudge:    0.25em;
    --link-action-tracking-hover: 0.04em;

    /* Action-link icon glyph (added v3.7.0). Defaults to the same
       arrow-right as --btn-icon-end but lives as its own token so a
       brand can swap the action-link arrow independently of buttons
       (a common move: keep buttons on the default arrow, switch
       action-links to a chevron). Override in @layer brand. */
    --link-action-icon: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24' fill='none' stroke='%23000' stroke-width='1.5' stroke-linecap='round' stroke-linejoin='miter'%3E%3Cline x1='5' y1='12' x2='19' y2='12'/%3E%3Cpolyline points='12 5 19 12 12 19'/%3E%3C/svg%3E");
  }
}

@layer components {
  /* ── INLINE LINK ── */
  .link-inline {
    color: var(--link-color);
    text-decoration: underline;
    text-decoration-color: var(--link-underline-rest);
    text-underline-offset: 0.15em;
    transition:
      color                  var(--transition-base),
      text-decoration-color  var(--transition-base);
  }
  .link-inline:hover {
    color: var(--link-color-hover);
    text-decoration-color: var(--link-underline-hover);
  }
  .link-inline:visited {
    color: var(--link-color-visited);
  }

  /* ── ACTION LINK ──
     Standalone CTA. Link-coloured, bold, with a trailing arrow that
     nudges right on hover. Letter-spacing opens 0.04em on hover — a
     small anticipation gesture without moving the element itself.

     Why inline-flex: lets the arrow sit on the same baseline as the
     text and gives gap-based spacing instead of ::after positioning
     hacks. align-items: center keeps the arrow visually-centred on
     cap-height even at large sizes.

     Why no padding / no negative margin: this is a LINK, not a button.
     It belongs in prose, in column stacks, in card footers — anywhere
     the parent flow already provides spacing. Padding-block would
     bump line-height in inline contexts; both padding and negative-
     margin tricks are .btn--text's territory.

     Arrow: SVG mask painted in currentColor (matches the .btn icon
     family for visual consistency). Auto-flips in RTL via :dir(rtl).
     Empty alt (`/ ""`) on the arrow content tells screen readers to
     skip it — the link's text already announces the action.

     On dark surfaces: --link-action-color resolves through the brand
     tokens which already do their own light/dark via light-dark().
     No component-level on-dark override needed — the master's
     color-scheme discipline handles it. */
  .link-action {
    color: var(--link-action-color);
    text-decoration: none;
    font-weight: var(--weight-semibold);
    display: inline-flex;
    align-items: center;
    gap: var(--link-action-gap);
    letter-spacing: normal;
    transition:
      color           var(--transition-base),
      letter-spacing  var(--transition-base);
  }
  .link-action::after {
    content: "";
    display: inline-block;
    inline-size: 1em;
    block-size: 1em;
    background: currentColor;
    /* Icon glyph from --link-action-icon token. Defaults to arrow-
       right; override in @layer brand or per-instance via inline
       `style="--link-action-icon: url(...);"`. */
    -webkit-mask-image: var(--link-action-icon);
            mask-image: var(--link-action-icon);
    -webkit-mask-size: contain;
            mask-size: contain;
    -webkit-mask-repeat: no-repeat;
            mask-repeat: no-repeat;
    transition: transform var(--transition-base);
  }
  :dir(rtl) .link-action::after {
    transform: scaleX(-1);
  }
  .link-action:hover {
    color: var(--link-action-color-hover);
    letter-spacing: var(--link-action-tracking-hover);
  }
  .link-action:hover::after {
    transform: translateX(var(--link-action-arrow-nudge));
  }
  :dir(rtl) .link-action:hover::after {
    transform: translateX(calc(var(--link-action-arrow-nudge) * -1)) scaleX(-1);
  }

  /* ── ACTION LINK SIZE VARIANTS ──
     Default sits at body size (inherits parent font-size). --sm
     tightens for card footers and dense rows. --lg amplifies for
     hero / marketing prominent CTAs and uses bold weight to match
     the larger size's visual gravity. */
  .link-action--sm { font-size: var(--text-_1); }
  .link-action--lg {
    font-size: var(--text-1);
    font-weight: var(--weight-bold);
  }
}


/* ================================================================================
   20. UTILITY TEXT — .eyebrow (canonical) + aliases + .lede + .subheading + .micro
   --------------------------------------------------------------------------------
   Eyebrow naming: v3 standardises on .eyebrow. Two legacy aliases preserved
   (.signpost and .preheader) so existing template muscle-memory keeps working.
   Both aliases map onto the new canonical scale:

     .eyebrow         (canonical, default size — section-level, above h2/h3)
     .eyebrow--lg     (larger — hero-level, above h1)
     .eyebrow--sm     (smaller — sub-section, above h4/h5)

     .signpost        → alias of .eyebrow
     .preheader       → alias of .eyebrow--lg

   Pick `.eyebrow` for new templates. The aliases stay forever but new code
   should use the canonical name.
   ================================================================================ */
@layer components {

  /* ── BASE eyebrow + aliases ── */
  .eyebrow,
  .signpost,
  .preheader {
    font-family: var(--type-eyebrow);
    font-weight: var(--weight-bold);
    line-height: var(--leading-snug);
    letter-spacing: var(--tracking-eyebrow);
    text-transform: uppercase;
    margin: 0 0 var(--space-2xs);
    color: var(--text-muted);
  }

  /* ── SIZES ── */
  .eyebrow,
  .signpost {
    font-size: var(--text-eyebrow);  /* default — section level */
  }
  .eyebrow--sm {
    font-size: var(--text-_1);
  }
  .eyebrow--lg,
  .preheader {
    font-size: var(--text-1);
    color: var(--brand-secondary-text);
    margin-block-end: var(--space-xs);
  }

  /* ── Kill heading margin-top after eyebrow (prevents margin collapse from looking lopsided) ── */
  .eyebrow + h1,  .eyebrow + h2,  .eyebrow + h3,  .eyebrow + h4,
  .signpost + h1, .signpost + h2, .signpost + h3, .signpost + h4,
  .preheader + h1, .preheader + h2, .preheader + h3, .preheader + h4 {
    margin-block-start: 0;
  }

  /* ── HERO SUBHEAD — large supporting line under h1 ── */
  .hero-subhead {
    font-size: var(--text-display-body);
    font-weight: var(--weight-regular);
    line-height: var(--leading-snug);
  }

  /* ── SUBHEADING — supporting line under h2/h3 ── */
  .subheading {
    font-size: var(--text-2);
    font-weight: var(--weight-regular);
    line-height: var(--leading-normal);
  }

  /* ── LEDE — opening paragraph treatment ── */
  .lede {
    font-size: var(--text-prose-lede);
    font-weight: var(--weight-regular);
    line-height: var(--leading-snug);
  }

  /* ── MICROTEXT — fine print, captions, footnotes ── */
  .microtext,
  small {
    font-size: var(--text-micro);
    font-weight: var(--weight-regular);
    line-height: var(--leading-snug);
    letter-spacing: 0.02em;
  }
}


/* ================================================================================
   21. FORMS
   --------------------------------------------------------------------------------
   Modernisations vs v2:
     • :user-invalid replaces :invalid for live validation styling — only
       shows error after the user has interacted, not on first render.
     • field-sizing: content for textareas (set in reset; auto-grow).
     • accent-color (set globally) handles native checkbox/radio fills for free.
       We still custom-style for visual consistency, but :checked colour
       inherits from --brand-accent automatically if we drop the custom paint.
     • light-dark() on form bg + placeholder.
     • Logical properties on padding/margin.
     • The legacy .form-group--error class is preserved as an explicit override
       (rare cases where validation state is server-driven, not client-side).
   ================================================================================ */
@layer components {

  /* ── Form group (stacked layout) ── */
  .form-group {
    display: flex;
    flex-direction: column;
    gap: var(--space-2xs);
    margin-block-end: var(--space-md);
  }

  /* ── Label ── */
  .form-label {
    font-size: var(--text-card-h6);
    font-weight: var(--weight-semibold);
    color: var(--text-heading);
  }
  .form-label .required {
    color: var(--state-error);
    margin-inline-start: 0.15em;
  }

  /* ── Text input + select share ~80% of styling ──
     Common surface (font, colour, border, radius, padding-block,
     transition, line-height) declared once. Inputs use symmetric
     padding-inline; selects split it (extra room on the end side
     for the arrow icon).

     No `outline: none` here — the base layer's :focus-visible rule
     applies a focus ring for keyboard users, and :focus:not(:focus-
     visible) suppresses the outline for mouse-focus (where the
     border-color change is sufficient indication). */
  .form-input,
  .form-select {
    font-family: inherit;
    font-size: var(--form-font-size);
    color: var(--form-text);
    background-color: var(--form-bg);
    border: var(--border-width-xs) solid var(--form-border);
    border-radius: var(--form-radius);
    padding-block: var(--form-padding-y);
    transition: border-color var(--transition-base);
    line-height: var(--leading-normal);
    /* Fill the container by default — narrow form-groups (cards, dialogs,
       sidebars) are the common case. For inline forms (nav search bar,
       newsletter signup row), reach for .form-input--auto, or set
       `flex: 1` on a flex child — the master's existing inline patterns
       (.error-page__search .form-input, .search-form-inline .form-input)
       use flex: 1 and continue to work because flex-basis: 0 overrides
       inline-size from CSS. (box-sizing: border-box is global, set at the
       universal selector in Section 1.) */
    inline-size: 100%;
    max-inline-size: 100%;
  }
  /* Opt-out for cases where the input should size to content / UA default —
     inline forms, search-in-nav, anywhere the parent isn't sizing for it. */
  .form-input--auto {
    inline-size: auto;
  }
  .form-input  { padding-inline: var(--form-padding-x); }
  .form-select {
    padding-inline-start: var(--form-padding-x);
    padding-inline-end:   var(--form-select-padding-end);
    appearance: none;
    background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24' fill='none' stroke='%2371717a' stroke-width='1.5' stroke-linecap='round' stroke-linejoin='miter'%3E%3Cpolyline points='6 9 12 15 18 9'/%3E%3C/svg%3E");
    background-repeat: no-repeat;
    background-position: right var(--form-select-arrow-offset) center;
    background-size: var(--form-select-arrow-size);
    cursor: pointer;
  }

  .form-input::placeholder { color: var(--form-placeholder); }

  .form-input:hover,
  .form-select:hover { border-color: var(--form-border-hover); }
  .form-input:focus,
  .form-select:focus { border-color: var(--form-border-focus); }

  /* textarea sizing — field-sizing: content is applied in the reset, scoped
     to textarea.form-input. inline-size: 100% from .form-input above fills
     the container; this min-block-size: 5rem keeps an empty textarea from
     collapsing vertically to a single line. */
  textarea.form-input { min-block-size: 5rem; }

  /* Select option styling — light-dark aware via --form-* tokens. */
  .form-select option {
    background-color: var(--form-bg);
    color: var(--form-text);
  }

  /* ── Checkbox & Radio (custom-styled — accent-color handles native fallback) ── */
  .form-check {
    display: flex;
    align-items: center;
    gap: var(--space-xs);
    cursor: pointer;
    font-size: var(--form-font-size);
    color: var(--text-body);
  }
  .form-check input[type="checkbox"],
  .form-check input[type="radio"] {
    appearance: none;
    inline-size: 1.15em;
    block-size: 1.15em;
    border: var(--border-width-xs) solid var(--form-border);
    background-color: var(--form-bg);
    cursor: pointer;
    flex-shrink: 0;
    display: grid;
    place-content: center;
    transition:
      border-color    var(--transition-base),
      background-color var(--transition-base);
  }
  .form-check input[type="checkbox"] { border-radius: var(--radius-1); }
  .form-check input[type="radio"]    { border-radius: var(--radius-circle); }

  .form-check input[type="checkbox"]:checked {
    background-color: var(--brand-accent);
    border-color: var(--brand-accent);
  }
  .form-check input[type="checkbox"]:checked::after {
    content: "";
    inline-size: 0.55em;
    block-size: 0.55em;
    background-color: var(--text-on-brand);
    -webkit-mask-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24' fill='none' stroke='%23fff' stroke-width='3' stroke-linecap='round' stroke-linejoin='round'%3E%3Cpolyline points='20 6 9 17 4 12'/%3E%3C/svg%3E");
            mask-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24' fill='none' stroke='%23fff' stroke-width='3' stroke-linecap='round' stroke-linejoin='round'%3E%3Cpolyline points='20 6 9 17 4 12'/%3E%3C/svg%3E");
    -webkit-mask-size: contain;
            mask-size: contain;
  }
  .form-check input[type="radio"]:checked { border-color: var(--brand-accent); }
  .form-check input[type="radio"]:checked::after {
    content: "";
    inline-size: 0.5em;
    block-size: 0.5em;
    border-radius: var(--radius-circle);
    background-color: var(--brand-accent);
  }

  /* ── Helper text ── */
  .form-helper {
    font-size: calc(var(--text-card-h6) * 0.9);
    color: var(--text-muted);
    line-height: var(--leading-normal);
  }

  /* ── VALIDATION — declarative via :user-invalid (live, post-interaction) ── */
  /* Fires only after the user has touched the field and blurred / submitted.
     Replaces v2's manually-applied .form-group--error class for the common case. */
  .form-input:user-invalid,
  .form-select:user-invalid {
    border-color: var(--form-border-error);
  }
  .form-input:user-invalid:focus,
  .form-select:user-invalid:focus {
    outline-color: var(--form-border-error);
  }
  /* Helper text adjacent to an invalid field flips to error tone. */
  .form-input:user-invalid ~ .form-helper,
  .form-select:user-invalid ~ .form-helper {
    color: var(--state-error-text);
  }

  /* :user-valid — opt-in success styling. Subtle by default; comment out if not wanted. */
  .form-input:user-valid:not(:placeholder-shown),
  .form-select:user-valid {
    border-color: var(--state-success);
  }

  /* ── Explicit override classes (server-driven validation, edge cases) ── */
  .form-group--error  .form-input,
  .form-group--error  .form-select { border-color: var(--state-error); }
  .form-group--error  .form-helper { color: var(--state-error-text); }
  .form-group--error  .form-label  { color: var(--state-error-text); }

  .form-group--success .form-input,
  .form-group--success .form-select { border-color: var(--state-success); }
  .form-group--success .form-helper { color: var(--state-success-text); }

  /* ── Disabled ── */
  .form-input:disabled,
  .form-select:disabled,
  .form-group--disabled .form-input,
  .form-group--disabled .form-select {
    opacity: 0.5;
    cursor: not-allowed;
    background-color: var(--bg-subtle);
  }
  .form-group--disabled .form-label { opacity: 0.5; }
}


/* ================================================================================
   22. SURFACES
   --------------------------------------------------------------------------------
   Standalone containers with their own background. Rule: if it's singular,
   it's a surface. If it's plural in a grid, it's a card.

   Big v3 change: `.surface--dark` and `.surface--light` flip the local
   color-scheme — every light-dark() token inside the subtree resolves to
   the opposite mode automatically. No manual heading-colour overrides.
   No on-dark variants. The system just flips.
   ================================================================================ */
@layer tokens {
  :root {
    --surface-padding: var(--space-xl);
    --surface-radius:  var(--radius-4);
  }
}

@layer components {
  .surface {
    padding: var(--surface-padding);
    border-radius: var(--surface-radius);
    background-color: var(--bg-page);
    color: var(--text-body);
  }

  /* ── Background variants (no scheme flip) ── */
  .surface--alt    { background-color: var(--bg-surface-alt); }
  .surface--soft   { background-color: var(--bg-surface); }
  .surface--brand  {
    background-color: var(--brand-accent);
    color: var(--text-on-brand);
    color-scheme: dark;  /* anything inside reads as dark mode */
  }

  /* ── Scheme-flip variants ── */
  /* These don't just paint dark — they tell the subtree "treat this as dark mode".
     Every nested light-dark() token, every nested form input, every component
     using OS color-scheme defaults flips. Genuinely magical. */
  .surface--dark {
    color-scheme: dark;
    background-color: var(--bg-page);   /* now resolves to the dark side of light-dark() */
    color: var(--text-body);
  }
  .surface--light {
    color-scheme: light;
    background-color: var(--bg-page);   /* now resolves to the light side */
    color: var(--text-body);
  }

  /* ── Border modifier ── */
  .surface--bordered {
    border: var(--border-width-xs) solid var(--border-subtle);
  }
}


/* ================================================================================
   23. CARDS
   --------------------------------------------------------------------------------
   Composable card with vertical (default), horizontal, and thumbnail layouts.

   v3 modernisations:
     • Container queries — cards reflow based on the GRID's width, not viewport.
       A card in a 4-up grid stays compact even on a wide screen.
     • :has() variants — card with image vs. without, with link vs. without.
       Reduces the modifier-class burden for content-aware tweaks.
     • Concentric inner radii — inner buttons/images get parent_radius − padding.
     • Logical properties for horizontal-reverse (free RTL).

   Structure:
     .card
       .card__img > img
       .card__body
         .card__eyebrow
         .card__title
         .card__text
         <a class="btn btn--text">Read more</a>   ← v3.1: was .card__link

   Grid wrapper:
     .card-grid (the grid containing cards) — sets up container query context.
   ================================================================================ */
@layer tokens {
  :root {
    --card-radius:   var(--radius-4);
    --card-padding:  var(--space-md);
    --card-gap:      var(--space-sm);
    --card-shadow:   var(--shadow-none);
    --card-bg:       var(--bg-page);
    --card-border:   transparent;
    --card-img-ratio: 56.25%;            /* 16:9 — vertical card's padding-block-start aspect trick */
    --card-horizontal-img-width: 40%;
    --card-horizontal-aspect: auto;      /* body-driven default (v3.13.0). Set to a ratio (4/3, 16/9) for aspect-pinned. */
    --card-thumbnail-size: 7.5rem;       /* 120px @ default rem */
    --card-lift-distance: -4px;
    --card-eyebrow-tracking: 0.1em;
  }
}

@layer components {

  /* ── GRID WRAPPER ── */
  /* Anything containing cards in a grid sets this. Establishes the container
     query context AND applies subgrid-friendly tracks (Phase 3 will lean on
     subgrid for true cross-card alignment). */
  .card-grid {
    display: grid;
    grid-template-columns: repeat(auto-fill, minmax(20rem, 1fr));
    gap: var(--space-md);
    container-type: inline-size;
    container-name: card-grid;
  }

  /* ── GRACEFUL GRID (added v3.13.0) ──
     Three-card grids hit the "orphan row" problem at tablet: at default
     .card-grid (auto-fill minmax 20rem) a 3-card grid collapses to 2+1 at
     tablet — two cards side-by-side, one alone below with empty space to
     its right. Designers find this ugly; it's also a missed-density signal
     (the tablet viewport has more horizontal room than a single column but
     less than three).

     .card-grid--graceful is the opt-in fix: three columns at desktop, ONE
     column at mobile + tablet, BUT at the tablet window (36-60rem) the
     cards flip to horizontal layout (image-left, body-right, body-driven
     card height). The result:

       desktop (>60rem):   3 vertical cards in a row.
       tablet  (36-60rem): cards stack vertically, each laid out horizontally
                           (image left at --card-horizontal-img-width, body
                           right). Card height is body-driven so cards are
                           short — total scroll is ~half what a 2+1 vertical
                           grid would be.
       mobile  (<36rem):   1 vertical card per row.

     Why media queries (not container queries) for the grid template:
     container queries can't query their own container — `@container` styles
     descendants, not the queried element itself. The grid-template-columns
     decision is on the grid itself, so it has to use @media. Inner card
     adjustments could move to container queries if grids ever appear in
     narrower contexts (sidebars, splits) — flagged as a future refinement.

     The :not(.card--horizontal):not(.card--thumbnail) guards prevent
     graceful's tablet overrides from double-applying to cards that are
     already horizontal/thumbnail by modifier. (For .card--horizontal cards,
     master's existing rules already produce the right layout. For
     .card--thumbnail cards, the small fixed image size is intentional and
     graceful should leave it alone.)

     Use this for 3-card grids of content with image + short title + body.
     Not the right tool for 4+ card grids (no orphan to solve), card-less
     content (no image to anchor the horizontal layout), or very-long-body
     cards (horizontal feels stretched).

     ── Editorial-discipline knobs (existing master mechanisms; v3.15.1
     surfaced them in docs after a consumer-conversation about pinning
     image aspect + bounding body length in graceful grids):

     • Pin image aspect at the tablet breakpoint via --card-horizontal-aspect.
       Graceful's tablet cards become horizontal cards (flex-direction: row),
       so they pick up whatever --card-horizontal-aspect is set to. Default
       is `auto` (body-driven — image stretches to body height via flex +
       object-fit cover crop; no empty padding possible). Override per-grid
       via inline style, or brand-wide via @layer brand:

         <div class="card-grid--graceful" style="--card-horizontal-aspect: 4/3">…</div>

         @layer brand {
           :root { --card-horizontal-aspect: 4 / 3; }     ← affects every horizontal card
         }

       Aspect-pinned mode trades "image yields to body" for "consistent image
       shape across cards in the grid." Trade-off: if body content is shorter
       than the aspect-derived image height, body has empty space below the
       content. Combine with .line-clamp-N on .card__text to make that
       empty space uniform across cards rather than variable.

     • Bound body length via .line-clamp-N on .card__text:

         <p class="card__text line-clamp-2">…</p>
         <p class="card__text line-clamp-3">…</p>

       Caps body height at N lines. Pairs naturally with aspect-pinned mode
       for "all cards look the same shape" editorial-grid feel. Body-driven
       mode (the default) doesn't strictly need it but consistent body length
       still helps a grid read as a unit. */
  .card-grid--graceful {
    display: grid;
    grid-template-columns: repeat(3, 1fr);
    gap: var(--space-lg);
  }
  @media (max-width: 60rem) {
    .card-grid--graceful {
      grid-template-columns: 1fr;
    }
  }
  @media (min-width: 36rem) and (max-width: 60rem) {
    .card-grid--graceful > .card {
      flex-direction: row;
    }
    .card-grid--graceful > .card:not(.card--horizontal):not(.card--thumbnail) .card__img {
      inline-size: var(--card-horizontal-img-width);
      flex-shrink: 0;
      padding-block-start: 0;
    }
    .card-grid--graceful > .card:not(.card--horizontal):not(.card--thumbnail) .card__img img {
      position: static;
      block-size: 100%;
    }
  }

  /* ── BASE CARD ── */
  .card {
    background-color: var(--card-bg);
    border-radius: var(--card-radius);
    border: var(--border-width-xs) solid var(--card-border);
    box-shadow: var(--card-shadow);
    overflow: clip;     /* `clip` instead of `hidden` — preserves scroll behaviour */
    display: flex;
    flex-direction: column;
    text-decoration: none;
    color: inherit;
    transition:
      box-shadow   var(--transition-base),
      transform    var(--transition-base),
      border-color var(--transition-base);
  }

  /* ── Image slot ── */
  .card__img {
    position: relative;
    overflow: clip;
  }
  .card__img img {
    inline-size: 100%;
    block-size: 100%;
    object-fit: cover;
    display: block;
  }

  /* Aspect ratio for the default vertical card — padding-block-start trick
     gives .card__img a known block-size derived from its inline-size. */
  .card:not(.card--horizontal):not(.card--thumbnail) .card__img {
    padding-block-start: var(--card-img-ratio);
  }
  /* Absolute positioning on the img generalised v3.13.0 to include horizontal
     cards too. Vertical cards: .card__img has block-size from padding trick,
     absolute img fills. Horizontal cards: .card__img stretches to row height
     via flex (driven by body content), absolute img fills the stretched parent.
     Thumbnail cards: keep img in normal flow because .card__img has a fixed
     inline-size (~120px) and lets the img define its own block-size. */
  .card:not(.card--thumbnail) .card__img img {
    position: absolute;
    inset-block-start: 0;
    inset-inline-start: 0;
  }

  /* ── Body ── */
  .card__body {
    padding: var(--card-padding);
    display: flex;
    flex-direction: column;
    gap: var(--card-gap);
    flex: 1;
  }

  /* ── Eyebrow ── */
  .card__eyebrow {
    font-size: var(--text-card-h6);
    font-weight: var(--weight-bold);
    letter-spacing: var(--card-eyebrow-tracking);
    text-transform: uppercase;
    color: var(--brand-accent-text);
  }
  /* Meta variant (date + tag pair, etc.) */
  .card__eyebrow--meta {
    display: flex;
    align-items: center;
    flex-wrap: wrap;
    gap: var(--space-xs);
  }

  /* ── Title ── */
  .card__title {
    font-family: var(--type-card-heading);
    font-size: var(--text-card-h5);
    font-weight: var(--weight-bold);
    color: var(--text-heading);
    line-height: var(--leading-snug);
  }
  .card__title--sm { font-size: var(--text-card-h6); }
  .card__title--md { font-size: var(--text-card-h5); }
  .card__title--lg { font-size: var(--text-card-h4); }

  /* ── Text ── */
  .card__text {
    font-size: var(--text-card-body);
    color: var(--text-muted);
    line-height: var(--leading-loose);
  }

  /* ── Card link ──
     For "Read more"-style links inside cards, use a button instead:
       <a class="btn btn--text">Read more</a>
     The .btn--text style (master Section 18) gives no fill, no border, hover
     bg tint, and matches the system's text-as-action visual treatment. */

  /* ── CONCENTRIC INNER RADII ──
     Solid-fill buttons (.btn--primary, --tint, --outline, --white-*)
     sitting flush against the card's curved corner pick up a concentric
     radius (parent radius − padding). Falls back to the button's NATURAL
     radius when card padding is larger than the parent's radius (in
     which case the button is fully inside a flat region and concentric
     doesn't apply).

     .btn--text is excluded — it has no fill or border at rest, so
     concentric corners are irrelevant. Its hover bg uses the explicit
     .btn--text border-radius. */
  .card__body > .btn:not(.btn--text),
  .card__body .card__inner-action {
    border-radius: max(var(--btn-radius), calc(var(--card-radius) - var(--card-padding)));
  }

  /* ── INTERACTION VARIANTS ── */

  /* Lift — flat at rest, raises on hover.

     Two specificity battles, both won by .card.card--lift selector:

     1. transform value:
        .card.card--lift:hover  (0,3,0) — translateY(-4px)  ← WINS
        .reveal.is-visible      (0,2,0) — none

     2. transition property (so box-shadow + border-color animate too):
        .card.card--lift        (0,2,0) — full transition list  ← WINS
        .reveal                 (0,1,0) — opacity + transform only

     Without (2), .reveal's transition list wins over .card's (equal
     specificity, .reveal defined later). Reveal's list lacks
     box-shadow + border-color, so those properties snap instantly on
     hover — making the lift feel uneven (shadow appears, but the
     translate seems missing because everything else was instant). */
  .card.card--lift {
    box-shadow: var(--shadow-none);
    border-color: transparent;
    transition:
      box-shadow   var(--transition-base),
      transform    var(--transition-base),
      border-color var(--transition-base),
      opacity      var(--transition-base);
  }
  .card.card--lift:hover {
    box-shadow: var(--shadow-lg);
    transform: translateY(var(--card-lift-distance));
    border-color: var(--border-subtle);
  }

  /* Reveal variant — invisible at rest, materialises on hover. Same
     specificity treatment as .card--lift. */
  .card.card--reveal {
    background-color: transparent;
    box-shadow: var(--shadow-none);
    border-color: transparent;
    transition:
      background-color var(--transition-base),
      box-shadow       var(--transition-base),
      transform        var(--transition-base),
      border-color     var(--transition-base),
      opacity          var(--transition-base);
  }
  .card.card--reveal:hover {
    background-color: var(--card-bg);
    box-shadow: var(--shadow-lg);
    transform: translateY(var(--card-lift-distance));
    border-color: var(--border-subtle);
  }

  /* Ghost — pure content, no chrome */
  .card--ghost {
    background-color: transparent;
    border: none;
    box-shadow: none;
    border-radius: 0;
  }
  .card--ghost .card__body { padding: 0; }

  /* ── HORIZONTAL CARD ──
     Body-driven by default (v3.13.0). Pre-v3.13.0 master shipped this
     without an aspect-ratio pin and with `justify-content: center` on the
     body, which produced visible empty padding whenever the image was
     taller than the body content (portrait source images, short titles
     etc.). The fix is two-part:

     1. Drop `justify-content: center` on the body — content stacks at
        the top of the flex row, body's natural height drives card height.
     2. Image is absolutely-positioned via the base .card__img rule above,
        so .card__img has no intrinsic block-size — flex stretch makes it
        the row height (= body content height), and the absolute img fills
        the stretched parent with object-fit: cover cropping to taste.

     Result: image crops vary card-by-card (the image yields to the body),
     no empty padding is structurally possible.

     For consumers wanting aspect-pinned (consistent image shape across
     cards in a list — the editorial-grid feel), opt in:

       per-card        <div class="card card--horizontal card--horizontal--fixed">…
       per-instance    <div class="card card--horizontal" style="--card-horizontal-aspect: 16/9">…
       brand-wide      @layer brand { :root { --card-horizontal-aspect: 4 / 3; } }

     The aspect-pinned mode trades "empty padding when body < image height"
     for "predictable card heights." Body still stacks at top (no centering)
     so the empty space falls below the body content, not around it. */
  .card--horizontal,
  .card--horizontal-reverse {
    flex-direction: row;
  }
  .card--horizontal-reverse { flex-direction: row-reverse; }

  .card--horizontal .card__img,
  .card--horizontal-reverse .card__img {
    inline-size: var(--card-horizontal-img-width);
    flex-shrink: 0;
    aspect-ratio: var(--card-horizontal-aspect);
  }
  /* Body has no justify-content — natural top-stacking. v3.13.0 removed
     the previous justify-content: center which created the empty-padding
     bug. See the Section 27 comment above. */

  /* Opt-in aspect-pinned: consistent image shape across cards in a list. */
  .card--horizontal--fixed {
    --card-horizontal-aspect: 4 / 3;
  }

  /* ── THUMBNAIL CARD ── */
  .card--thumbnail {
    flex-direction: row;
    align-items: stretch;
  }
  .card--thumbnail.card--reverse { flex-direction: row-reverse; }
  .card--thumbnail .card__img {
    inline-size: var(--card-thumbnail-size);
    flex-shrink: 0;
  }
  .card--thumbnail .card__body { justify-content: center; }

  /* ── DECLARATIVE :has() VARIANTS ── */

  /* Card with no image → tighter padding (no awkward visual whitespace) */
  .card:not(:has(.card__img)) .card__body {
    padding: var(--space-lg);
  }

  /* Card with a button inside → reads as interactive. The lift variant
     handles the visual; this :has() hook is left available for downstream
     themes that want extra styling without editing the markup. */
  .card:has(.btn):not(.card--lift):not(.card--reveal) {
    /* No default styling — hook only. */
  }

  /* ── CONTAINER QUERIES — card grid responsive ── */

  /* When the grid container is narrower than two columns' worth of room,
     stack horizontal cards vertically. */
  @container card-grid (max-width: 32rem) {
    .card--horizontal,
    .card--horizontal-reverse,
    .card--thumbnail,
    .card--thumbnail.card--reverse {
      flex-direction: column;
    }
    .card--horizontal .card__img,
    .card--horizontal-reverse .card__img,
    .card--thumbnail .card__img {
      inline-size: 100%;
      padding-block-start: var(--card-img-ratio);
      position: relative;
      /* When a horizontal card collapses to vertical (narrow grid), use the
         vertical-card aspect-trick (padding-block-start) as the height source.
         Reset aspect-ratio in case --card-horizontal-aspect was set to a value
         (.card--horizontal--fixed or brand-wide token override) — otherwise the
         two height mechanisms would compete. */
      aspect-ratio: auto;
    }
    .card--horizontal .card__img img,
    .card--horizontal-reverse .card__img img,
    .card--thumbnail .card__img img {
      position: absolute;
      inset-block-start: 0;
      inset-inline-start: 0;
    }
  }

  /* When the grid is wide AND the card has a long body, allow generous body padding */
  @container card-grid (min-width: 60rem) {
    .card__body { padding: var(--space-lg); }
  }

  /* ── REDUCED MOTION ── */
  @media (prefers-reduced-motion: reduce) {
    .card { transition: none; }
  }
}


/* ================================================================================
   24. GLASSMORPHISM
   --------------------------------------------------------------------------------
   Universal .glass modifier — frosted-glass effect on any container. Three
   intensity tiers; pairs with --dark for dark-tinted variants.

   v3 modernisations:
     • Tints expressed via color-mix(in oklch, white/black X%, transparent)
       instead of rgba literals.
     • light-dark() on default tints so .glass auto-reads in dark mode.
     • Backdrop-filter unchanged (universally supported in 2026).
   ================================================================================ */
@layer tokens {
  :root {
    --glass-blur: 12px;

    --glass-tint:        color-mix(in oklch,
                           light-dark(white, black) 15%,
                           transparent);
    --glass-tint-strong: color-mix(in oklch,
                           light-dark(white, black) 35%,
                           transparent);
    --glass-tint-solid:  color-mix(in oklch,
                           light-dark(white, black) 75%,
                           transparent);

    --glass-border:      color-mix(in oklch,
                           light-dark(white, white) 20%,
                           transparent);
    --glass-border-solid: color-mix(in oklch,
                           light-dark(white, white) 50%,
                           transparent);

    --glass-shadow:      0 4px 24px light-dark(rgb(0 0 0 / 0.06), rgb(0 0 0 / 0.4));
  }
}

@layer components {
  .glass {
    background-color: var(--glass-tint);
    -webkit-backdrop-filter: blur(var(--glass-blur));
            backdrop-filter: blur(var(--glass-blur));
    border: var(--border-width-xs) solid var(--glass-border);
    box-shadow: var(--glass-shadow);
  }
  .glass--strong { background-color: var(--glass-tint-strong); }
  .glass--solid  {
    background-color: var(--glass-tint-solid);
    border-color: var(--glass-border-solid);
  }

  /* Forces dark glass regardless of OS scheme — useful on a known-image bg */
  .glass--dark {
    color-scheme: dark;
    color: var(--text-on-brand);
  }

  /* Fallback for browsers without backdrop-filter (none current) */
  @supports not ((backdrop-filter: blur(1px)) or (-webkit-backdrop-filter: blur(1px))) {
    .glass        { background-color: color-mix(in oklch, light-dark(white, black) 70%, transparent); }
    .glass--solid { background-color: color-mix(in oklch, light-dark(white, black) 88%, transparent); }
  }
}


/* ================================================================================
   25. SPOT ICONS
   --------------------------------------------------------------------------------
   Decorative single-colour illustrations for card headers and feature grids.
   You supply the SVG (24×24, currentColor stroke); the component handles
   sizing, colour, background tint, hover state.
   ================================================================================ */
@layer tokens {
  :root {
    --spot-icon-size:    3rem;     /* 48px @ default */
    --spot-icon-size-sm: 2.25rem;  /* 36px */
    --spot-icon-size-lg: 4rem;     /* 64px */
    --spot-icon-padding: 0.625rem;
    --spot-icon-color:   var(--brand-accent);
    --spot-icon-bg:      var(--brand-accent-tint-12);
    --spot-icon-bg-hover: var(--brand-accent-tint-20);
    --spot-icon-radius:  var(--radius-2);
  }
}

@layer components {
  .spot-icon {
    display: inline-flex;
    align-items: center;
    justify-content: center;
    inline-size: var(--spot-icon-size);
    block-size: var(--spot-icon-size);
    padding: var(--spot-icon-padding);
    color: var(--spot-icon-color);
    background-color: var(--spot-icon-bg);
    border-radius: var(--spot-icon-radius);
    flex-shrink: 0;
    transition: background-color var(--transition-base);
  }
  .spot-icon svg { inline-size: 100%; block-size: 100%; display: block; }

  .spot-icon--sm {
    inline-size: var(--spot-icon-size-sm);
    block-size: var(--spot-icon-size-sm);
    padding: 0.5rem;
  }
  .spot-icon--lg {
    inline-size: var(--spot-icon-size-lg);
    block-size: var(--spot-icon-size-lg);
    padding: 0.75rem;
  }

  .spot-icon--circle    { border-radius: var(--radius-circle); }
  .spot-icon--hoverable:hover { background-color: var(--spot-icon-bg-hover); }

  .spot-icon--neutral {
    color: var(--text-muted);
    background-color: var(--bg-subtle);
  }
}


/* ================================================================================
   26. ACCORDION
   --------------------------------------------------------------------------------
   Two markup patterns supported:

     Native:  <details><summary class="accordion__trigger">…</summary>…</details>
              CSS reads [open] attribute (browser-managed).

     ARIA:    <button class="accordion__trigger" aria-expanded="false">…</button>
              JS toggles aria-expanded + .is-open on the parent .accordion__item.
              CSS reads .is-open class (parallel to [open]).

   The trigger styles reset native <button> defaults (background, border,
   inline-size, text-align) so either element renders identically.
   ================================================================================ */
@layer tokens {
  :root {
    --accordion-padding-y: 0.85rem;
    --accordion-icon-size: 14px;
    --accordion-title-size:    var(--text-card-h5);
    --accordion-title-weight:  var(--weight-bold);
    --accordion-content-size:  var(--text-card-body);
    --accordion-content-color: var(--text-muted);
    /* Reading-width cap. FAQ-style accordions read better at ~50rem
       (800px) than full-width. Brand can override per project. */
    --accordion-max-width:     50rem;
  }
}

@layer components {
  .accordion {
    display: flex;
    flex-direction: column;
    max-inline-size: var(--accordion-max-width);
  }
  .accordion__item {
    border-block-end: var(--border-width-xs) solid var(--border-subtle);
    overflow: clip;
  }
  .accordion__item:last-child { border-block-end: none; }

  /* Trigger — works as <summary> (native details), <button> (ARIA pattern),
     or any element with a click handler. Resets native <button> chrome so
     element choice is purely semantic.

     Full-width trigger with title at start and icon at end. Container's
     --accordion-max-width caps the overall width to a sensible reading
     measure, so triggers all align consistently rather than each being
     content-width with variable lengths. */
  .accordion__trigger {
    padding-block: var(--accordion-padding-y);
    font-family: inherit;
    font-weight: var(--accordion-title-weight);
    font-size: var(--accordion-title-size);
    cursor: pointer;
    list-style: none;
    display: flex;
    justify-content: space-between;
    align-items: center;
    gap: var(--space-md);
    color: var(--text-heading);
    /* Native <button> reset */
    inline-size: 100%;
    background-color: transparent;
    border: none;
    text-align: start;
  }
  .accordion__trigger::-webkit-details-marker { display: none; }
  .accordion__trigger::marker { display: none; content: ""; }

  .accordion__icon {
    color: var(--text-muted);
    display: flex;
    align-items: center;
    justify-content: center;
    flex-shrink: 0;
    inline-size: var(--accordion-icon-size);
    block-size: var(--accordion-icon-size);
  }
  .accordion__icon .v-bar {
    transition:
      transform var(--transition-base),
      opacity   var(--transition-base);
    transform-origin: center;
  }
  /* Open state — supports both [open] (native details) and .is-open (ARIA) */
  .accordion__item[open] .accordion__icon .v-bar,
  .accordion__item.is-open .accordion__icon .v-bar {
    transform: scaleY(0);
    opacity: 0;
  }

  .accordion__content {
    display: grid;
    grid-template-rows: 0fr;
    transition: grid-template-rows var(--transition-base);
  }
  .accordion__item[open] .accordion__content,
  .accordion__item.is-open .accordion__content {
    grid-template-rows: 1fr;
  }
  /* min-block-size: 0 + overflow: hidden — required for the grid track
     to actually compress to 0fr. Without min-block-size: 0, grid items
     have an implicit min-block-size of auto (= content size), which
     prevents the row from shrinking below content height regardless of
     fr value. overflow: clip alone doesn't trigger the same
     min-content-treats-as-zero behaviour as overflow: hidden in some
     browsers. */
  .accordion__content-inner {
    min-block-size: 0;
    overflow: hidden;
  }
  .accordion__content-inner p {
    padding-block-end: var(--space-sm);
    font-size: var(--accordion-content-size);
    color: var(--accordion-content-color);
    line-height: var(--leading-loose);
    margin: 0;
  }
}


/* ================================================================================
   27. ALERTS
   --------------------------------------------------------------------------------
   Block callouts. Four semantic variants tied to state tokens.
   ================================================================================ */
@layer tokens {
  :root {
    --alert-padding-y:  var(--space-sm);
    --alert-padding-x:  var(--space-md);
    --alert-radius:     var(--radius-2);
    --alert-font-size:  var(--text-card-body);
    --alert-title-size: var(--text-card-h6);
    --alert-icon-size:  1.25em;
  }
}

@layer components {
  .alert {
    display: flex;
    gap: var(--space-xs);
    padding-block: var(--alert-padding-y);
    padding-inline: var(--alert-padding-x);
    border-radius: var(--alert-radius);
    border: var(--border-width-xs) solid var(--border-subtle);
    background-color: var(--bg-surface);
    color: var(--text-body);
    font-size: var(--alert-font-size);
    line-height: var(--leading-loose);
    position: relative;
  }

  .alert__icon {
    flex-shrink: 0;
    inline-size: var(--alert-icon-size);
    block-size: var(--alert-icon-size);
    margin-block-start: 0.15em;
  }

  .alert__content { flex: 1; min-inline-size: 0; }
  .alert__title {
    font-size: var(--alert-title-size);
    font-weight: var(--weight-bold);
    margin-block-end: 0.25em;
  }
  .alert__body p { margin: 0; }

  .alert__dismiss {
    position: absolute;
    inset-block-start: var(--alert-padding-y);
    inset-inline-end:  var(--alert-padding-x);
    background: none;
    border: none;
    cursor: pointer;
    color: inherit;
    opacity: 0.5;
    padding: 0;
    line-height: 1;
    font-size: 1.1em;
    transition: opacity var(--transition-base);
  }
  .alert__dismiss:hover { opacity: 1; }

  /* Semantic variants */
  .alert--info {
    background-color: var(--state-info-soft);
    border-color: var(--state-info);
    color: var(--state-info-text);
  }
  .alert--success {
    background-color: var(--state-success-soft);
    border-color: var(--state-success);
    color: var(--state-success-text);
  }
  .alert--warning {
    background-color: var(--state-warning-soft);
    border-color: var(--state-warning);
    color: var(--state-warning-text);
  }
  .alert--error {
    background-color: var(--state-error-soft);
    border-color: var(--state-error);
    color: var(--state-error-text);
  }
}


/* ================================================================================
   28. BADGES / TAGS
   --------------------------------------------------------------------------------
   Inline labels for status, categories, metadata. Soft + solid variants.
   ================================================================================ */
@layer tokens {
  :root {
    --badge-font-size:    var(--text-_1);
    --badge-font-weight:  var(--weight-bold);
    --badge-padding-y:    0.2em;
    --badge-padding-x:    0.6em;
    --badge-radius:       var(--radius-1);
    --badge-tracking:     0.02em;
  }
}

@layer components {
  .badge {
    display: inline-flex;
    align-items: center;
    gap: 0.35em;
    font-family: inherit;
    font-size: var(--badge-font-size);
    font-weight: var(--badge-font-weight);
    letter-spacing: var(--badge-tracking);
    line-height: 1;
    padding-block: var(--badge-padding-y);
    padding-inline: var(--badge-padding-x);
    border-radius: var(--badge-radius);
    white-space: nowrap;
    background-color: var(--bg-subtle);
    color: var(--text-body);
    border: var(--border-width-xs) solid var(--border-subtle);
  }

  .badge--pill { border-radius: var(--radius-pill); }

  /* Soft semantic variants — accent-tinted bg, accent-text */
  .badge--accent {
    background-color: var(--brand-accent-tint-12);
    color: var(--brand-accent-text);
    border-color: var(--brand-accent-tint-35);
  }
  .badge--success { background-color: var(--state-success-soft); color: var(--state-success-text); border-color: var(--state-success); }
  .badge--warning { background-color: var(--state-warning-soft); color: var(--state-warning-text); border-color: var(--state-warning); }
  .badge--error   { background-color: var(--state-error-soft);   color: var(--state-error-text);   border-color: var(--state-error); }
  .badge--info    { background-color: var(--state-info-soft);    color: var(--state-info-text);    border-color: var(--state-info); }

  /* Solid variants — full saturation */
  .badge--solid-accent  { background-color: var(--brand-accent); color: var(--text-on-brand);   border-color: var(--brand-accent); }
  .badge--solid-success { background-color: var(--state-success); color: var(--text-on-brand);  border-color: var(--state-success); }
  .badge--solid-error   { background-color: var(--state-error);   color: var(--text-on-brand);  border-color: var(--state-error); }
}


/* ================================================================================
   29. TABLES
   --------------------------------------------------------------------------------
   Data tables with opt-in modifiers (striped, hover, bordered, compact).
   On-dark variants removed in v3 — color-scheme: dark on a parent surface
   flips light-dark() tokens automatically.
   ================================================================================ */
@layer tokens {
  :root {
    --table-font-size:        var(--text-card-body);
    --table-header-size:      var(--text-card-h6);
    --table-header-weight:    var(--weight-bold);
    --table-header-tracking:  0.04em;
    --table-header-bg:        var(--bg-surface);
    --table-header-color:     var(--text-heading);
    --table-cell-padding-y:   0.75rem;
    --table-cell-padding-x:   1rem;
    --table-border-color:     var(--border-subtle);
    --table-border-strong:    var(--border-medium);
    --table-stripe-bg:        var(--bg-surface);
    --table-hover-bg:         var(--bg-surface-alt);
    --table-compact-padding-y: 0.4rem;
    --table-compact-padding-x: 0.75rem;
  }
}

@layer components {
  /* Base styling applies to both .table (canonical class) AND bare
     <table> elements. Bare-table support added in Audit Pass 1 so
     markdown / MDX tables render correctly without authors having
     to add a class wrapper. The .table class remains canonical for
     authored components and is required for the modifiers below
     (--striped, --hover, --bordered, --compact) — modifiers
     deliberately stay class-only so authors opt in. */
  .table,
  table {
    inline-size: 100%;
    border-collapse: separate;
    border-spacing: 0;
    border-radius: var(--radius-1);
    overflow: clip;
    font-size: var(--table-font-size);
    line-height: var(--leading-normal);
    color: var(--text-body);
  }

  .table thead th,
  table thead th {
    text-align: start;
    font-size: var(--table-header-size);
    font-weight: var(--table-header-weight);
    letter-spacing: var(--table-header-tracking);
    text-transform: uppercase;
    color: var(--table-header-color);
    background-color: var(--table-header-bg);
    padding-block: var(--table-cell-padding-y);
    padding-inline: var(--table-cell-padding-x);
    border-block-end: var(--border-width-xs) solid var(--table-border-strong);
  }

  .table tbody td,
  table tbody td {
    padding-block: var(--table-cell-padding-y);
    padding-inline: var(--table-cell-padding-x);
    border-block-end: var(--border-width-xs) solid var(--table-border-color);
    vertical-align: top;
  }
  .table tbody tr:last-child td,
  table tbody tr:last-child td {
    border-block-end: none;
    padding-block-end: calc(var(--table-cell-padding-y) * 0.5);
  }

  .table tfoot td,
  table tfoot td {
    padding-block: var(--table-cell-padding-y);
    padding-inline: var(--table-cell-padding-x);
    border-block-start: var(--border-width-xs) solid var(--table-border-strong);
    font-weight: var(--table-header-weight);
    color: var(--text-heading);
  }

  /* Modifiers — stay class-only so authors deliberately opt in. */
  .table--striped tbody tr:nth-child(even) { background-color: var(--table-stripe-bg); }
  .table--hover tbody tr {
    transition: background-color var(--transition-base);
  }
  .table--hover tbody tr:hover { background-color: var(--table-hover-bg); }

  .table--bordered thead th {
    border: var(--border-width-xs) solid var(--table-border-color);
    border-block-end-color: var(--table-border-strong);
  }
  .table--bordered tbody td {
    border: var(--border-width-xs) solid var(--table-border-color);
    border-block-start: none;
  }
  .table--bordered tfoot td {
    border: var(--border-width-xs) solid var(--table-border-color);
    border-block-start-color: var(--table-border-strong);
  }
  .table--bordered tbody tr:last-child td {
    padding-block-end: var(--table-cell-padding-y);
  }

  .table--compact thead th,
  .table--compact tbody td,
  .table--compact tfoot td {
    padding-block: var(--table-compact-padding-y);
    padding-inline: var(--table-compact-padding-x);
    font-size: calc(var(--table-font-size) * 0.9);
  }

  .table .text-end { text-align: end; }
}


/* ================================================================================
   30. PAGINATION
   --------------------------------------------------------------------------------
   Numbered page links. WordPress-compatible (matches the markup wp_pagenavi
   and core paginate_links() emit), but framework-agnostic underneath.
   ================================================================================ */
@layer tokens {
  :root {
    --pagination-min-size:    2.5rem;          /* 40px @ default */
    --pagination-radius:      var(--radius-2);
    --pagination-font-size:   var(--text-card-body);
    --pagination-font-weight: var(--weight-semibold);
    --pagination-color:       var(--text-muted);
    --pagination-hover-bg:    var(--bg-surface);
    --pagination-hover-color: var(--text-body);
    --pagination-active-bg:   var(--brand-accent);
    --pagination-active-color: var(--text-on-brand);
  }
}

@layer components {
  .pagination {
    display: flex;
    justify-content: center;
    gap: var(--space-xs);
    padding-block: var(--space-xl);
  }
  .pagination .nav-links { display: flex; gap: var(--space-xs); }

  .pagination a,
  .pagination span {
    display: inline-flex;
    align-items: center;
    justify-content: center;
    min-inline-size: var(--pagination-min-size);
    block-size: var(--pagination-min-size);
    padding-inline: var(--space-xs);
    border-radius: var(--pagination-radius);
    font-size: var(--pagination-font-size);
    font-weight: var(--pagination-font-weight);
    text-decoration: none;
    transition:
      background-color var(--transition-base),
      color            var(--transition-base);
  }
  .pagination a { color: var(--pagination-color); }
  .pagination a:hover {
    background-color: var(--pagination-hover-bg);
    color: var(--pagination-hover-color);
  }
  .pagination .current {
    background-color: var(--pagination-active-bg);
    color: var(--pagination-active-color);
  }
}


/* ================================================================================
   31. DOCUMENT ROW
   --------------------------------------------------------------------------------
   Horizontal list-item for downloadable resources / file directories.
   Three-column grid: icon → body → action.
   ================================================================================ */
@layer tokens {
  :root {
    --doc-row-bg:           var(--bg-page);
    --doc-row-radius:       var(--radius-2);
    --doc-row-icon-size:    2.5rem;
    --doc-row-icon-bg:      var(--bg-surface);
    --doc-row-icon-color:   var(--brand-accent);
    --doc-row-icon-radius:  var(--radius-1);
  }
}

@layer components {
  .doc-list {
    display: flex;
    flex-direction: column;
    gap: var(--space-sm);
    container-type: inline-size;
    container-name: doc-list;
  }

  .doc-row {
    display: grid;
    grid-template-columns: auto 1fr auto;
    align-items: start;
    gap: var(--space-md);
    padding: var(--space-md);
    background-color: var(--doc-row-bg);
    border-radius: var(--doc-row-radius);
    transition: box-shadow var(--transition-base);
  }
  .doc-row:hover { box-shadow: var(--shadow-sm); }

  .doc-row__icon {
    inline-size: var(--doc-row-icon-size);
    block-size: var(--doc-row-icon-size);
    display: flex;
    align-items: center;
    justify-content: center;
    background-color: var(--doc-row-icon-bg);
    border-radius: var(--doc-row-icon-radius);
    color: var(--doc-row-icon-color);
    flex-shrink: 0;
  }

  .doc-row__body { min-inline-size: 0; }
  .doc-row__title {
    font-size: var(--text-card-h5);
    font-weight: var(--weight-semibold);
    margin: 0 0 var(--space-2xs);
  }
  .doc-row__title a { color: var(--text-heading); text-decoration: none; }
  .doc-row__title a:hover { color: var(--brand-accent-text); }

  .doc-row__summary {
    font-size: var(--text-card-body);
    color: var(--text-muted);
    margin: 0 0 var(--space-xs);
    line-height: var(--leading-normal);
  }

  .doc-row__meta {
    display: flex;
    align-items: center;
    flex-wrap: wrap;
    gap: var(--space-xs);
    font-size: var(--text-micro);
    color: var(--text-muted);
  }
  .doc-row__date,
  .doc-row__size { white-space: nowrap; }
  .doc-row__size::before {
    content: '\00B7';
    margin-inline-end: var(--space-xs);
    color: var(--text-muted);
  }

  .doc-row__action {
    flex-shrink: 0;
    align-self: center;
  }

  /* Locked / gated content panel */
  .doc-locked {
    text-align: center;
    padding-block: var(--space-xl);
    padding-inline: var(--space-lg);
    border-radius: var(--radius-4);
  }
  .doc-locked__icon { color: var(--text-muted); margin-block-end: var(--space-sm); }
  .doc-locked__text {
    font-size: var(--text-card-body);
    color: var(--text-muted);
    margin: 0 0 var(--space-md);
  }
  .doc-locked__actions {
    display: flex;
    align-items: center;
    justify-content: center;
    gap: var(--space-md);
  }

  /* Container query: collapse to single column on narrow lists */
  @container doc-list (max-width: 32rem) {
    .doc-row {
      grid-template-columns: 1fr;
      gap: var(--space-sm);
    }
    .doc-row__icon { display: none; }
    .doc-row__action { justify-self: start; }
  }
}


/* ================================================================================
   32. LEGAL PAGE + PROSE RHYTHM (.flow / .legal-body)
   --------------------------------------------------------------------------------
   Prose-first layout for legal / policy / terms pages, plus the .flow
   rhythm system that any prose-y content can opt into.

   Asymmetric heading rhythm (added v3.11.0): loose gap above headings
   (--flow-space-h2 / --flow-space-h3) + tight gap below (--flow-space-tight)
   so headings visually couple to their following content rather than
   floating between sections — the editorial-typography convention.

   Tokens em-based by default (prose-context convention — same shape as
   Tailwind Typography, NYT, Stripe's blog). The rhythm scales with the
   prose font-size automatically. See DECISIONS-v3.md §
   "Why .flow ships em-based prose rhythm" for the full rationale.
   ================================================================================ */
@layer tokens {
  :root {
    /* .flow + .legal-body prose-rhythm knobs. Em-based by default so the
       gap scales with the prose font-size. Override per-brand in @layer
       brand or per-block via inline style on the .flow element.

         --flow-space        generic gap between adjacent block siblings
                             (paragraph→paragraph, p→ul, etc.).
         --flow-space-h2     gap above an h2 — the page-section break.
         --flow-space-h3     gap above an h3 — the subsection break.
         --flow-space-tight  gap below any heading — couples heading to
                             the following content. Wins over --flow-space
                             and over the heading-above rules when an h3
                             directly follows an h2 (subsection nesting).

       Brand wanting rem-based fixed rhythm:
         @layer brand {
           :root {
             --flow-space:       var(--space-md);
             --flow-space-h2:    var(--space-xl);
             --flow-space-h3:    var(--space-lg);
             --flow-space-tight: var(--space-xs);
           }
         }
       ...recovers v3.0–v3.10 behaviour at default body size. */
    --flow-space:        1.5em;
    --flow-space-h2:     3em;
    --flow-space-h3:     2em;
    --flow-space-tight:  0.5em;
  }
}
@layer components {
  /* bg in :where() so surface composition wins by specificity.
     <section class="legal-header surface--brand"> flips to brand-fill. */
  :where(.legal-header) {
    background-color: var(--bg-surface);
  }
  .legal-header {
    padding-block: var(--space-struct-y-base) var(--space-lg);
  }
  .legal-header h1 { margin-block-end: var(--space-xs); }
  .legal-header__updated {
    font-size: var(--text-micro);
    color: var(--text-muted);
    margin: 0;
  }

  .legal-body {
    padding-block: var(--space-xl) var(--space-struct-y-base);
    /* Prose body size — opting into the prose-scale alias so brands
       overriding --text-prose-body in their brand file can retune
       reading scale without touching --text-1 directly. */
    font-size: var(--text-prose-body);
  }
  /* Heading-above rules switched from descendant (.legal-body h2) to
     child + sibling (.legal-body > * + h2) in v3.11.0 for two reasons:
     (1) the descendant form applied even to the FIRST h2 in a body,
     adding margin-block-start to a heading with no preceding sibling
     (clumsy if .legal-body opens with a heading); (2) it parallels
     .flow's pattern, so the two prose containers stay consistent.
     Trade-off: a nested <h2> inside <article> inside .legal-body no
     longer auto-gets rhythm. Legal-page content is overwhelmingly
     flat-structured so the practical impact is near-zero. */
  .legal-body > * + * { margin-block-start: var(--flow-space); }
  .legal-body > * + h2 { margin-block-start: var(--flow-space-h2); }
  .legal-body > * + h3 { margin-block-start: var(--flow-space-h3); }
  .legal-body > :is(h1, h2, h3, h4, h5, h6) + * {
    /* Asymmetric heading rhythm — wide gap above, tight gap below.
       The heading visually couples to its content (paragraph, list,
       blockquote, etc.) rather than floating between sections.
       See .flow comment below for full rationale. */
    margin-block-start: var(--flow-space-tight);
  }
  .legal-body ul,
  .legal-body ol { padding-inline-start: var(--space-lg); }
  .legal-body a { color: var(--brand-accent-text); }
  .legal-body a:hover { text-decoration: underline; }

  /* ── .flow — prose-context vertical-rhythm opt-in ──
     Master resets `p { margin: 0 }` so tight component layouts
     (cards, dialogs, sidebars) stay clean. .flow is the opt-back-in
     for editorial content — articles, docs, MDX bodies, longer prose.
     The lobotomised-owl selector (`* + *`) gives every element after
     a sibling a top margin; pair-specific overrides bump the gap for
     headings (loose above) and tighten it for list items + post-
     heading elements (tight below).

     Asymmetric heading rhythm (loose above, tight below) is the
     editorial-typography convention: the heading visually couples
     to its content rather than floating between sections.

     Tokens are em-based by default — see the @layer tokens block
     above this section. Em-in-prose is the convention Tailwind
     Typography, NYT, Stripe's blog etc. all use: the rhythm scales
     with the prose font-size automatically. Set .flow's font-size
     and every gap grows proportionally. Brands wanting fixed
     rem-based rhythm can override the tokens with rem/token values.

     Override the rhythm per-block:
       <article class="flow" style="--flow-space: 1em">…</article>
   */
  .flow {
    /* Prose body size — opting into the prose-scale alias so brands
       overriding --text-prose-body retune reading scale here too.
       Inherits to descendant <p>, <li>, etc. */
    font-size: var(--text-prose-body);
  }
  .flow > * + * {
    margin-block-start: var(--flow-space);
  }
  .flow > * + h2 { margin-block-start: var(--flow-space-h2); }
  .flow > * + h3 { margin-block-start: var(--flow-space-h3); }
  /* Heading-trail (asymmetric tight). Must come AFTER the heading-above
     rules so an h3 directly following an h2 (subsection nesting) wins
     this rule (tight, 0.5em) rather than the heading-above one (loose,
     2em). Both have specificity 0,1,1 — source order decides. */
  .flow > :is(h1, h2, h3, h4, h5, h6) + * {
    margin-block-start: var(--flow-space-tight);
  }
  .flow ul,
  .flow ol {
    padding-inline-start: var(--space-lg);
  }
  .flow li + li {
    margin-block-start: var(--space-2xs);
  }
}


/* ================================================================================
   33. SYSTEM PAGES — 404 + SEARCH
   --------------------------------------------------------------------------------
   Centred-hero error / empty-state layouts with inline search and suggestion
   grids. Container queries on the suggestion grids replace v2's media queries.
   ================================================================================ */
@layer components {

  /* ── 404 / Error page ── */
  .error-page {
    padding-block: var(--space-struct-y-hero) var(--space-xl);
    text-align: center;
    container-type: inline-size;
    container-name: error-page;
  }
  .error-page h1 {
    font-size: var(--text-display-h1);
    font-weight: var(--weight-bold);
    margin-block-end: var(--space-sm);
  }
  .error-page .eyebrow,
  .error-page .preheader { margin-block-end: var(--space-xs); }
  .error-page__illustration {
    color: var(--text-muted);
    margin-block-end: var(--space-lg);
    opacity: 0.4;
  }
  .error-page__illustration svg { margin-inline: auto; }
  .error-page__message {
    font-size: var(--text-display-body);
    color: var(--text-muted);
    max-inline-size: 540px;
    margin: 0 auto var(--space-lg);
  }

  .error-page__search {
    display: flex;
    gap: var(--space-sm);
    max-inline-size: 460px;
    margin: 0 auto var(--space-lg);
  }
  .error-page__search .form-input { flex: 1; }

  .error-page__actions {
    display: flex;
    align-items: center;
    justify-content: center;
    gap: var(--space-md);
    flex-wrap: wrap;
  }

  .error-page__suggestions {
    padding-block: var(--space-struct-y-base);
  }
  .error-page__suggestions .eyebrow,
  .error-page__suggestions .signpost {
    text-align: center;
    margin-block-end: var(--space-lg);
  }
  .error-page__grid {
    display: grid;
    grid-template-columns: repeat(3, 1fr);
    gap: var(--space-md);
  }
  @container error-page (max-width: 48rem) {
    .error-page__grid { grid-template-columns: 1fr; }
  }

  /* ── Search page ── */
  .search-page {
    padding-block: var(--space-xl) var(--space-md);
    text-align: center;
    background-color: var(--bg-surface);
  }
  .search-page h1 {
    font-size: var(--text-display-h2);
    font-weight: var(--weight-bold);
    margin-block-end: var(--space-md);
  }

  .search-form-inline {
    display: flex;
    gap: var(--space-sm);
    max-inline-size: 500px;
    margin-inline: auto;
  }
  .search-form-inline .form-input { flex: 1; }

  /* Scoped to <section> to avoid collision with WordPress body.search-results class */
  section.search-results {
    padding-block: var(--space-md) var(--space-struct-y-base);
  }
  .search-results__pagination {
    text-align: center;
    padding-block-start: var(--space-xl);
  }

  section.search-empty {
    padding-block: var(--space-struct-y-base);
    text-align: center;
    container-type: inline-size;
    container-name: search-empty;
  }
  .search-empty__illustration {
    color: var(--text-muted);
    opacity: 0.35;
    margin-block-end: var(--space-lg);
  }
  .search-empty__illustration svg { margin-inline: auto; }
  .search-empty__message {
    font-size: var(--text-display-body);
    color: var(--text-muted);
    max-inline-size: 540px;
    margin: 0 auto var(--space-xl);
  }
  .search-empty__links {
    display: grid;
    grid-template-columns: repeat(3, 1fr);
    gap: var(--space-md);
    text-align: start;
  }
  @container search-empty (max-width: 48rem) {
    .search-empty__links { grid-template-columns: 1fr; }
  }
}


/* ================================================================================
   34. SKIP-TO-CONTENT
   --------------------------------------------------------------------------------
   Visually hidden, visible on keyboard focus. Wins over every other layer
   via z-skip token.
   ================================================================================ */
@layer components {
  .skip-to-content {
    position: absolute;
    inset-block-start: -100%;
    inset-inline-start: var(--space-md);
    z-index: var(--z-skip);
    padding-block: var(--space-xs);
    padding-inline: var(--space-md);
    background-color: var(--bg-page);
    color: var(--brand-accent-text);
    font-weight: var(--weight-bold);
    font-size: var(--text-card-body);
    border-end-end-radius: var(--radius-2);
    border-end-start-radius: var(--radius-2);
    box-shadow: var(--shadow-md);
    text-decoration: none;
    transition: inset-block-start var(--transition-base);
  }
  .skip-to-content:focus { inset-block-start: 0; }
}


/* ================================================================================
   35. TOUCH TARGETS
   --------------------------------------------------------------------------------
   Enforce 44px minimum on coarse pointers (touch). Honours WCAG 2.5.5.
   ================================================================================ */
@layer utilities {
  @media (pointer: coarse) {
    .btn--sm,
    .badge {
      min-block-size: 44px;
      display: inline-flex;
      align-items: center;
    }
  }
}


/* ================================================================================
   36. SPLIT LAYOUT UTILITY
   --------------------------------------------------------------------------------
   Two-column grid for text + image sections. Container-query-driven stack.
   ================================================================================ */
@layer utilities {
  .split-layout {
    display: grid;
    grid-template-columns: 1fr 1fr;
    gap: var(--space-2xl);
    align-items: center;
    container-type: inline-size;
    container-name: split;
  }
  .split-layout--start { align-items: start; }
  .split-layout--reverse > :first-child { order: 2; }
  .split-layout--reverse > :last-child  { order: 1; }

  @container split (max-width: 48rem) {
    .split-layout {
      grid-template-columns: 1fr;
      gap: var(--space-xl);
    }
    .split-layout--reverse > :first-child,
    .split-layout--reverse > :last-child { order: unset; }
  }
}


/* ================================================================================
   37. STICKY PAGE NAVIGATION
   --------------------------------------------------------------------------------
   Horizontal sticky nav with animated underline indicator. Glass-tinted bar.
   ================================================================================ */
@layer components {
  .page-nav {
    position: sticky;
    inset-block-start: 0;
    z-index: var(--z-sticky);
    background-color: color-mix(in oklch, var(--bg-page) 75%, transparent);
    -webkit-backdrop-filter: blur(8px);
            backdrop-filter: blur(8px);
    border-block-end: var(--border-width-xs) solid color-mix(in oklch, var(--bg-page) 50%, transparent);
    box-shadow: var(--shadow-xs);
  }
  .page-nav__inner {
    position: relative;
    display: flex;
    align-items: center;
    gap: var(--space-xs);
    overflow-x: auto;
    scrollbar-width: none;
  }
  .page-nav__inner::-webkit-scrollbar { display: none; }

  .page-nav__indicator {
    position: absolute;
    inset-block-end: 0;
    block-size: var(--border-width-md);
    border-radius: var(--border-width-md);
    background-color: var(--page-nav-accent, var(--brand-accent));
    pointer-events: none;
    transition:
      left  var(--transition-base),
      width var(--transition-base);
  }

  .page-nav__link {
    padding: var(--space-md);
    font-size: var(--btn-font-size);
    font-weight: var(--weight-semibold);
    color: var(--text-muted);
    white-space: nowrap;
    transition: color var(--transition-base);
    text-decoration: none;
  }
  .page-nav__link:hover,
  .page-nav__link.is-active { color: var(--text-heading); }

  .page-nav__cta {
    margin-inline-start: auto;
    padding-block: var(--btn-padding-y-sm);
    padding-inline: var(--btn-padding-x);
    color: var(--text-on-brand);
    border-radius: var(--radius-2);
    font-size: var(--text-micro);
    font-weight: var(--weight-semibold);
    white-space: nowrap;
    transition: background-color var(--transition-base);
    text-decoration: none;
    flex-shrink: 0;
  }
}


/* ================================================================================
   38. MEGA MENU
   --------------------------------------------------------------------------------
   Full-width hover-activated dropdown navigation. Supports link lists, card
   grids, tabbed panels, icon grids, event lists, calendar CTAs.

   The mega menu is OPTIONAL — only load this section if the project needs it.
   On a non-mega site, every selector below is inert.
   ================================================================================ */
@layer components {

  /* ── Full-width mega panel ── */
  .mega {
    position: absolute;
    inset-block-start: 100%;
    inset-inline: 0;
    inline-size: 100%;
    background-color: var(--bg-surface);
    border-block: var(--border-width-xs) solid var(--border-subtle);
    box-shadow: var(--shadow-lg);
    opacity: 0;
    visibility: hidden;
    transition:
      opacity    var(--transition-base),
      transform  var(--transition-base),
      visibility 0s var(--duration-base);
    transform: translateY(-4px);
    pointer-events: none;
    z-index: var(--z-popover);
  }
  .site-nav__item:hover > .mega {
    opacity: 1;
    visibility: visible;
    pointer-events: auto;
    transform: translateY(0);
    transition:
      opacity    var(--transition-base),
      transform  var(--transition-base),
      visibility 0s 0s;
  }
  /* Hover gap bridge — prevents the menu disappearing while moving cursor */
  .mega::before {
    content: '';
    position: absolute;
    inset-block-start: -16px;
    inset-inline: 0;
    block-size: 16px;
  }
  .mega__inner {
    max-inline-size: var(--layout-max);
    margin-inline: auto;
    padding-block: var(--space-lg);
    padding-inline: var(--space-gutter, var(--space-md));
  }

  /* ── Multi-section layout ── */
  .mega__sections {
    display: grid;
    gap: var(--space-lg);
  }
  .mega__sections--cols-3   { grid-template-columns: 1fr 1fr 1fr; }
  .mega__sections--cols-2-1 { grid-template-columns: 2fr 1fr; }

  .mega__section-heading {
    font-size: var(--text-card-h6);
    font-weight: var(--weight-bold);
    color: var(--text-muted);
    text-transform: uppercase;
    letter-spacing: 0.06em;
    margin: 0 0 var(--space-sm);
    padding-block-end: var(--space-xs);
    border-block-end: var(--border-width-xs) solid var(--border-subtle);
  }

  /* ── Section link list ── */
  .mega__section-links {
    list-style: none;
    margin: 0;
    padding: 0;
    display: flex;
    flex-direction: column;
    gap: 0.15rem;
  }
  .mega__section-link {
    display: flex;
    align-items: center;
    gap: 0.6rem;
    padding-block: 0.55rem;
    padding-inline: 0.65rem;
    border-radius: var(--radius-2);
    text-decoration: none;
    color: var(--text-body);
    font-size: var(--text-card-h6);
    font-weight: var(--weight-semibold);
    transition:
      background-color var(--transition-base),
      color            var(--transition-base);
  }
  .mega__section-link:hover {
    background-color: var(--bg-page);
    color: var(--link-color);
  }
  .mega__section-link svg {
    inline-size: 18px;
    block-size: 18px;
    flex-shrink: 0;
    stroke: currentColor;
    fill: none;
    stroke-width: 1.8;
    stroke-linecap: round;
    stroke-linejoin: round;
  }
  .mega__section-link--accent {
    color: var(--link-color);
    font-weight: var(--weight-bold);
  }
  .mega__section-link--accent:hover { color: var(--link-color-hover); }
  .mega__section-desc {
    font-size: var(--text-card-h6);
    color: var(--text-muted);
    font-weight: var(--weight-regular);
  }

  /* ── Card overrides scoped for compact mega context ── */
  .mega__card-grid {
    display: grid;
    grid-template-columns: repeat(3, 1fr);
    gap: var(--space-sm);
  }
  .mega__card {
    --card-radius: var(--radius-4);
    --card-padding: var(--space-sm);
    --card-img-ratio: 50%;
  }
  .mega__card .card__title {
    display: flex;
    align-items: center;
    gap: 0.4rem;
    margin-block-end: 0;
  }
  .mega__card .card__text { font-size: var(--text-card-h6); }

  /* ── Theme cards ── */
  .mega__theme-grid {
    display: grid;
    grid-template-columns: 1fr 1fr;
    gap: 0.25rem;
  }
  .mega__theme-card {
    display: flex;
    align-items: flex-start;
    gap: 0.65rem;
    padding-block: 0.6rem;
    padding-inline: 0.7rem;
    border-radius: var(--radius-4);
    text-decoration: none;
    color: inherit;
    transition: background-color var(--transition-base);
  }
  .mega__theme-card:hover { background-color: var(--bg-page); }
  .mega__theme-icon {
    inline-size: 36px;
    block-size: 36px;
    border-radius: var(--radius-2);
    flex-shrink: 0;
    display: flex;
    align-items: center;
    justify-content: center;
  }
  .mega__theme-icon svg {
    inline-size: 18px;
    block-size: 18px;
    stroke-width: 1.8;
    stroke-linecap: round;
    stroke-linejoin: round;
    fill: none;
  }
  .mega__theme-label h3 {
    font-size: var(--text-card-h6);
    font-weight: var(--weight-bold);
    color: var(--text-heading);
    margin: 0 0 0.1rem;
  }
  .mega__theme-label p {
    font-size: var(--text-micro);
    color: var(--text-muted);
    line-height: var(--leading-snug);
    margin: 0;
  }

  /* ── Icon grid ── */
  .mega__icon-grid {
    display: grid;
    grid-template-columns: 1fr 1fr;
    gap: 0.35rem;
  }
  .mega__icon-item {
    display: flex;
    align-items: flex-start;
    gap: 0.75rem;
    padding: 0.85rem;
    border-radius: var(--radius-4);
    text-decoration: none;
    color: inherit;
    transition: background-color var(--transition-base);
  }
  .mega__icon-item:hover { background-color: var(--bg-page); }
  .mega__icon-circle {
    inline-size: 42px;
    block-size: 42px;
    border-radius: var(--radius-2);
    flex-shrink: 0;
    display: flex;
    align-items: center;
    justify-content: center;
  }
  .mega__icon-circle svg {
    inline-size: 20px;
    block-size: 20px;
    stroke-width: var(--border-width-sm);
    stroke-linecap: round;
    stroke-linejoin: round;
    fill: none;
  }
  .mega__icon-text h3 {
    font-size: var(--text-card-h6);
    font-weight: var(--weight-bold);
    color: var(--text-heading);
    margin: 0 0 0.15rem;
  }
  .mega__icon-text p {
    font-size: var(--text-card-h6);
    color: var(--text-muted);
    line-height: var(--leading-normal);
    margin: 0;
  }

  /* ── Two-column intro layout ── */
  .mega__inner--two-col {
    display: grid;
    grid-template-columns: 280px 1fr;
    gap: var(--space-lg);
    align-items: start;
  }
  .mega__intro { padding-inline-end: var(--space-sm); }
  .mega__intro::before {
    content: '';
    display: block;
    inline-size: 40px;
    block-size: var(--border-width-md);
    border-radius: var(--border-width-md);
    margin-block-end: var(--space-sm);
    background-color: var(--brand-accent);
  }
  .mega__intro-heading {
    font-size: var(--text-card-h4);
    font-weight: 800;
    color: var(--text-heading);
    margin: 0 0 var(--space-xs);
    line-height: var(--leading-snug);
  }
  .mega__intro-blurb {
    font-size: var(--text-card-body);
    color: var(--text-muted);
    line-height: var(--leading-normal);
    margin: 0 0 1.25rem;
  }
  .mega__intro-link {
    display: inline-flex;
    align-items: center;
    gap: 0.35rem;
    font-size: var(--text-card-h6);
    font-weight: var(--weight-bold);
    color: var(--link-color);
    text-decoration: none;
    transition:
      gap   var(--transition-base),
      color var(--transition-base);
  }
  .mega__intro-link:hover {
    gap: 0.55rem;
    color: var(--link-color-hover);
  }
  .mega__intro-link svg {
    inline-size: 14px;
    block-size: 14px;
    stroke: currentColor;
    fill: none;
    stroke-width: var(--border-width-sm);
  }

  /* ── Events list + calendar CTA ── */
  .mega__events-list {
    display: flex;
    flex-direction: column;
  }
  .mega__event-item {
    display: flex;
    align-items: flex-start;
    gap: var(--space-sm);
    padding-block: 0.6rem;
    text-decoration: none;
    color: var(--text-body);
    border-block-end: var(--border-width-xs) solid var(--border-subtle);
    transition: color var(--transition-base);
  }
  .mega__event-item:first-of-type { padding-block-start: 0; }
  .mega__event-item:hover,
  .mega__event-item:hover .mega__event-name { color: var(--link-color); }
  .mega__event-date {
    flex-shrink: 0;
    inline-size: 70px;
    font-size: var(--text-micro);
    font-weight: var(--weight-bold);
    color: var(--brand-accent-text);
    white-space: nowrap;
    padding-block-start: 0.1rem;
  }
  .mega__event-info {
    display: flex;
    flex-direction: column;
    gap: 0.1rem;
  }
  .mega__event-name {
    font-size: var(--text-card-h6);
    font-weight: var(--weight-semibold);
    color: var(--text-heading);
    line-height: var(--leading-snug);
    transition: color var(--transition-base);
  }
  .mega__event-location {
    font-size: var(--text-card-h6);
    color: var(--text-muted);
    font-weight: var(--weight-regular);
    line-height: var(--leading-snug);
  }
  .mega__calendar-cta {
    display: flex;
    flex-direction: column;
    align-items: flex-start;
    justify-content: center;
    background-color: var(--bg-surface);
    border-radius: var(--radius-4);
    padding: var(--space-lg);
  }
  .mega__calendar-icon {
    color: var(--brand-accent);
    margin-block-end: var(--space-sm);
  }
  .mega__calendar-heading {
    font-size: var(--text-card-h4);
    font-weight: 800;
    color: var(--text-heading);
    margin: 0 0 var(--space-xs);
  }
  .mega__calendar-blurb {
    font-size: var(--text-card-body);
    color: var(--text-muted);
    line-height: var(--leading-normal);
    margin: 0 0 var(--space-md);
  }

  /* ── Tabbed mega panel ── */
  .mega__tabbed {
    display: grid;
    grid-template-columns: 220px 1fr;
    min-block-size: 340px;
  }
  .mega__tab-nav {
    list-style: none;
    margin: 0;
    padding: 0;
    border-inline-end: var(--border-width-xs) solid var(--border-subtle);
    display: flex;
    flex-direction: column;
    gap: 0.15rem;
    padding-inline-end: var(--space-md);
  }
  .mega__tab-link {
    display: flex;
    align-items: center;
    gap: 0.65rem;
    padding-block: 0.75rem;
    padding-inline: 0.85rem;
    border-radius: var(--radius-2);
    text-decoration: none;
    color: var(--text-muted);
    font-size: var(--text-card-h6);
    font-weight: var(--weight-semibold);
    transition:
      background-color var(--transition-base),
      color            var(--transition-base);
    cursor: default;
  }
  .mega__tab-link:hover {
    background-color: var(--bg-page);
    color: var(--text-body);
  }
  .mega__tab-link.is-active {
    background-color: var(--brand-accent-soft);
    color: var(--brand-accent-strong);
  }
  .mega__tab-link svg {
    inline-size: 20px;
    block-size: 20px;
    flex-shrink: 0;
    stroke: currentColor;
    fill: none;
    stroke-width: 1.8;
    stroke-linecap: round;
    stroke-linejoin: round;
  }
  .mega__tab-desc {
    font-size: var(--text-card-h6);
    font-weight: var(--weight-regular);
    color: var(--text-muted);
    margin-block-start: 0.1rem;
  }
  .mega__tab-link.is-active .mega__tab-desc {
    color: var(--brand-accent-strong);
    opacity: 0.7;
  }
  .mega__tab-content { padding-inline-start: var(--space-lg); }
  .mega__tab-panel {
    display: none;
    animation: megaFadeIn var(--duration-base) var(--ease-out);
  }
  .mega__tab-panel.is-active { display: block; }
  @keyframes megaFadeIn {
    from { opacity: 0; }
    to   { opacity: 1; }
  }
  .mega__tab-footer {
    margin-block-start: var(--space-sm);
    padding-block-start: var(--space-xs);
  }
  .mega__tab-footer a {
    display: inline-flex;
    align-items: center;
    gap: 0.35rem;
    font-size: var(--text-card-h6);
    font-weight: var(--weight-bold);
    color: var(--link-color);
    text-decoration: none;
    transition:
      gap   var(--transition-base),
      color var(--transition-base);
  }
  .mega__tab-footer a:hover {
    gap: 0.55rem;
    color: var(--link-color-hover);
  }
  .mega__tab-footer svg {
    inline-size: 14px;
    block-size: 14px;
    stroke: currentColor;
    fill: none;
    stroke-width: 2;
  }

  /* ── Mobile toggle + responsive collapse ──
     Scoped to .site-nav:has(.mega) — projects using the BEM site-nav without
     mega menus get their own drawer-style mobile pattern in Section 46 (at the
     900px breakpoint). The :has(.mega) scope here prevents Section 38's
     1024px-breakpoint rules from cross-contaminating non-mega navs (which
     previously caused the hamburger to appear at 1024px without Section 46's
     drawer firing until 900px — a dead zone where the menu was uncontrolled). */

  .site-nav:has(.mega) .site-nav__toggle {
    display: none;
    background: none;
    border: none;
    cursor: pointer;
    padding: var(--space-xs);
    color: var(--text-body);
    border-radius: var(--radius-2);
  }
  .site-nav:has(.mega) .site-nav__toggle:hover { background-color: var(--bg-surface-alt); }

  /* Note: mega menu collapse is a viewport-driven media query (page-level
     responsive behaviour). Below 900px = mobile menu mode.

     Pre-v3.12.0 this breakpoint was 1024px — a holdover from when the
     mega panels were assumed to need 1024px+ to render their multi-
     column layouts. Structural analysis showed otherwise: .mega has
     position: absolute; inset-inline: 0 (full-width container) +
     .mega__inner has max-inline-size: var(--layout-max) (content
     bounded), so at 900px viewport with three-column sections each
     column lands ~290px wide — usable. v3.12.0 lowered to 900px to
     match Section 46's BEM site-nav drawer breakpoint. Result: single
     mobile-collapse threshold across both site-nav patterns, and
     laptops 900–1280px keep the full nav.

     If a brand's mega content needs more width than 900px to render
     non-cramped (unusually dense panels, narrow body font that fits
     less per column, etc.), the breakpoint can be reset per project
     via project-layer CSS:
       @media (max-width: 1024px) {
         .site-nav:has(.mega) .site-nav__toggle { display: block; }
         ... etc ...
       }
     ...but the master default is now the more usable 900px. */
  @media (max-width: 900px) {
    .site-nav:has(.mega) .site-nav__toggle { display: block; }
    .site-nav:has(.mega) .site-nav__cta--desktop { display: none; }
    .site-nav:has(.mega) .site-nav__menu {
      display: none;
      position: absolute;
      inset-block-start: 100%;
      inset-inline: 0;
      background-color: var(--bg-page);
      flex-direction: column;
      padding-block: var(--space-xs) var(--space-sm);
      padding-inline: var(--space-sm);
      border-block-end: var(--border-width-xs) solid var(--border-subtle);
      box-shadow: var(--shadow-md);
      z-index: calc(var(--z-fixed) + 1);
      max-block-size: 80vh;
      overflow-y: auto;
    }
    .site-nav:has(.mega) .site-nav__menu.is-open { display: flex; }
    .site-nav:has(.mega) .site-nav__link {
      padding-block: 0.65rem;
      inline-size: 100%;
      font-size: var(--text-card-body);
    }
    .mega {
      position: static;
      transform: none;
      opacity: 1;
      visibility: visible;
      pointer-events: auto;
      box-shadow: none;
      border: none;
      border-radius: 0;
      padding: 0;
      inline-size: 100%;
      display: none;
    }
    .site-nav__item.is-mobile-open > .mega { display: block; }
    .mega__inner {
      padding-block: var(--space-sm);
      padding-inline: var(--space-xs) 0;
    }
    .mega__inner--two-col,
    .mega__sections--cols-3,
    .mega__sections--cols-2-1,
    .mega__card-grid,
    .mega__icon-grid,
    .mega__theme-grid {
      grid-template-columns: 1fr;
    }
    .mega__inner--two-col { gap: var(--space-sm); }
    .mega__intro::before { display: none; }
    .mega__card { flex-direction: row; }
    .mega__card .card__img {
      inline-size: 80px;
      flex-shrink: 0;
    }
    .mega__card .card__img img {
      block-size: 100%;
      min-block-size: 60px;
    }
    .mega__calendar-cta { padding: var(--space-md); }
    .mega__calendar-icon { display: none; }
    .mega__tabbed {
      grid-template-columns: 1fr;
      min-block-size: 0;
    }
    .mega__tab-nav {
      flex-direction: row;
      flex-wrap: wrap;
      gap: 0.35rem;
      border-inline-end: none;
      padding-inline-end: 0;
      padding-block-end: var(--space-xs);
      border-block-end: var(--border-width-xs) solid var(--border-subtle);
      margin-block-end: var(--space-sm);
    }
    .mega__tab-link {
      padding-block: 0.5rem;
      padding-inline: 0.75rem;
    }
    .mega__tab-content { padding-inline-start: 0; }
    .site-nav:has(.mega) .site-nav__cta--mobile {
      display: inline-flex;
      align-items: center;
      gap: 0.4rem;
      padding-block: var(--btn-padding-y);
      padding-inline: var(--btn-padding-x);
      font-size: var(--text-card-body);
      font-weight: var(--weight-bold);
      background-color: var(--btn-primary-bg);
      color: var(--btn-primary-text);
      border-radius: var(--radius-pill);
      text-decoration: none;
      margin-block-start: 0.75rem;
      inline-size: fit-content;
    }
  }
  @media (min-width: 901px) {
    .site-nav:has(.mega) .site-nav__cta--mobile { display: none; }
  }
}


/* ================================================================================
   39. SCROLL-REVEAL ANIMATION
   --------------------------------------------------------------------------------
   Site-wide fade + slide-up. JS adds .is-visible when the element enters
   the viewport (use IntersectionObserver).

   Stagger:   inline style `style="--reveal-delay: 120ms"` per element.
   No-slide:  add `.reveal--no-translate` for fade-only (e.g. when sliding
              would conflict with a parent transform or grid placement).

   Tokens are tunable per-brand if you want a longer / springier reveal.
   ================================================================================ */
@layer tokens {
  :root {
    --reveal-distance: 1.5rem;
    --reveal-duration: var(--duration-slow);
    --reveal-easing:   var(--ease-out);
    /* --reveal-delay is set per-element via inline style; no default here. */
  }
}

/* In @layer components (NOT utilities) so component hover rules can win
   via specificity. .reveal in @layer utilities would beat any
   component:hover transform regardless of specificity, because
   utilities is declared after components in the layer order. */
@layer components {
  .reveal {
    opacity: 0;
    transform: translateY(var(--reveal-distance));
    transition:
      opacity   var(--reveal-duration) var(--reveal-easing),
      transform var(--reveal-duration) var(--reveal-easing);
    transition-delay: var(--reveal-delay, 0s);
    will-change: opacity, transform;
  }
  .reveal.is-visible {
    opacity: 1;
    transform: none;
  }
  /* Fade-only variant — no translate. */
  .reveal--no-translate            { transform: none; }
  .reveal--no-translate.is-visible { transform: none; }
}


/* ================================================================================
   ████████████████████████████████████████████████████████████████████████████████
   PAGE COMPOSITION
   ████████████████████████████████████████████████████████████████████████████████

   Sections 40–46. Page-section patterns and card systems. Patterns used
   throughout this group:

     • Subgrid for cross-card row alignment (opt-in via .card-grid--aligned)
     • color-scheme scoping on full sections (.referral-cta, .site-footer)
     • Container queries on every grid that should respond to its container
     • State-token-driven semantic pills (no more hard-coded hex)
     • Logical properties throughout — RTL-ready
     • :has() for content-aware tweaks
     • Concentric radii on nested blocks
   ================================================================================ */


/* ================================================================================
   40. LAYOUT PATTERNS — hero-split, split-section, hero-badge, intro-section
   --------------------------------------------------------------------------------
   • .hero-split    — top-of-page hero. Image + panel landing pattern.
   • .split-section — mid-page sister. Canonical "image + body" shape.
   • .hero-badge    — small absolute-positioned overlay badge.
   • .intro-section — page intro composed from .split-section.
   ================================================================================ */
@layer tokens {
  :root {
    --hero-split-min-bs:        clamp(560px, calc(100svh - 72px), 880px);
    --hero-split-panel-pad-y:   clamp(2rem, 4vw + 1rem, 4rem);
    --hero-split-panel-pad-in:  clamp(1.5rem, 3vw + 1rem, 3.5rem);
    --hero-split-mobile-aspect: 16 / 10;

    --split-image-aspect:        1 / 1;
    --split-image-aspect-mobile: 16 / 10;
    --split-section-gap:         var(--space-2xl);
  }
}

@layer components {

  /* ────────── 40.1 HERO-SPLIT ────────── */
  /* bg + border in :where() so surface composition wins.
     <section class="hero-split surface--brand"> flips to brand-fill hero. */
  :where(.hero-split) {
    background-color: var(--bg-surface);
    border-block-end: var(--border-width-sm) solid var(--border-subtle);
  }
  .hero-split {
    min-block-size: var(--hero-split-min-bs);
    container-type: inline-size;
    container-name: hero-split;
  }

  .hero-split__breadcrumb {
    font-size: var(--text-micro);
    color: var(--text-muted);
    margin: 0 0 var(--space-md);
    line-height: var(--leading-snug);
  }
  .hero-split__breadcrumb a {
    color: inherit;
    text-decoration: none;
    transition: color var(--transition-base);
  }
  .hero-split__breadcrumb a:hover { color: var(--link-color-hover); }
  .hero-split__breadcrumb-sep     { margin-inline: var(--space-xs); opacity: 0.5; }
  .hero-split__breadcrumb-current { color: var(--text-body); }

  .hero-split__inner {
    display: grid;
    grid-template-columns: 3fr 2fr;
    min-block-size: inherit;
  }
  .hero-split--image-start .hero-split__inner { grid-template-columns: 2fr 3fr; }
  .hero-split--image-start .hero-split__inner > .hero-split__panel { order: 2; }
  .hero-split--image-start .hero-split__inner > .hero-split__media { order: 1; }

  .hero-split__panel {
    display: flex;
    flex-direction: column;
    justify-content: center;
    padding-block: var(--hero-split-panel-pad-y);
    padding-inline-start: max(7.5vw, calc((100vw - var(--layout-max)) / 2));
    padding-inline-end:   var(--hero-split-panel-pad-in);
  }
  .hero-split--image-start .hero-split__panel {
    padding-inline-start: var(--hero-split-panel-pad-in);
    padding-inline-end:   max(7.5vw, calc((100vw - var(--layout-max)) / 2));
  }

  .hero-split__eyebrow {
    font-family: var(--font-display);
    font-size: var(--text-eyebrow);
    font-weight: var(--weight-semibold);
    letter-spacing: var(--tracking-wide);
    text-transform: uppercase;
    color: var(--brand-secondary-text);
    margin: 0 0 var(--space-sm);
  }
  .hero-split__h1 {
    font-family: var(--type-display-heading);
    font-size: var(--text-display-h1);
    font-weight: 800;
    line-height: var(--lh-display);
    letter-spacing: var(--tracking-tight);
    color: var(--text-heading);
    margin: 0 0 var(--space-md);
    max-inline-size: none;
    text-wrap: balance;
  }
  .hero-split__lede {
    font-size: var(--text-display-body);
    max-inline-size: 32em;
    margin: 0 0 var(--space-lg);
  }
  .hero-split__actions {
    display: flex;
    gap: var(--space-sm);
    align-items: center;
    flex-wrap: wrap;
  }
  .hero-split__media {
    background-size: cover;
    background-position: center;
    min-block-size: 100%;
  }

  /* Mobile collapse — driven by container width, not viewport */
  @container hero-split (max-width: 48rem) {
    .hero-split { min-block-size: 0; }
    .hero-split__inner { grid-template-columns: 1fr; }
    .hero-split__inner > .hero-split__media { order: 1; }
    .hero-split__inner > .hero-split__panel { order: 2; }
    .hero-split--image-start .hero-split__inner > .hero-split__panel { order: 2; }
    .hero-split--image-start .hero-split__inner > .hero-split__media { order: 1; }
    .hero-split__media {
      aspect-ratio: var(--hero-split-mobile-aspect);
      min-block-size: 0;
    }
    .hero-split__panel {
      padding-block: var(--space-xl);
      padding-inline: var(--space-md);
    }
    .hero-split__actions {
      flex-direction: column;
      align-items: stretch;
      gap: var(--space-xs);
    }
    .hero-split__actions .btn {
      inline-size: 100%;
      justify-content: center;
    }
  }

  /* ────────── 40.2 SPLIT-SECTION ──────────
     Default: text first, image second. Visual: text 60% / image 40%.
     Modifiers: --image-led (image dominant), --image-start (image first),
                --align-start (top-aligned).
     Logical-property naming: --image-start replaces v2's --image-left. */

  .split-section {
    display: grid;
    grid-template-columns: 3fr 2fr;
    gap: var(--split-section-gap);
    align-items: center;
    container-type: inline-size;
    container-name: split-section;
  }
  .split-section--image-led        { grid-template-columns: 2fr 3fr; }
  .split-section--image-start      { grid-template-columns: 2fr 3fr; }
  .split-section--image-start.split-section--image-led { grid-template-columns: 3fr 2fr; }
  .split-section--image-start > :first-child { order: 2; }
  .split-section--image-start > :last-child  { order: 1; }
  .split-section--align-start { align-items: start; }

  .split-section__media {
    margin: 0;
    border-radius: var(--radius-4);
    overflow: clip;
    aspect-ratio: var(--split-image-aspect);
  }
  .split-section__media > img,
  .split-section__media > picture,
  .split-section__media > picture > img {
    inline-size: 100%;
    block-size: 100%;
    object-fit: cover;
    display: block;
  }

  @container split-section (max-width: 64rem) {
    .split-section,
    .split-section--image-led,
    .split-section--image-start,
    .split-section--image-start.split-section--image-led {
      grid-template-columns: 1fr;
      gap: var(--space-xl);
    }
    .split-section--image-start > :first-child,
    .split-section--image-start > :last-child { order: unset; }
    .split-section__media { aspect-ratio: var(--split-image-aspect-mobile); }
  }

  /* ────────── 40.3 HERO-BADGE ──────────
     Small absolute-positioned overlay badge. */

  .hero-badge {
    position: absolute;
    inset-block-start: calc(var(--space-md) * -1);
    inset-inline-end: var(--space-lg);
    background-color: var(--brand-primary);
    color: var(--text-on-brand);
    color-scheme: dark;
    padding-block: var(--space-sm);
    padding-inline: var(--space-md);
    border-radius: var(--radius-4);
    font-weight: var(--weight-bold);
    font-size: var(--text-micro);
    box-shadow: var(--shadow-sm);
    line-height: var(--leading-snug);
    text-align: center;
  }
  .hero-badge strong {
    display: block;
    font-size: var(--text-display-h5);
  }

  /* ────────── 40.4 INTRO-SECTION ──────────
     Composes .split-section. Text + portrait image. */

  .intro-section {
    padding-block: var(--space-struct-y-base);
    background-color: var(--bg-page);
  }
  .intro-text h2 {
    font-size: var(--text-display-h2);
    font-weight: var(--weight-bold);
    line-height: var(--leading-snug);
    margin-block-end: var(--space-md);
  }
  /* :has()-driven margin: paragraph margin only between paragraphs, not after eyebrows */
  .intro-text p:not(:has(+ p)):not(.eyebrow):not(.signpost):not(.preheader) {
    margin-block-end: var(--space-sm);
    line-height: var(--leading-loose);
  }
  .intro-text .btn { margin-block-start: var(--space-sm); }
  .intro-image { aspect-ratio: 4 / 5; }
}


/* ================================================================================
   41. EDITORIAL PATTERNS — pull-quote, breadcrumb, CTA section, related grid
   ================================================================================ */
@layer components {

  /* ────────── 41.1 PULL-QUOTE ────────── */
  .pull-quote {
    position: relative;
    isolation: isolate;
    margin-block: var(--space-lg);
    padding: var(--space-md);
    padding-inline-end: var(--space-sm);
    font-family: var(--font-display);
    font-style: italic;
    font-size: var(--text-display-h4);
    font-weight: var(--weight-bold);
    color: var(--text-body);
    line-height: var(--leading-snug);
    max-inline-size: none;
    text-wrap: balance;
  }
  .pull-quote::before {
    content: '\201C';
    position: absolute;
    inset-block-start: -0.3em;
    inset-inline-start: -0.1em;
    font-family: var(--font-display);
    font-size: 7rem;
    font-weight: var(--weight-bold);
    line-height: 1;
    color: color-mix(in oklch, var(--brand-primary) 18%, transparent);
    pointer-events: none;
    z-index: var(--z-below);
  }

  /* ────────── 41.2 BREADCRUMB ────────── */
  .breadcrumb {
    padding-block: var(--space-sm);
    font-size: var(--text-micro);
    color: var(--text-muted);
  }
  .breadcrumb a {
    color: var(--text-muted);
    text-decoration: none;
    transition: color var(--transition-base);
  }
  .breadcrumb a:hover { color: var(--link-color-hover); }
  .breadcrumb .sep {
    margin-inline: var(--space-xs);
    opacity: 0.5;
    user-select: none;
  }

  /* ────────── 41.3 CTA-SECTION (closing CTA) ──────────
     One closing-CTA pattern across the site. Quieter than a hero — smaller
     padding, lighter bg, smaller heading. Reads as a deliberate full-stop,
     not a second hero competing with the page above.

     Default bg: --bg-surface-alt (a quiet grey panel).
     Brand-fill closing CTA: <section class="cta-section surface--brand">.
     Dark closing CTA:       <section class="cta-section surface--dark">.
     Soft closing CTA:       <section class="cta-section surface--soft">.

     The default background-color is wrapped in :where() so it sits at zero
     specificity — any .surface--* class on the same element wins by
     specificity, and the composition pattern Just Works without project-
     layer overrides. Prior to v3.10.0 the bg was declared directly on
     .cta-section in @layer components, which beat .surface--brand (same
     layer, same specificity, later source order) and forced consumers to
     ship custom overrides to flip the colour. Fixed by lowering the
     default's specificity, not by moving it to a different layer. */

  :where(.cta-section) {
    background-color: var(--bg-surface-alt);
  }
  .cta-section {
    padding-block: var(--space-2xl);
    position: relative;
  }
  .cta-section__layout {
    max-inline-size: 640px;
    margin-inline: auto;
    text-align: center;
  }
  .cta-section__layout .cta-actions { justify-content: center; }

  .cta-text h2 {
    font-size: var(--text-display-h3);
    margin-block-end: var(--space-md);
    text-wrap: balance;
  }
  .cta-text p:not(.eyebrow):not(.signpost):not(.preheader) {
    line-height: var(--leading-loose);
    margin-block-end: var(--space-lg);
    font-size: var(--text-display-body);
    max-inline-size: 32em;
    text-wrap: pretty;
  }
  .cta-text .cta-actions {
    display: flex;
    gap: var(--space-sm);
    align-items: center;
    flex-wrap: wrap;
  }

  /* ────────── 41.4 SECTION UTILITIES ────────── */

  .section-cta-centered {
    margin-block-start: var(--space-lg);
    text-align: center;
  }
  .cta-button-row {
    display: flex;
    gap: var(--space-sm);
    flex-wrap: wrap;
    margin-block-start: var(--space-lg);
  }
  .empty-state {
    text-align: center;
    padding-block: var(--space-xl);
    padding-inline: var(--space-md);
    color: var(--text-muted);
  }
  .image-placeholder {
    inline-size: 100%;
    block-size: 100%;
    background-color: var(--bg-surface-alt);
    display: flex;
    align-items: center;
    justify-content: center;
    color: var(--text-muted);
  }

  /* ────────── 41.5 RELATED CONTENT FOOTER ────────── */
  .related-section {
    padding-block: var(--space-struct-y-base);
    background-color: var(--bg-surface-alt);
  }
  .related-header {
    margin-block-end: var(--space-lg);
  }
  .related-grid {
    display: grid;
    grid-template-columns: repeat(auto-fill, minmax(20rem, 1fr));
    gap: var(--space-lg);
    container-type: inline-size;
    container-name: related-grid;
  }

  /* ────────── 41.6 NARROW CONTAINER ────────── */
  .container--narrow {
    max-inline-size: var(--layout-max-narrow);
    margin-inline: auto;
    padding-inline: var(--space-md);
  }
}


/* ================================================================================
   42. CARD SYSTEMS
   --------------------------------------------------------------------------------
   Three-scale card pattern across content types:
     SECTION (L) — editorial-weight detail blocks
     CARD    (M) — grid tiles
     ROW     (S) — sidebar / mega-style stacks

   Card-on-bg discipline:
     • Cards sit on --bg-page.
     • Sections containing cards sit on --bg-surface-alt.
     • That contrast gap defines the card edge — no borders.

   Subgrid for row alignment: opt in via `.card-grid--aligned` on the parent.
   The grid declares explicit row tracks; each card sets `display: grid;
   grid-template-rows: subgrid; grid-row: span N;`. Result: title bottoms,
   excerpt bottoms, and CTA tops align across siblings.
   ================================================================================ */
@layer components {

  /* ────────── 42.1 CARD-GRID — ALIGNED VARIANT (subgrid) ──────────
     Add .card-grid--aligned to the parent grid. Cards inside opt into
     subgrid via .news-card, .tile-card, etc. The 5-row template matches
     the typical [photo, eyebrow, title, body, footer] structure. */

  .card-grid--aligned {
    grid-template-rows: auto auto auto 1fr auto;
  }
  .card-grid--aligned > .news-card,
  .card-grid--aligned > .tile-card,
  .card-grid--aligned > .card {
    display: grid;
    grid-template-rows: subgrid;
    grid-row: span 5;
  }


  /* ────────── 42.2 TILE-CARD FAMILY (was .svc-* / .prog-*) ──────────
     Generic image+body+arrow card. Three scales. */

  /* SCALE L — TILE-SECTION (full-width editorial) */
  .tile-section { padding-block: var(--space-lg); }
  .tile-section__body {
    display: flex;
    flex-direction: column;
    gap: var(--space-sm);
    align-items: flex-start;
  }
  .tile-section__title {
    font-family: var(--type-display-heading);
    font-size: var(--text-display-h3);
    font-weight: var(--weight-bold);
    line-height: var(--lh-display);
    letter-spacing: var(--tracking-tight);
    margin: 0;
    color: var(--text-heading);
  }
  .tile-section__blurb {
    color: var(--text-muted);
    margin: 0;
    max-inline-size: 60ch;
    font-size: var(--text-display-body);
    line-height: var(--leading-loose);
  }
  .tile-section__cta { margin-block-start: var(--space-xs); }
  .tile-section__media-placeholder {
    display: flex;
    align-items: center;
    justify-content: center;
    inline-size: 100%;
    block-size: 100%;
    color: var(--text-muted);
    font-size: var(--text-micro);
    font-family: var(--font-display);
    letter-spacing: var(--tracking-wide);
    text-transform: uppercase;
    background-color: var(--bg-surface-alt);
  }

  /* SCALE M — TILE-CARD (grid tile) */
  .tile-card {
    --tile-photo-w: 10rem;  /* 160px @ default */
    display: grid;
    grid-template-columns: var(--tile-photo-w) 1fr auto;
    gap: var(--space-sm);
    padding: var(--space-sm);
    background-color: var(--bg-page);
    border-radius: var(--radius-2);
    align-items: center;
    text-decoration: none;
    color: inherit;
    transition:
      transform   var(--transition-base),
      box-shadow  var(--transition-base);
    container-type: inline-size;
    container-name: tile-card;
  }
  .tile-card:hover {
    transform: translateY(var(--card-lift-distance));
    box-shadow: var(--shadow-md);
  }
  /* :has() variant — no photo column */
  .tile-card:not(:has(.tile-card__photo)) {
    grid-template-columns: 1fr auto;
  }
  /* Legacy explicit class still works */
  .tile-card--no-photo { grid-template-columns: 1fr auto; }

  .tile-card__body {
    display: flex;
    flex-direction: column;
    gap: var(--space-2xs);
    min-inline-size: 0;
  }
  .tile-card__title {
    font-family: var(--type-card-heading);
    font-size: var(--text-card-h4);
    font-weight: var(--weight-semibold);
    color: var(--brand-secondary-text);
    margin: 0;
    line-height: var(--leading-snug);
    max-inline-size: none;
  }
  .tile-card__arrow {
    font-family: var(--type-card-heading);
    font-size: var(--text-card-h5);
    color: var(--brand-primary-text);
    font-weight: var(--weight-semibold);
    align-self: center;
    transition: transform var(--transition-base);
  }
  .tile-card:hover .tile-card__arrow {
    transform: translateX(var(--btn-icon-nudge));
  }

  /* Container query — narrow tile collapses to stacked */
  @container tile-card (max-width: 22rem) {
    .tile-card {
      grid-template-columns: 1fr;
      text-align: start;
    }
    .tile-card__photo { display: none; }
    .tile-card__arrow { justify-self: end; }
  }

  /* SCALE S — TILE-ROW (compact list) */
  .tile-row {
    display: grid;
    grid-template-columns: 1fr auto;
    gap: var(--space-2xs) var(--space-sm);
    padding-block: 0.75rem;
    border-block-end: var(--border-width-xs) solid var(--border-subtle);
    text-decoration: none;
    color: inherit;
    align-items: center;
    transition: color var(--transition-base);
  }
  .tile-row:first-of-type { padding-block-start: 0; }
  .tile-row:hover .tile-row__title { color: var(--brand-secondary-text); }
  .tile-row__body {
    display: flex;
    flex-direction: column;
    gap: 0.25rem;
    min-inline-size: 0;
  }
  .tile-row__title {
    font-family: var(--type-card-heading);
    font-size: var(--text-card-h6);
    font-weight: var(--weight-semibold);
    color: var(--text-body);
    transition: color var(--transition-base);
    line-height: var(--leading-snug);
  }
  .tile-row__meta {
    font-size: var(--text-card-h6);
    color: var(--text-muted);
  }
  .tile-row__arrow {
    font-family: var(--type-card-heading);
    color: var(--brand-primary-text);
    font-weight: var(--weight-semibold);
  }


  /* ────────── 42.3 NEWS-CARD FAMILY ──────────
     Image-led vertical card for posts. Three scales. */

  /* SCALE L — SECTION */
  .news-section { padding-block: var(--space-lg); }
  .news-section__body {
    display: flex;
    flex-direction: column;
    gap: var(--space-sm);
    align-items: flex-start;
  }
  .news-section__title {
    font-family: var(--type-display-heading);
    font-size: var(--text-display-h2);
    font-weight: var(--weight-bold);
    line-height: var(--lh-display);
    letter-spacing: var(--tracking-tight);
    margin: 0;
    color: var(--text-heading);
  }
  .news-section__title a { color: inherit; text-decoration: none; }
  .news-section__excerpt {
    color: var(--text-muted);
    margin: 0;
    max-inline-size: 60ch;
    font-size: var(--text-display-body);
    line-height: var(--leading-loose);
  }
  .news-section__cta { margin-block-start: var(--space-xs); }

  /* SCALE M — NEWS-CARD (image-top vertical) */
  .news-card {
    display: flex;
    flex-direction: column;
    background-color: var(--bg-page);
    border-radius: var(--card-radius);
    overflow: clip;
    text-decoration: none;
    color: inherit;
    transition:
      transform   var(--transition-base),
      box-shadow  var(--transition-base);
  }
  .news-card:hover {
    transform: translateY(var(--card-lift-distance));
    box-shadow: var(--shadow-md);
  }
  .news-card__photo {
    position: relative;
    background-color: var(--bg-surface-alt);
    overflow: clip;
    aspect-ratio: 4 / 3;
  }
  .news-card__photo img {
    inline-size: 100%;
    block-size: 100%;
    object-fit: cover;
    display: block;
  }
  .news-card__body {
    padding: var(--space-md);
    padding-block-start: var(--space-sm);
    display: flex;
    flex-direction: column;
    gap: var(--space-xs);
    flex: 1;
  }
  .news-card__title {
    font-family: var(--type-card-heading);
    font-size: var(--text-card-h4);
    font-weight: var(--weight-semibold);
    color: var(--brand-secondary-text);
    margin: 0;
    line-height: var(--leading-snug);
    max-inline-size: none;
    text-wrap: balance;
  }
  .news-card__excerpt {
    margin: 0;
    font-size: var(--text-card-body);
    color: var(--text-muted);
    line-height: var(--leading-normal);
    display: -webkit-box;
    -webkit-line-clamp: 3;
            line-clamp: 3;
    -webkit-box-orient: vertical;
    overflow: hidden;
  }

  /* SCALE S — NEWS-ROW */
  .news-row {
    display: grid;
    grid-template-columns: 1fr auto;
    gap: var(--space-2xs) var(--space-sm);
    padding-block: 0.75rem;
    border-block-end: var(--border-width-xs) solid var(--border-subtle);
    text-decoration: none;
    color: inherit;
    align-items: center;
    transition: color var(--transition-base);
  }
  .news-row:first-of-type { padding-block-start: 0; }
  .news-row:hover .news-row__title { color: var(--brand-secondary-text); }
  .news-row__body {
    display: flex;
    flex-direction: column;
    gap: 0.25rem;
    min-inline-size: 0;
  }
  .news-row .news-eyebrow { font-size: 0.7rem; letter-spacing: var(--tracking-wide); }
  .news-row__title {
    font-family: var(--type-card-heading);
    font-size: var(--text-card-h6);
    font-weight: var(--weight-semibold);
    color: var(--text-body);
    transition: color var(--transition-base);
    line-height: var(--leading-snug);
  }
  .news-row__arrow {
    font-family: var(--type-card-heading);
    color: var(--brand-primary-text);
    font-weight: var(--weight-semibold);
  }


  /* ────────── 42.4 PERSON / TEAM CARDS ──────────
     Round avatar + name + role. Used on team / about / related-contacts. */

  .person-card {
    text-align: center;
    text-decoration: none;
    color: inherit;
    padding-block: var(--space-sm);
    padding-inline: var(--space-xs);
  }
  .person-card__photo {
    inline-size: 6rem;
    block-size: 6rem;
    border-radius: var(--radius-circle);
    background-color: var(--bg-surface-alt);
    margin: 0 auto var(--space-sm);
    overflow: clip;
    border: var(--border-width-md) solid var(--bg-page);
    box-shadow: var(--shadow-sm);
    transition:
      transform   var(--transition-base),
      box-shadow  var(--transition-base);
  }
  .person-card:hover .person-card__photo {
    transform: scale(1.06);
    box-shadow: var(--shadow-md);
  }
  .person-card__photo img {
    inline-size: 100%;
    block-size: 100%;
    object-fit: cover;
  }
  .person-card__name {
    font-size: var(--text-card-body);
    font-weight: var(--weight-bold);
    margin-block-end: 0;
    max-inline-size: none;
  }
  .person-card__role {
    font-size: var(--text-eyebrow);
    color: var(--brand-primary-text);
    font-weight: var(--weight-semibold);
    margin: 0;
    max-inline-size: none;
  }

  /* bg in :where() so surface composition wins. */
  :where(.people-cta) {
    background-color: var(--bg-surface);
  }
  .people-cta {
    border-radius: var(--radius-4);
    padding-block: var(--space-lg);
    padding-inline: var(--space-xl);
    display: flex;
    align-items: center;
    gap: var(--space-md);
  }
  .people-cta p {
    font-size: var(--text-card-body);
    line-height: var(--leading-loose);
    margin: 0;
    max-inline-size: none;
  }

  .team-card {
    text-align: center;
    text-decoration: none;
    color: inherit;
    padding: var(--space-sm);
  }
  .team-card__photo {
    inline-size: 6rem;
    block-size: 6rem;
    border-radius: var(--radius-circle);
    overflow: clip;
    margin: 0 auto var(--space-sm);
    border: var(--border-width-sm) solid var(--bg-page);
    box-shadow: var(--shadow-sm);
  }
  .team-card__photo img {
    inline-size: 100%;
    block-size: 100%;
    object-fit: cover;
  }
  .team-card h3 {
    font-size: var(--text-card-body);
    font-weight: var(--weight-bold);
    margin-block-end: 0;
    max-inline-size: none;
  }
  .team-card .role {
    font-size: var(--text-micro);
    color: var(--brand-primary-text);
    font-weight: var(--weight-semibold);
  }


  /* ────────── 42.5 STORY-CARD ──────────
     Outcome / case-study card. Hover lifts to --bg-page. */

  .stories {
    padding-block: var(--space-struct-y-hero);
    background-color: var(--bg-surface-alt);
  }
  .stories__header {
    text-align: center;
    margin-block-end: var(--space-xl);
  }
  .stories__header h2 {
    font-size: var(--text-display-h2);
    font-weight: var(--weight-bold);
    line-height: var(--leading-snug);
    text-wrap: balance;
  }
  .stories__grid {
    display: grid;
    grid-template-columns: repeat(auto-fit, minmax(13.75rem, 1fr));
    gap: var(--space-lg);
  }
  .story-card {
    background-color: transparent;
    border-radius: var(--card-radius);
    padding-block: var(--space-lg);
    padding-inline: var(--space-md);
    text-align: start;
    text-decoration: none;
    color: inherit;
    transition:
      transform        var(--transition-base),
      background-color var(--transition-base),
      box-shadow       var(--transition-base);
  }
  .story-card:hover {
    background-color: var(--bg-page);
    transform: translateY(-4px);
    box-shadow: var(--shadow-md);
  }
  .story-card__avatar {
    inline-size: 6rem;
    block-size: 6rem;
    border-radius: var(--radius-circle);
    margin: 0 0 var(--space-sm);
    background-color: var(--bg-surface-alt);
    overflow: clip;
  }
  .story-card__avatar img {
    inline-size: 100%;
    block-size: 100%;
    object-fit: cover;
  }
  .story-card h3 {
    font-size: var(--text-card-h3);
    font-weight: var(--weight-bold);
    margin-block-end: var(--space-xs);
    max-inline-size: none;
  }
  .story-card p {
    font-size: var(--text-micro);
    line-height: var(--leading-loose);
    margin-block-end: var(--space-sm);
  }


  /* ────────── 42.6 TESTIMONIAL-CARD ────────── */
  .testimonials__grid {
    display: grid;
    grid-template-columns: repeat(auto-fit, minmax(17.5rem, 1fr));
    gap: var(--space-lg);
  }
  .testimonial-card {
    background-color: var(--bg-page);
    border-radius: var(--card-radius);
    padding-block: var(--space-md);
    padding-inline: var(--space-lg);
  }
  .testimonial-card__stars {
    color: var(--state-warning);
    font-size: 1rem;
    margin-block-end: var(--space-sm);
    letter-spacing: 2px;
  }
  .testimonial-card blockquote {
    font-size: var(--text-card-body);
    line-height: var(--leading-loose);
    font-style: normal;
    margin: 0 0 var(--space-sm) 0;
  }
  .testimonial-card cite {
    font-size: var(--text-micro);
    font-weight: var(--weight-semibold);
    color: var(--text-body);
    font-style: normal;
  }
}


/* ================================================================================
   43. ARCHIVE LAYOUTS
   --------------------------------------------------------------------------------
   Hero / body / 3-up grid for any list view (CPT archives, category lists,
   directory pages). Composes naturally with .news-card / .tile-card /
   .person-card in .archive-grid.
   ================================================================================ */
@layer components {

  /* bg in :where() so surface composition wins. */
  :where(.archive-hero) {
    background-color: var(--bg-surface);
  }
  .archive-hero {
    padding-block: var(--space-struct-y-base);
    text-align: center;
  }
  .archive-hero h1 {
    font-size: var(--text-display-h1);
    font-weight: var(--weight-bold);
    margin-block-end: var(--space-sm);
    text-wrap: balance;
  }
  .archive-hero p:not(.eyebrow):not(.signpost):not(.preheader) {
    font-size: var(--text-display-body);
    color: var(--text-muted);
    max-inline-size: 600px;
    margin-inline: auto;
    text-wrap: pretty;
  }

  .archive-body {
    padding-block: var(--space-struct-y-base);
    background-color: var(--bg-surface-alt);
  }

  .archive-grid {
    display: grid;
    grid-template-columns: repeat(auto-fill, minmax(20rem, 1fr));
    gap: var(--space-lg);
    margin-block-start: var(--space-xl);
    container-type: inline-size;
    container-name: archive-grid;
  }
}


/* ================================================================================
   44. PAGE-SECTION PATTERNS — contact, ways, faq, mission, timeline
   ================================================================================ */
@layer components {

  /* ────────── 44.1 CONTACT LAYOUT ──────────
     Two-column: contact-info (start) + contact-form (end). */

  .contact-layout {
    display: grid;
    grid-template-columns: 1fr 1fr;
    gap: var(--space-2xl);
    padding-block: var(--space-struct-y-base);
    container-type: inline-size;
    container-name: contact;
  }
  .contact-info h2 {
    font-size: var(--text-display-h2);
    font-weight: var(--weight-bold);
    margin-block-end: var(--space-md);
    text-wrap: balance;
  }
  .contact-info p:not(.eyebrow):not(.signpost):not(.preheader) {
    line-height: var(--leading-loose);
    margin-block-end: var(--space-lg);
  }
  .contact-detail {
    display: flex;
    align-items: center;
    gap: var(--space-sm);
    padding-block: var(--space-sm);
    border-block-end: var(--border-width-xs) solid var(--border-subtle);
  }
  .contact-detail:first-of-type {
    border-block-start: var(--border-width-xs) solid var(--border-subtle);
  }
  .contact-detail svg { flex-shrink: 0; }
  .contact-detail a {
    color: var(--link-color);
    text-decoration: none;
    font-weight: var(--weight-semibold);
  }
  .contact-detail a:hover { color: var(--link-color-hover); }
  /* bg in :where() so surface composition wins. */
  :where(.contact-form) {
    background-color: var(--bg-surface);
  }
  .contact-form {
    border-radius: var(--radius-4);
    padding: var(--space-xl);
  }
  @container contact (max-width: 64rem) {
    .contact-layout { grid-template-columns: 1fr; }
  }


  /* ────────── 44.2 WAYS / WAY-CARD ──────────
     2- or 3-card layout for major action choices ("Donate / Volunteer /
     Partner"). Body flex-grows so CTA pins to bottom across mismatched
     body lengths. */

  .ways {
    padding-block: var(--space-3xl);
    background-color: var(--bg-surface-alt);
    position: relative;
    overflow: clip;
  }
  .ways__header {
    text-align: center;
    margin-block-end: var(--space-xl);
  }
  .ways__header h2 {
    font-size: var(--text-display-h2);
    font-weight: var(--weight-bold);
    color: var(--text-heading);
    line-height: var(--leading-snug);
    margin-block-end: var(--space-sm);
    text-wrap: balance;
  }
  .ways__header p:not(.eyebrow):not(.signpost):not(.preheader) {
    font-size: var(--text-display-body);
    max-inline-size: 600px;
    margin-inline: auto;
    text-wrap: pretty;
  }
  .ways__grid {
    display: grid;
    grid-template-columns: 1fr 1fr;
    gap: var(--space-lg);
    max-inline-size: 880px;
    margin-inline: auto;
    container-type: inline-size;
    container-name: ways;
  }
  @container ways (max-width: 48rem) {
    .ways__grid { grid-template-columns: 1fr; }
  }

  .way-card {
    border-radius: var(--card-radius);
    overflow: clip;
    text-align: center;
    background-color: var(--bg-page);
    display: flex;
    flex-direction: column;
    transition:
      transform   var(--transition-base),
      box-shadow  var(--transition-base);
    text-decoration: none;
    color: inherit;
  }
  .way-card:hover {
    transform: translateY(-4px);
    box-shadow: var(--shadow-md);
  }
  .way-card__illustration {
    inline-size: 100%;
    aspect-ratio: 2 / 1;
    overflow: clip;
    display: block;
    background-color: var(--bg-surface-alt);
  }
  .way-card__illustration img {
    inline-size: 100%;
    block-size: 100%;
    object-fit: cover;
    display: block;
  }
  .way-card__body {
    padding-block: var(--space-xl);
    padding-inline: var(--space-lg);
    display: flex;
    flex-direction: column;
    align-items: center;
    flex: 1;
  }
  .way-card h3 {
    font-size: var(--text-display-h3);
    font-weight: var(--weight-bold);
    margin-block-end: var(--space-sm);
  }
  .way-card p {
    margin-block-end: var(--space-lg);
    flex: 1;
    line-height: var(--leading-loose);
  }


  /* ────────── 44.3 FAQ ──────────
     Native <details>/<summary> with +/− affordance.
     Animates open via grid-template-rows like the .accordion does. */

  .faq details {
    border-block-end: var(--border-width-xs) solid var(--border-subtle);
  }
  .faq summary {
    padding-block: var(--space-md);
    font-weight: var(--weight-semibold);
    font-size: var(--text-prose-h6);
    cursor: pointer;
    list-style: none;
    display: flex;
    align-items: center;
    justify-content: space-between;
  }
  .faq summary::-webkit-details-marker { display: none; }
  .faq summary::after {
    content: '+';
    font-size: var(--text-prose-h3);
    font-weight: var(--weight-regular);
    transition: transform var(--transition-base);
    flex-shrink: 0;
    margin-inline-start: var(--space-sm);
  }
  .faq details[open] summary::after { content: '−'; }
  .faq details .faq__answer {
    padding-block-end: var(--space-md);
    color: var(--text-muted);
    font-size: var(--text-card-body);
    line-height: var(--leading-loose);
  }


  /* ────────── 44.4 MISSION SECTION ──────────
     Vision/Mission/Values pages. Composes .split-section. */

  .mission-section {
    padding-block: var(--space-struct-y-base);
    background-color: var(--bg-surface);
  }
  .mission-text h2 {
    font-size: var(--text-display-h2);
    font-weight: var(--weight-bold);
    line-height: var(--leading-snug);
    margin-block-end: var(--space-lg);
    text-wrap: balance;
  }
  .mission-text h3 {
    font-size: var(--text-micro);
    font-weight: var(--weight-bold);
    text-transform: uppercase;
    letter-spacing: var(--tracking-wide);
    color: var(--brand-primary-text);
    margin-block: var(--space-md) var(--space-xs);
  }
  .mission-text p:not(.eyebrow):not(.signpost):not(.preheader) {
    line-height: var(--leading-loose);
    font-size: var(--text-card-body);
    margin-block-end: var(--space-md);
  }
  .mission-image { aspect-ratio: 4 / 5; }


  /* ────────── 44.5 TIMELINE ──────────
     Vertical timeline for organisational heritage / story.
     JS hook: `.js-timeline-ready` is added to .timeline-track once the
     IntersectionObserver wires up. Items fade in as the user scrolls.
     Before JS runs, items are fully visible (graceful degradation).
     Reduced-motion is honoured globally + locally. */

  .timeline-section {
    padding-block: var(--space-struct-y-base);
    background-color: var(--bg-page);
  }
  .timeline-header {
    display: flex;
    flex-direction: column;
    align-items: center;
    text-align: center;
    margin-block-end: var(--space-2xl);
    gap: var(--space-xs);
  }
  .timeline-header h2 {
    font-size: var(--text-display-h2);
    font-weight: var(--weight-bold);
    text-wrap: balance;
  }
  .timeline-header p:not(.eyebrow):not(.signpost):not(.preheader) {
    color: var(--text-muted);
    max-inline-size: 60ch;
    font-size: var(--text-card-body);
    line-height: var(--leading-loose);
    margin: 0;
  }

  .timeline-track {
    position: relative;
    max-inline-size: 800px;
    margin-inline: auto;
    container-type: inline-size;
    container-name: timeline;
  }
  .timeline-track::before {
    content: '';
    position: absolute;
    inset-inline-start: 50%;
    inset-block: 0;
    inline-size: var(--border-width-sm);
    background-color: var(--border-subtle);
    transform: translateX(-50%);
  }
  .timeline-progress {
    position: absolute;
    inset-inline-start: 50%;
    inset-block-start: 0;
    inline-size: var(--border-width-sm);
    block-size: 0;
    background-color: var(--brand-primary);
    transform: translateX(-50%);
    transition: height var(--transition-base);
    z-index: var(--z-raised);
  }
  .timeline-ball {
    position: absolute;
    inset-inline-start: 50%;
    inset-block-start: 0;
    inline-size: 16px;
    block-size: 16px;
    border-radius: var(--radius-circle);
    background-color: var(--brand-primary);
    border: var(--border-width-sm) solid var(--bg-page);
    box-shadow:
      0 0 0 var(--border-width-xs) var(--brand-primary),
      var(--shadow-sm);
    transform: translateX(-50%);
    z-index: 3;
    opacity: 0;
    transition: top var(--transition-base);
  }

  .tl-item {
    position: relative;
    display: grid;
    grid-template-columns: 1fr 1fr;
    gap: var(--space-xl);
    padding-block-end: var(--space-xl);
  }
  .js-timeline-ready .tl-item {
    opacity: 0.25;
    transform: translateY(16px);
    transition:
      opacity   var(--transition-base),
      transform var(--transition-base);
  }
  .tl-item:last-child { padding-block-end: 0; }
  .js-timeline-ready .tl-item.is-reached {
    opacity: 1;
    transform: translateY(0);
  }

  .tl-item::before {
    content: '';
    position: absolute;
    inset-inline-start: 50%;
    inset-block-start: 6px;
    inline-size: 12px;
    block-size: 12px;
    border-radius: var(--radius-circle);
    background-color: var(--border-medium);
    border: var(--border-width-sm) solid var(--bg-page);
    box-shadow: 0 0 0 var(--border-width-xs) var(--border-medium);
    transform: translateX(-50%);
    z-index: 2;
    transition:
      background-color var(--transition-base),
      box-shadow       var(--transition-base);
  }
  .js-timeline-ready .tl-item.is-reached::before {
    background-color: var(--brand-primary);
    box-shadow: 0 0 0 var(--border-width-xs) var(--brand-primary);
  }

  .tl-item .tl-content {
    text-align: end;
    padding-inline-end: var(--space-sm);
  }
  .tl-item:nth-child(even) .tl-content {
    order: 2;
    text-align: start;
    padding-inline-end: 0;
    padding-inline-start: var(--space-sm);
  }
  .tl-item:nth-child(even) .tl-spacer { order: 1; }

  .tl-year {
    font-size: var(--text-micro);
    font-weight: var(--weight-bold);
    color: var(--brand-primary-text);
    margin-block-end: var(--space-xs);
  }
  .tl-content h3 {
    font-size: var(--text-card-h3);
    font-weight: var(--weight-bold);
    margin-block-end: var(--space-xs);
    max-inline-size: none;
  }
  .tl-content p {
    font-size: var(--text-micro);
    line-height: var(--leading-loose);
  }

  @container timeline (max-width: 40rem) {
    .tl-item {
      grid-template-columns: 1fr;
      gap: 0;
      padding-inline-start: 32px;
    }
    .tl-item .tl-spacer { display: none; }
    .tl-item .tl-content {
      text-align: start;
      padding: 0;
    }
    .tl-item:nth-child(even) .tl-content { order: unset; }
    .timeline-track::before { inset-inline-start: 8px; transform: none; }
    .timeline-progress      { inset-inline-start: 8px; transform: none; }
    .timeline-ball          { inset-inline-start: 8px; }
    .tl-item::before        { inset-inline-start: 8px; transform: translateX(-50%); }
  }

  @media (prefers-reduced-motion: reduce) {
    .js-timeline-ready .tl-item { opacity: 1; transform: none; transition: none; }
    .timeline-ball { display: none; }
    .js-timeline-ready .tl-item::before {
      background-color: var(--brand-primary);
      box-shadow: 0 0 0 var(--border-width-xs) var(--brand-primary);
    }
  }
}


/* ================================================================================
   45. ACCESSIBILITY-FIRST PATTERNS
   --------------------------------------------------------------------------------
   Generic accessibility layers — usable on any project. Designed-for-Rathbone
   originally, refactored generic in v3.

     45.1  .easy-read         — LD-friendly secondary view
     45.2  .status-pill       — Active / Paused / Closed (state-token-driven)
     45.3  .eligibility-panel — at-a-glance "is this for them?" panel
     45.4  .referral-cta      — clear pathway block (auto-darkens via color-scheme)
     45.5  .funder-logo-strip — greyscale-on-rest trust-signal logo row
     45.6  .report-feature    — featured PDF download card
   ================================================================================ */
@layer components {

  /* ────────── 45.1 EASY READ ──────────
     Signposted secondary content layer. Larger type, generous line-height,
     warm bg with accent border-inline-start as visual signpost. */

  /* bg in :where() so surface composition wins. The accent border
     stays at full specificity — it's identity ("easy-read marker"),
     not surface context. */
  :where(.easy-read) {
    background-color: var(--bg-surface-alt);
  }
  .easy-read {
    border-inline-start: var(--border-width-md) solid var(--brand-secondary);
    border-end-end-radius: var(--radius-4);
    border-start-end-radius: var(--radius-4);
    padding-block: var(--space-xl);
    padding-inline: var(--space-lg);
    margin-block: var(--space-2xl);
  }
  .easy-read__eyebrow {
    display: block;
    font-family: var(--type-eyebrow);
    font-size: var(--text-1);
    font-weight: var(--weight-bold);
    text-transform: uppercase;
    letter-spacing: var(--tracking-wide);
    color: var(--brand-secondary-text);
    margin: 0 0 var(--space-sm);
  }
  .easy-read__heading {
    font-family: var(--type-display-heading);
    font-size: var(--text-4);
    font-weight: var(--weight-bold);
    line-height: var(--leading-snug);
    color: var(--text-heading);
    margin: 0 0 var(--space-md);
    max-inline-size: none;
    text-wrap: balance;
  }
  .easy-read__summary {
    font-size: var(--text-2);
    line-height: var(--leading-loose);
    color: var(--text-body);
    margin: 0 0 var(--space-md);
    max-inline-size: 60ch;
  }
  .easy-read__summary strong {
    color: var(--text-heading);
    font-weight: var(--weight-bold);
  }
  .easy-read__points {
    list-style: none;
    padding: 0;
    margin: 0;
    display: flex;
    flex-direction: column;
    gap: var(--space-sm);
  }
  .easy-read__points li {
    font-size: var(--text-2);
    line-height: var(--leading-loose);
    color: var(--text-body);
    padding-inline-start: 2rem;
    position: relative;
    max-inline-size: 60ch;
  }
  .easy-read__points li::before {
    content: '';
    position: absolute;
    inset-inline-start: 0;
    inset-block-start: 0.55em;
    inline-size: 12px;
    block-size: 12px;
    border-radius: var(--radius-circle);
    background-color: var(--brand-secondary);
  }


  /* ────────── 45.2 STATUS-PILL ──────────
     Active / Paused / Closed — wired to state tokens, not hard-coded hex.
     Light/dark mode auto-handled by light-dark() inside state tokens. */

  .status-pill {
    display: inline-flex;
    align-items: center;
    gap: 0.4rem;
    padding-block: 0.35rem;
    padding-inline: 0.75rem;
    border-radius: var(--radius-pill);
    font-family: var(--font-body);
    font-size: var(--text-_1);
    font-weight: var(--weight-semibold);
    letter-spacing: var(--tracking-normal);
    white-space: nowrap;
    background-color: var(--bg-subtle);
    color: var(--text-body);
  }
  .status-pill::before {
    content: '';
    inline-size: 8px;
    block-size: 8px;
    border-radius: var(--radius-circle);
    background-color: currentColor;
    flex-shrink: 0;
  }
  .status-pill--active {
    background-color: var(--state-success-soft);
    color: var(--state-success-text);
  }
  .status-pill--paused {
    background-color: var(--state-warning-soft);
    color: var(--state-warning-text);
  }
  .status-pill--closed {
    background-color: var(--state-error-soft);
    color: var(--state-error-text);
  }


  /* ────────── 45.3 ELIGIBILITY PANEL ──────────
     dt/dd grid in a soft surface. */

  /* bg in :where() so surface composition wins. */
  :where(.eligibility-panel) {
    background-color: var(--bg-surface-alt);
  }
  .eligibility-panel {
    border-radius: var(--radius-4);
    padding-block: var(--space-lg);
    padding-inline: var(--space-xl);
    margin-block: var(--space-lg);
    container-type: inline-size;
    container-name: eligibility;
  }
  .eligibility-panel__heading {
    font-family: var(--type-display-heading);
    font-size: var(--text-card-h3);
    font-weight: var(--weight-bold);
    color: var(--text-heading);
    margin: 0 0 var(--space-md);
    max-inline-size: none;
    text-wrap: balance;
  }
  .eligibility-panel__list {
    display: grid;
    grid-template-columns: max-content 1fr;
    gap: var(--space-sm) var(--space-md);
    margin: 0;
  }
  .eligibility-panel__list dt {
    font-family: var(--font-body);
    font-weight: var(--weight-bold);
    color: var(--brand-secondary-text);
    font-size: var(--text-1);
    text-transform: uppercase;
    letter-spacing: var(--tracking-wide);
    align-self: baseline;
  }
  .eligibility-panel__list dd {
    margin: 0;
    font-size: var(--text-card-body);
    line-height: var(--leading-loose);
    color: var(--text-body);
    max-inline-size: none;
  }
  @container eligibility (max-width: 40rem) {
    .eligibility-panel__list {
      grid-template-columns: 1fr;
      gap: var(--space-xs) 0;
    }
    .eligibility-panel__list dd { margin-block-end: var(--space-sm); }
  }


  /* ────────── 45.4 REFERRAL CTA ──────────
     Brand-coloured block with eyebrow + heading + body + grid of action
     routes. color-scheme: dark scopes light-dark() inside to dark mode,
     so all nested tokens resolve for white-on-brand readability. */

  .referral-cta {
    background-color: var(--brand-primary);
    color: var(--text-on-brand);
    color-scheme: dark;
    border-radius: var(--radius-4);
    padding: var(--space-xl);
    margin-block: var(--space-2xl);
  }
  .referral-cta__eyebrow {
    display: block;
    font-family: var(--type-eyebrow);
    font-size: var(--text-1);
    font-weight: var(--weight-bold);
    text-transform: uppercase;
    letter-spacing: var(--tracking-wide);
    color: var(--brand-accent);
    margin: 0 0 var(--space-sm);
  }
  .referral-cta__heading {
    font-family: var(--type-display-heading);
    font-size: var(--text-display-h3);
    font-weight: var(--weight-bold);
    color: var(--text-on-brand);
    margin: 0 0 var(--space-md);
    max-inline-size: none;
    text-wrap: balance;
  }
  .referral-cta__body {
    font-size: var(--text-display-body);
    line-height: var(--leading-loose);
    color: color-mix(in oklch, var(--text-on-brand) 90%, transparent);
    margin: 0 0 var(--space-lg);
    max-inline-size: 60ch;
    text-wrap: pretty;
  }
  .referral-cta__routes {
    display: grid;
    grid-template-columns: repeat(auto-fit, minmax(12.5rem, 1fr));
    gap: var(--space-md);
    margin: 0 0 var(--space-lg);
    list-style: none;
    padding: 0;
  }
  .referral-cta__routes li {
    color: color-mix(in oklch, var(--text-on-brand) 90%, transparent);
    font-size: var(--text-1);
    line-height: var(--leading-normal);
    max-inline-size: none;
  }
  .referral-cta__routes li strong {
    display: block;
    color: var(--brand-accent);
    font-size: var(--text-_1);
    text-transform: uppercase;
    letter-spacing: var(--tracking-wide);
    margin-block-end: 0.25rem;
  }
  .referral-cta__actions {
    display: flex;
    gap: var(--space-sm);
    flex-wrap: wrap;
  }


  /* ────────── 45.5 FUNDER LOGO STRIP ──────────
     Greyscale-on-rest, full-colour-on-hover trust-signal logo row. */

  /* bg + border in :where() so surface composition wins.
     The visual context (bg, top/bottom rule) yields to the container's
     surface; component contract (padding, container query) stays at
     normal specificity. */
  :where(.funder-logo-strip) {
    background-color: var(--bg-page);
    border-block: var(--border-width-xs) solid var(--border-subtle);
  }
  .funder-logo-strip {
    padding-block: var(--space-2xl);
    container-type: inline-size;
    container-name: funder-strip;
  }
  .funder-logo-strip__eyebrow {
    display: block;
    text-align: center;
    font-family: var(--type-eyebrow);
    font-size: var(--text-1);
    font-weight: var(--weight-bold);
    text-transform: uppercase;
    letter-spacing: var(--tracking-wide);
    color: var(--text-muted);
    margin: 0 0 var(--space-lg);
  }
  .funder-logo-strip__row {
    display: flex;
    flex-wrap: wrap;
    justify-content: center;
    align-items: center;
    gap: var(--space-xl);
    max-inline-size: 1100px;
    margin-inline: auto;
    padding-inline: var(--space-md);
  }
  .funder-logo-strip__logo {
    flex: 0 0 auto;
    max-block-size: 56px;
    inline-size: auto;
    filter: grayscale(40%) opacity(0.75);
    transition: filter var(--transition-base);
  }
  .funder-logo-strip__logo:hover {
    filter: grayscale(0%) opacity(1);
  }
  @container funder-strip (max-width: 48rem) {
    .funder-logo-strip__row { gap: var(--space-md); }
    .funder-logo-strip__logo { max-block-size: 40px; }
  }


  /* ────────── 45.6 REPORT FEATURE ──────────
     Featured PDF download card for Annual / Impact reports. */

  .report-feature {
    display: grid;
    grid-template-columns: 12.5rem 1fr;
    gap: var(--space-xl);
    align-items: center;
    background-color: var(--bg-surface);
    border-radius: var(--card-radius);
    padding: var(--space-xl);
    text-decoration: none;
    color: inherit;
    transition:
      transform   var(--transition-base),
      box-shadow  var(--transition-base);
    container-type: inline-size;
    container-name: report-feature;
  }
  .report-feature:hover {
    transform: translateY(-2px);
    box-shadow: var(--shadow-md);
  }
  .report-feature__thumb {
    aspect-ratio: 3 / 4;
    background-color: var(--bg-surface-alt);
    border-radius: var(--radius-2);
    overflow: clip;
    box-shadow: var(--shadow-sm);
  }
  .report-feature__thumb img {
    inline-size: 100%;
    block-size: 100%;
    object-fit: cover;
    display: block;
  }
  .report-feature__eyebrow {
    display: block;
    font-family: var(--type-eyebrow);
    font-size: var(--text-0);
    font-weight: var(--weight-bold);
    text-transform: uppercase;
    letter-spacing: var(--tracking-wide);
    color: var(--brand-secondary-text);
    margin: 0 0 var(--space-xs);
  }
  .report-feature__heading {
    font-family: var(--type-card-heading);
    font-size: var(--text-card-h3);
    font-weight: var(--weight-bold);
    color: var(--text-heading);
    margin: 0 0 var(--space-sm);
    max-inline-size: none;
    text-wrap: balance;
  }
  .report-feature__summary {
    font-size: var(--text-card-body);
    line-height: var(--leading-loose);
    color: var(--text-muted);
    margin: 0 0 var(--space-md);
    max-inline-size: none;
  }
  .report-feature__meta {
    display: flex;
    align-items: center;
    gap: var(--space-sm);
    font-size: var(--text-_1);
    color: var(--text-muted);
  }
  .report-feature__meta-item {
    display: inline-flex;
    align-items: center;
    gap: 0.35rem;
  }
  @container report-feature (max-width: 40rem) {
    .report-feature {
      grid-template-columns: 1fr;
      padding: var(--space-lg);
    }
    .report-feature__thumb { max-inline-size: 12.5rem; }
  }
}


/* ================================================================================
   46. BEM SITE NAV + DROPDOWN + MOBILE MENU + FOOTER
   --------------------------------------------------------------------------------
   46.1  .site-nav__*       — top nav with About-style dropdown
   46.2  Mobile menu        — hamburger overlay (≤900px page-level breakpoint)
   46.3  Body scroll lock   — pairs with mobile menu open state
   46.4  .site-footer__*    — three-column footer (auto-flips dark via color-scheme)

   Page-level breakpoint at 900px stays as @media query (this is genuinely
   a viewport-level decision: the whole header layout changes between
   desktop and mobile. Cards-inside-nav still get container queries.)

   As of v3.12.0, Section 38's mega-menu collapse uses the same 900px
   breakpoint — both site-nav patterns now share a single mobile-mode
   threshold. Earlier versions had Section 38 at 1024px and Section 46
   at 900px, creating an awkward 901–1024px band on mega-equipped navs.
   ================================================================================ */
@layer tokens {
  :root {
    /* Site-nav dropdown shadow. Sibling token to --menu-shadow (the
       .dropdown-menu standalone component, Section 58) and --modal-shadow
       (Section 48). Defaults to --shadow-md — slightly tighter than a
       free-floating popover, sized for a hover/focus submenu close to its
       trigger. Brands can retune in @layer brand without redeclaring the
       full box-shadow stack. */
    --dropdown-shadow: var(--shadow-md);
  }
}
@layer components {

  /* ────────── 46.1 SITE NAV — BEM ────────── */

  .site-nav {
    background-color: var(--bg-page);
    border-block-end: var(--border-width-xs) solid var(--border-subtle);
  }
  .site-nav__inner {
    display: flex;
    align-items: center;
    gap: 2rem;
    max-inline-size: var(--layout-max);
    margin-inline: auto;
    padding-block: 1.25rem;
    padding-inline: 1.5rem;
  }
  .site-nav__brand {
    display: inline-flex;
    align-items: center;
    flex-shrink: 0;
  }
  .site-nav__logo {
    block-size: auto;
    max-block-size: 48px;
    inline-size: auto;
  }
  .site-nav__menu {
    display: flex;
    list-style: none;
    gap: 1.75rem;
    margin: 0 0 0 auto;
    padding: 0;
  }
  .site-nav__item { margin: 0; }
  .site-nav__link {
    font-family: var(--font-body);
    font-weight: var(--weight-medium);
    color: var(--brand-primary-text);
    text-decoration: none;
    font-size: var(--text-1);
    transition: color var(--duration-fast) var(--ease-out);
  }
  .site-nav__link:hover {
    color: var(--brand-secondary-text);
  }
  .site-nav__link.is-current {
    color: var(--brand-primary-text);
    font-weight: var(--weight-bold);
    text-decoration: underline;
    text-underline-offset: 0.4em;
    text-decoration-color: var(--brand-accent);
    text-decoration-thickness: 3px;
  }

  /* Default Donate-style CTA. Brand can override. */
  .site-nav__cta--desktop {
    background-color: var(--brand-accent);
    color: var(--brand-primary-text);
    padding-block: 0.625rem;
    padding-inline: 1.25rem;
    border-radius: var(--radius-2);
    font-weight: var(--weight-bold);
    text-decoration: none;
    font-size: var(--text-1);
    transition: background-color var(--duration-fast) var(--ease-out);
  }
  .site-nav__cta--desktop:hover {
    background-color: var(--brand-accent-strong);
    color: var(--brand-primary-text);
  }

  /* About-style dropdown */
  .site-nav__item--has-children { position: relative; }
  .site-nav__caret {
    display: inline-block;
    margin-inline-start: 0.25rem;
    vertical-align: middle;
    transition: transform var(--duration-fast) var(--ease-out);
  }
  .site-nav__item--has-children.is-open .site-nav__caret,
  .site-nav__item--has-children:hover .site-nav__caret,
  .site-nav__item--has-children:focus-within .site-nav__caret {
    transform: rotate(-180deg);
  }

  .site-nav__submenu {
    position: absolute;
    inset-block-start: 100%;
    inset-inline-start: 0;
    min-inline-size: 13.75rem;
    background-color: var(--bg-page);
    border-radius: var(--radius-2);
    box-shadow: var(--dropdown-shadow);
    list-style: none;
    margin: 0.5rem 0 0;
    padding-block: 0.5rem;
    opacity: 0;
    visibility: hidden;
    transform: translateY(-4px);
    transition:
      opacity    var(--duration-fast) var(--ease-out),
      transform  var(--duration-fast) var(--ease-out),
      visibility var(--duration-fast) var(--ease-out);
    z-index: var(--z-dropdown);
  }
  .site-nav__item--has-children:hover .site-nav__submenu,
  .site-nav__item--has-children:focus-within .site-nav__submenu,
  .site-nav__item--has-children.is-open .site-nav__submenu {
    opacity: 1;
    visibility: visible;
    transform: translateY(0);
  }
  .site-nav__sublink {
    display: block;
    padding-block: 0.5rem;
    padding-inline: 1rem;
    font-family: var(--font-body);
    font-weight: var(--weight-medium);
    font-size: var(--text-1);
    color: var(--brand-primary-text);
    text-decoration: none;
    white-space: nowrap;
    transition:
      background-color var(--duration-instant) var(--ease-out),
      color            var(--duration-instant) var(--ease-out);
  }
  .site-nav__sublink:hover,
  .site-nav__sublink:focus {
    background-color: var(--bg-surface-alt);
    color: var(--brand-secondary-text);
  }


  /* ────────── 46.2 MOBILE MENU — overlay ──────────
     Pair with a small JS controller for toggle, body scroll lock, Esc key,
     and breakpoint reset. */

  .site-nav__toggle {
    display: none;
    background-color: transparent;
    border: 0;
    inline-size: 44px;
    block-size: 44px;
    padding: 0;
    cursor: pointer;
    color: var(--brand-primary-text);
    margin-inline-start: auto;
  }
  .site-nav__toggle-icon { display: block; margin-inline: auto; }
  .site-nav__toggle-icon--close { display: none; }

  @media (max-width: 900px) {
    .site-nav__toggle {
      display: inline-flex;
      align-items: center;
      justify-content: center;
    }

    .site-nav__menu {
      position: fixed;
      inset: 64px 0 0 0;
      background-color: var(--bg-page);
      flex-direction: column;
      align-items: stretch;
      gap: 0;
      margin: 0;
      padding: var(--space-md);
      overflow-y: auto;
      transform: translateX(100%);
      transition: transform var(--transition-base);
      z-index: var(--z-fixed);
    }
    .site-nav.is-menu-open .site-nav__menu {
      transform: translateX(0);
    }
    .site-nav.is-menu-open .site-nav__toggle-icon--open  { display: none; }
    .site-nav.is-menu-open .site-nav__toggle-icon--close { display: block; }

    .site-nav__item {
      border-block-end: var(--border-width-xs) solid var(--border-subtle);
    }
    .site-nav__link {
      display: flex;
      align-items: center;
      justify-content: space-between;
      padding-block: var(--space-md);
      font-size: var(--text-2);
      font-weight: var(--weight-semibold);
    }

    /* Mobile dropdown — accordion-style, expanded inline */
    .site-nav__submenu {
      position: static;
      box-shadow: none;
      background-color: transparent;
      margin: 0;
      padding-block-end: var(--space-md);
      padding-inline: 0;
      opacity: 1;
      visibility: visible;
      transform: none;
      display: none;
    }
    .site-nav__item--has-children.is-open .site-nav__submenu {
      display: block;
    }
    .site-nav__sublink {
      padding-block: 0.5rem;
      padding-inline-start: 1.25rem;
      padding-inline-end: 0;
      font-size: var(--text-1);
    }

    .site-nav__cta--desktop { display: none; }
    .site-nav__item--mobile-cta {
      border: 0;
      padding-block-start: var(--space-lg);
    }
    .site-nav__cta--mobile {
      display: inline-block;
      background-color: var(--brand-accent);
      color: var(--brand-primary-text);
      padding-block: 0.875rem;
      padding-inline: 1.5rem;
      border-radius: var(--radius-2);
      font-weight: var(--weight-bold);
      text-decoration: none;
      font-size: var(--text-2);
      text-align: center;
      inline-size: 100%;
    }
    .site-nav__cta--mobile:hover {
      background-color: var(--brand-accent-strong);
      color: var(--brand-primary-text);
    }
  }
  @media (min-width: 901px) {
    .site-nav__item--mobile-cta { display: none; }
  }


  /* ────────── 46.3 BODY SCROLL LOCK ──────────
     Class added to <html> by JS when mobile menu opens. */

  .scroll-lock,
  .scroll-lock body {
    overflow: hidden;
  }


  /* ────────── 46.4 SITE FOOTER — three-column ──────────
     Brand-coloured footer. color-scheme: dark scopes light-dark() inside,
     so every nested token resolves dark — text contrast on brand fill
     stays correct without manual override of every text style. */

  .site-footer {
    background-color: var(--brand-primary);
    color: color-mix(in oklch, var(--text-on-brand) 92%, transparent);
    color-scheme: dark;
    padding-block: 5rem 2.5rem;
    margin-block-start: 6rem;
  }
  .site-footer .container {
    max-inline-size: var(--layout-max);
    margin-inline: auto;
    padding-inline: 1.5rem;
  }
  .site-footer__top {
    display: flex;
    align-items: center;
    gap: 1.5rem;
    margin-block-end: 3rem;
    padding-block-end: 2rem;
    border-block-end: var(--border-width-xs) solid color-mix(in oklch, white 12%, transparent);
  }
  .site-footer__logo {
    block-size: auto;
    max-block-size: 56px;
    inline-size: auto;
  }
  .site-footer__wordmark {
    display: flex;
    flex-direction: column;
  }
  .site-footer__wordmark-sub {
    font-size: var(--text-0);
    opacity: 0.7;
  }
  .site-footer__wordmark-main {
    font-size: var(--text-2);
    font-weight: var(--weight-bold);
    color: var(--brand-accent);
  }
  .site-footer__heading {
    color: var(--text-on-brand);
    font-weight: var(--weight-bold);
    font-size: var(--text-3);
    margin: 0 0 1.25rem;
  }
  .site-footer__contact-row {
    margin-block-end: 0.75rem;
  }
  .site-footer__contact-row a,
  .site-footer__nav-link {
    color: color-mix(in oklch, var(--text-on-brand) 85%, transparent);
    text-decoration: none;
    font-weight: var(--weight-bold);
    transition: color var(--transition-base);
  }
  .site-footer__contact-row a:hover,
  .site-footer__nav-link:hover {
    color: var(--brand-accent);
  }
  .site-footer__nav-list {
    list-style: none;
    padding: 0;
    margin: 0;
  }
  .site-footer__nav-link {
    line-height: 2.1;
    font-size: var(--text-1);
    font-weight: var(--weight-regular);
  }
  .site-footer__grid {
    display: grid;
    grid-template-columns: repeat(auto-fit, minmax(15rem, 1fr));
    gap: 3rem;
    margin-block-end: 3rem;
  }
  .site-footer__legal,
  .site-footer__bottom {
    color: color-mix(in oklch, var(--text-on-brand) 70%, transparent);
    font-size: var(--text-1);
    line-height: var(--leading-loose);
  }
  .site-footer__legal {
    margin-block-start: 1rem;
  }
  .site-footer__legal-links {
    list-style: none;
    padding: 0;
    display: flex;
    gap: 1.5rem;
    flex-wrap: wrap;
    border-block-start: var(--border-width-xs) solid color-mix(in oklch, white 18%, transparent);
    padding-block-start: 1.5rem;
    margin: 0 0 1.5rem;
  }
  .site-footer__legal-links a {
    color: color-mix(in oklch, var(--text-on-brand) 85%, transparent);
    text-decoration: none;
    font-size: var(--text-1);
    transition: color var(--transition-base);
  }
  .site-footer__legal-links a:hover {
    color: var(--brand-accent);
  }
  .site-footer__bottom {
    display: flex;
    align-items: center;
    gap: 1rem;
    padding-block-start: 1rem;
  }
  .site-footer__credit {
    margin: 0;
    opacity: 0.7;
  }
  .site-footer__credit-logo {
    block-size: auto;
    max-block-size: 28px;
    opacity: 0.85;
  }
  .site-footer__newsletter-text {
    color: color-mix(in oklch, var(--text-on-brand) 85%, transparent);
    font-size: var(--text-1);
    margin-block-end: 1rem;
  }
  .site-footer__newsletter-field {
    margin-block-end: 0.75rem;
  }
  .site-footer__newsletter-input {
    inline-size: 100%;
    padding-block: 0.65rem;
    padding-inline: 0.875rem;
    border: var(--border-width-xs) solid color-mix(in oklch, white 25%, transparent);
    background-color: color-mix(in oklch, white 8%, transparent);
    color: var(--text-on-brand);
    border-radius: var(--radius-1);
    font-size: var(--text-1);
    font-family: inherit;
  }
  .site-footer__newsletter-input::placeholder {
    color: color-mix(in oklch, var(--text-on-brand) 50%, transparent);
  }
}


/* ================================================================================
   ████████████████████████████████████████████████████████████████████████████████
   INTERACTION COMPONENTS
   ████████████████████████████████████████████████████████████████████████████████

   Sections 47–53. Patterns where the user-experience is the surface area:
   toast, modal, progress, chip, hamburger, nav-tabs, scroll-shrink helper.

   All apply v3 patterns: logical properties, color-mix(), light-dark(),
   motion tokens, container queries, native primitives where they exist
   (e.g. <dialog>, <progress>).
   ================================================================================ */


/* ================================================================================
   47. TOAST NOTIFICATIONS
   --------------------------------------------------------------------------------
   Fixed-position stacked notifications with semantic variants. Slide-in
   animation from the inline-end edge. JS handles dismiss timer + stacking
   (typically: append a new .toast to a fixed-position .toast-stack region,
   stagger their entrance via animation-delay, remove after timeout).

   Variants: success / warning / error / info — soft bg, border-inline-start
   stripe in the state colour.
   ================================================================================ */
@layer tokens {
  :root {
    --toast-padding-y:    var(--space-sm);
    --toast-padding-x:    var(--space-md);
    --toast-radius:       var(--radius-2);
    --toast-shadow:       var(--shadow-lg);
    --toast-font-size:    var(--text-card-body);
    --toast-icon-size:    1.25em;
    --toast-gap:          var(--space-xs);
    --toast-min-width:    18.75rem;   /* 300px */
    --toast-max-width:    26.25rem;   /* 420px */
    --toast-margin:       var(--space-md);
    --toast-stripe-width: var(--border-width-md);
  }
}

@layer components {
  /* Stack region — pin to a corner; each .toast inside flows naturally. */
  .toast-stack {
    position: fixed;
    inset-block-end: var(--toast-margin);
    inset-inline-end: var(--toast-margin);
    display: flex;
    flex-direction: column;
    gap: var(--toast-gap);
    z-index: var(--z-toast);
    pointer-events: none;  /* Stack itself doesn't intercept clicks */
  }
  .toast-stack > .toast { pointer-events: auto; }

  /* Standalone (no stack region) — toast positions itself */
  .toast {
    display: flex;
    align-items: flex-start;
    gap: var(--toast-gap);
    min-inline-size: var(--toast-min-width);
    max-inline-size: var(--toast-max-width);
    padding-block: var(--toast-padding-y);
    padding-inline: var(--toast-padding-x);
    border-radius: var(--toast-radius);
    box-shadow: var(--toast-shadow);
    background-color: var(--bg-surface);
    color: var(--text-body);
    font-size: var(--toast-font-size);
    line-height: var(--leading-normal);
    border-inline-start: var(--toast-stripe-width) solid var(--border-medium);
    animation: toast-slide-in var(--transition-base) backwards;
  }
  .toast:not(.toast-stack > .toast) {
    position: fixed;
    inset-block-end: var(--toast-margin);
    inset-inline-end: var(--toast-margin);
    z-index: var(--z-toast);
  }

  .toast__icon {
    flex-shrink: 0;
    inline-size: var(--toast-icon-size);
    block-size: var(--toast-icon-size);
    margin-block-start: 0.1em;
  }

  .toast__body {
    flex: 1;
    min-inline-size: 0;
  }
  .toast__title {
    font-size: inherit;
    font-weight: var(--weight-bold);
    margin: 0 0 0.15em;
  }
  .toast__body p { margin: 0; }

  .toast__dismiss {
    background: none;
    border: none;
    padding: 0;
    margin-inline-start: var(--space-xs);
    cursor: pointer;
    color: inherit;
    opacity: 0.5;
    font-size: 1.1em;
    line-height: 1;
    transition: opacity var(--transition-base);
  }
  .toast__dismiss:hover { opacity: 1; }

  /* Semantic variants */
  .toast--success {
    background-color: var(--state-success-soft);
    color: var(--state-success-text);
    border-inline-start-color: var(--state-success);
  }
  .toast--warning {
    background-color: var(--state-warning-soft);
    color: var(--state-warning-text);
    border-inline-start-color: var(--state-warning);
  }
  .toast--error {
    background-color: var(--state-error-soft);
    color: var(--state-error-text);
    border-inline-start-color: var(--state-error);
  }
  .toast--info {
    background-color: var(--state-info-soft);
    color: var(--state-info-text);
    border-inline-start-color: var(--state-info);
  }

  @keyframes toast-slide-in {
    from { opacity: 0; transform: translateX(100%); }
    to   { opacity: 1; transform: translateX(0); }
  }
}


/* ================================================================================
   48. MODAL / DIALOG  (native <dialog>)
   --------------------------------------------------------------------------------
   Built on the native <dialog> element. Free wins from going native:
     • Focus trap (browser handles)
     • ESC closes (browser handles)
     • ::backdrop pseudo for the scrim
     • Top-layer rendering — no z-index wars
     • showModal() / close() API; form method="dialog" submits as close

   Markup:
     <dialog class="modal modal--md">
       <header class="modal__header">
         <h2 class="modal__title">Title</h2>
         <button class="modal__close" aria-label="Close" formmethod="dialog">×</button>
       </header>
       <div class="modal__body">…</div>
       <footer class="modal__footer">
         <button class="btn btn--std btn--tint" formmethod="dialog">Cancel</button>
         <button class="btn btn--std btn--primary">Save</button>
       </footer>
     </dialog>

   Open:  modal.showModal()    (modal — pushes to top layer + traps focus)
   Open:  modal.show()         (non-modal — inline-positioned, no scrim)
   Close: modal.close()        (or any form button with formmethod="dialog")
   ================================================================================ */
@layer tokens {
  :root {
    --modal-radius:      var(--radius-4);
    --modal-padding:     var(--space-lg);
    --modal-shadow:      var(--shadow-xl);
    --modal-bg:          var(--bg-page);
    --modal-overlay-bg:  color-mix(in oklch, black 50%, transparent);
    --modal-max-width:   35rem;       /* 560px — default size */
    --modal-max-width-sm: 25rem;
    --modal-max-width-lg: 50rem;
    --modal-header-padding-end:    var(--space-md);
    --modal-footer-gap:            var(--space-sm);
    --modal-footer-padding-start:  var(--space-md);
    --modal-close-size:            2rem;
  }
}

@layer components {
  /* Base dialog — applies whether opened modal or non-modal */
  dialog.modal,
  .modal {
    margin: auto;          /* centres in viewport when open */
    border: none;
    padding: var(--modal-padding);
    border-radius: var(--modal-radius);
    background-color: var(--modal-bg);
    color: var(--text-body);
    box-shadow: var(--modal-shadow);
    inline-size: 100%;
    max-inline-size: var(--modal-max-width);
    max-block-size: 90svh;     /* prevent overflow on small viewports */
    overflow: auto;
  }
  /* Size modifiers */
  .modal--sm { max-inline-size: var(--modal-max-width-sm); }
  .modal--md { max-inline-size: var(--modal-max-width); }
  .modal--lg { max-inline-size: var(--modal-max-width-lg); }
  .modal--full {
    max-inline-size: 95vw;
    max-block-size: 95svh;
  }

  /* Backdrop scrim — fires automatically with showModal() */
  dialog.modal::backdrop,
  .modal::backdrop {
    background-color: var(--modal-overlay-bg);
    -webkit-backdrop-filter: blur(2px);
            backdrop-filter: blur(2px);
  }

  /* Header */
  .modal__header {
    display: flex;
    align-items: center;
    justify-content: space-between;
    gap: var(--modal-header-padding-end);
    margin-block-end: var(--space-md);
    padding-block-end: var(--space-sm);
    border-block-end: var(--border-width-xs) solid var(--border-subtle);
  }
  .modal__title {
    font-size: var(--text-card-h3);
    font-weight: var(--weight-bold);
    margin: 0;
    text-wrap: balance;
  }
  .modal__close {
    inline-size: var(--modal-close-size);
    block-size: var(--modal-close-size);
    background: none;
    border: none;
    cursor: pointer;
    color: var(--text-muted);
    font-size: 1.5em;
    line-height: 1;
    border-radius: var(--radius-circle);
    display: inline-flex;
    align-items: center;
    justify-content: center;
    flex-shrink: 0;
    transition:
      color            var(--transition-base),
      background-color var(--transition-base);
  }
  .modal__close:hover {
    color: var(--text-body);
    background-color: var(--bg-subtle);
  }

  /* Body */
  .modal__body {
    font-size: var(--text-card-body);
    line-height: var(--leading-loose);
    color: var(--text-body);
  }
  .modal__body p { margin-block-end: var(--space-sm); }
  .modal__body p:last-child { margin-block-end: 0; }

  /* Footer */
  .modal__footer {
    display: flex;
    justify-content: flex-end;
    gap: var(--modal-footer-gap);
    margin-block-start: var(--space-md);
    padding-block-start: var(--modal-footer-padding-start);
    border-block-start: var(--border-width-xs) solid var(--border-subtle);
    flex-wrap: wrap;
  }
  .modal__footer--start { justify-content: flex-start; }
  .modal__footer--between { justify-content: space-between; }

  /* Open-state animations.
     :modal pseudo-class fires when dialog is opened via showModal().
     Reduced motion handled globally by reset. */
  dialog.modal[open],
  .modal[open] {
    animation: modal-fade-in var(--duration-fast) var(--ease-out);
  }
  dialog.modal[open]::backdrop,
  .modal[open]::backdrop {
    animation: modal-backdrop-fade var(--duration-fast) var(--ease-out);
  }
  @keyframes modal-fade-in {
    from { opacity: 0; transform: translateY(8px) scale(0.98); }
    to   { opacity: 1; transform: translateY(0)   scale(1); }
  }
  @keyframes modal-backdrop-fade {
    from { opacity: 0; }
    to   { opacity: 1; }
  }
}


/* ================================================================================
   49. PROGRESS BAR
   --------------------------------------------------------------------------------
   Two flavours:
     • Native <progress> — preferred where the value is meaningful (uploads,
       form completion, downloads). Browser handles a11y + value semantics.
     • .progress / .progress__bar — div-based, useful where you need a
       gradient fill, animated stripes, or non-numeric "indeterminate" loops.

   Variants: --indeterminate (looping), --sm (smaller height).
   ================================================================================ */
@layer tokens {
  :root {
    --progress-height:    0.5rem;        /* 8px */
    --progress-height-sm: 0.25rem;       /* 4px */
    --progress-radius:    var(--radius-pill);
    --progress-track-bg:  var(--bg-subtle);
    --progress-bar-bg:    var(--brand-accent);
    --progress-duration:  var(--duration-base);
  }
}

@layer components {
  /* Native <progress> styling */
  progress {
    appearance: none;
    inline-size: 100%;
    block-size: var(--progress-height);
    border: none;
    border-radius: var(--progress-radius);
    background-color: var(--progress-track-bg);
    overflow: hidden;
  }
  progress::-webkit-progress-bar {
    background-color: var(--progress-track-bg);
    border-radius: var(--progress-radius);
  }
  progress::-webkit-progress-value {
    background-color: var(--progress-bar-bg);
    border-radius: var(--progress-radius);
    transition: inline-size var(--progress-duration) var(--ease-out);
  }
  progress::-moz-progress-bar {
    background-color: var(--progress-bar-bg);
    border-radius: var(--progress-radius);
  }

  /* Div-based — for indeterminate / animated / gradient-filled cases */
  .progress {
    inline-size: 100%;
    block-size: var(--progress-height);
    background-color: var(--progress-track-bg);
    border-radius: var(--progress-radius);
    overflow: clip;
    position: relative;
  }
  .progress--sm { block-size: var(--progress-height-sm); }
  .progress__bar {
    block-size: 100%;
    background-color: var(--progress-bar-bg);
    border-radius: inherit;
    transition: inline-size var(--progress-duration) var(--ease-out);
  }

  /* Indeterminate — looping bar of 30% width sliding across */
  .progress--indeterminate .progress__bar,
  .progress--indeterminate::after {
    content: "";
    position: absolute;
    inset-block: 0;
    inline-size: 30%;
    background-color: var(--progress-bar-bg);
    border-radius: inherit;
    animation: progress-loop 1.4s var(--ease-in-out) infinite;
  }
  @keyframes progress-loop {
    from { inset-inline-start: -30%; }
    to   { inset-inline-start: 100%; }
  }
}


/* ================================================================================
   50. INTERACTIVE CHIP
   --------------------------------------------------------------------------------
   Filterable / selectable / dismissable inline tag.
   Reads ARIA-correctly via aria-pressed (toggle) or removable button.

   Markup:
     <button class="chip" aria-pressed="false">Design</button>           toggle
     <span   class="chip">
       Active filter
       <button class="chip__remove" aria-label="Remove">×</button>
     </span>                                                              dismiss
   ================================================================================ */
@layer tokens {
  :root {
    --chip-padding-y:     0.4rem;
    --chip-padding-x:     0.85rem;
    --chip-radius:        var(--radius-pill);
    --chip-font-size:     var(--text-_1);
    --chip-font-weight:   var(--weight-semibold);

    --chip-bg-rest:       var(--bg-subtle);
    --chip-bg-hover:      color-mix(in oklch, var(--brand-accent) 8%, var(--bg-subtle));
    --chip-bg-active:     var(--brand-accent);
    --chip-text-rest:     var(--text-body);
    --chip-text-active:   var(--text-on-brand);
    --chip-border:        transparent;

    --chip-remove-size:    1em;
    --chip-remove-margin:  0.4em;
  }
}

@layer components {
  .chip {
    display: inline-flex;
    align-items: center;
    gap: 0.4em;
    padding-block: var(--chip-padding-y);
    padding-inline: var(--chip-padding-x);
    border-radius: var(--chip-radius);
    border: var(--border-width-xs) solid var(--chip-border);
    background-color: var(--chip-bg-rest);
    color: var(--chip-text-rest);
    font-family: inherit;
    font-size: var(--chip-font-size);
    font-weight: var(--chip-font-weight);
    line-height: 1;
    text-decoration: none;
    cursor: pointer;
    white-space: nowrap;
    transition:
      background-color var(--transition-base),
      color            var(--transition-base),
      border-color     var(--transition-base);
  }
  .chip:hover  { background-color: var(--chip-bg-hover); }

  /* Active state — driven by aria-pressed (toggle) or .is-active class */
  .chip[aria-pressed="true"],
  .chip.is-active {
    background-color: var(--chip-bg-active);
    color: var(--chip-text-active);
  }

  /* Disabled */
  .chip:disabled,
  .chip[aria-disabled="true"] {
    opacity: 0.5;
    cursor: not-allowed;
    pointer-events: none;
  }

  /* Remove button (×) — sits inside the chip */
  .chip__remove {
    inline-size: var(--chip-remove-size);
    block-size: var(--chip-remove-size);
    margin-inline-start: var(--chip-remove-margin);
    border: none;
    background: none;
    color: inherit;
    cursor: pointer;
    opacity: 0.6;
    border-radius: var(--radius-circle);
    display: inline-flex;
    align-items: center;
    justify-content: center;
    flex-shrink: 0;
    line-height: 1;
    transition:
      opacity         var(--transition-base),
      background-color var(--transition-base);
  }
  .chip__remove:hover {
    opacity: 1;
    background-color: color-mix(in oklch, currentColor 15%, transparent);
  }

  /* Touch target enforcement — coarse pointers get 44px min */
  @media (pointer: coarse) {
    .chip { min-block-size: 2.75rem; }
  }
}


/* ================================================================================
   51. HAMBURGER
   --------------------------------------------------------------------------------
   Three-bar mobile menu icon-button. Animates to an X when toggled.
   No JS dependency for the visuals — flip a class or aria-expanded.

   Markup:
     <button class="hamburger" aria-label="Open menu" aria-expanded="false">
       <span class="hamburger__bar"></span>
       <span class="hamburger__bar"></span>
       <span class="hamburger__bar"></span>
     </button>

   Open state: set aria-expanded="true" (or .is-open class).
   ================================================================================ */
@layer tokens {
  :root {
    --hamburger-size:        2.75rem;   /* 44px — meets WCAG touch target */
    --hamburger-bar-color:   var(--text-heading);
    --hamburger-bar-color-on-dark: var(--text-on-brand);
    --hamburger-bar-width:   1.5rem;     /* 24px */
    --hamburger-bar-height:  2px;
    --hamburger-bar-gap:     0.4rem;     /* 6px between bars */
    --hamburger-transition:  var(--transition-base);
  }
}

@layer components {
  .hamburger {
    inline-size: var(--hamburger-size);
    block-size: var(--hamburger-size);
    padding: 0;
    background: none;
    border: none;
    cursor: pointer;
    display: inline-flex;
    flex-direction: column;
    align-items: center;
    justify-content: center;
    gap: var(--hamburger-bar-gap);
    border-radius: var(--radius-2);
  }
  .hamburger:hover {
    background-color: color-mix(in oklch, currentColor 8%, transparent);
  }

  .hamburger__bar {
    display: block;
    inline-size: var(--hamburger-bar-width);
    block-size: var(--hamburger-bar-height);
    background-color: var(--hamburger-bar-color);
    border-radius: var(--hamburger-bar-height);
    transition:
      transform        var(--hamburger-transition),
      opacity          var(--hamburger-transition),
      background-color var(--hamburger-transition);
    transform-origin: center;
  }

  /* On-dark variant — flip bar colour via parent surface or modifier */
  .hamburger--on-dark .hamburger__bar,
  .surface--dark .hamburger .hamburger__bar,
  .on-dark .hamburger .hamburger__bar {
    background-color: var(--hamburger-bar-color-on-dark);
  }

  /* Open state — bars cross into an X.
     Calc: bar-gap + bar-height worth of vertical movement to meet at centre. */
  .hamburger[aria-expanded="true"] .hamburger__bar:nth-child(1),
  .hamburger.is-open .hamburger__bar:nth-child(1) {
    transform: translateY(calc(var(--hamburger-bar-gap) + var(--hamburger-bar-height))) rotate(45deg);
  }
  .hamburger[aria-expanded="true"] .hamburger__bar:nth-child(2),
  .hamburger.is-open .hamburger__bar:nth-child(2) {
    opacity: 0;
  }
  .hamburger[aria-expanded="true"] .hamburger__bar:nth-child(3),
  .hamburger.is-open .hamburger__bar:nth-child(3) {
    transform: translateY(calc((var(--hamburger-bar-gap) + var(--hamburger-bar-height)) * -1)) rotate(-45deg);
  }
}


/* ================================================================================
   52. NAV TABS
   --------------------------------------------------------------------------------
   Inline horizontal tab strip with sliding underline indicator. Sibling to
   the sticky `.page-nav` (Section 37) — that one's a sticky page-section
   nav bar; this one's an inline tab strip used in headers, content tabs,
   filter bars.

   Two variants (set on the parent .nav-tabs element):
     .nav-tabs--section  fades in once .is-visible is added (e.g. after
                         hero scroll, set by IntersectionObserver)
     .nav-tabs--site     always visible. Active state from JS matching
                         current path to a link's href.

   Indicator JS:
     1. On mount + on resize, measure the active link's left + width
     2. Set --nav-tabs-indicator-x and --nav-tabs-indicator-w on the
        parent .nav-tabs element via inline style
     3. On change, the indicator transitions left + width
   ================================================================================ */
@layer tokens {
  :root {
    --nav-tabs-gap:              var(--space-lg);
    --nav-tabs-link-padding-y:   0.5em;
    --nav-tabs-link-color:       color-mix(in oklch, var(--text-heading) 70%, transparent);
    --nav-tabs-link-color-hover: var(--text-muted);
    --nav-tabs-link-color-active: var(--text-heading);
    --nav-tabs-link-font-size:   var(--text-1);
    --nav-tabs-link-font-weight: var(--weight-regular);
    --nav-tabs-link-tracking:    0.02em;

    --nav-tabs-indicator-height: 2px;
    --nav-tabs-indicator-color:  var(--text-heading);
    --nav-tabs-indicator-easing: var(--ease-out);
  }
}

@layer components {
  .nav-tabs {
    display: flex;
    align-items: center;
    gap: var(--nav-tabs-gap);
    position: relative;
  }

  /* Section variant — fades in after hero scroll */
  .nav-tabs--section {
    opacity: 0;
    transition: opacity var(--transition-base);
  }
  .nav-tabs--section.is-visible { opacity: 1; }

  .nav-tabs__link {
    font-size: var(--nav-tabs-link-font-size);
    font-weight: var(--nav-tabs-link-font-weight);
    color: var(--nav-tabs-link-color);
    text-decoration: none;
    letter-spacing: var(--nav-tabs-link-tracking);
    white-space: nowrap;
    padding-block: var(--nav-tabs-link-padding-y);
    transition: color var(--transition-base);
    position: relative;
  }
  .nav-tabs__link:hover    { color: var(--nav-tabs-link-color-hover); }
  .nav-tabs__link.is-active { color: var(--nav-tabs-link-color-active); }

  /* Sliding indicator. JS measures active link, sets x + width via style. */
  .nav-tabs__indicator {
    position: absolute;
    inset-block-end: -1px;
    inset-inline-start: var(--nav-tabs-indicator-x, 0);
    inline-size: var(--nav-tabs-indicator-w, 0);
    block-size: var(--nav-tabs-indicator-height);
    background-color: var(--nav-tabs-indicator-color);
    border-radius: var(--nav-tabs-indicator-height);
    transition:
      inset-inline-start var(--duration-base) var(--nav-tabs-indicator-easing),
      inline-size        var(--duration-base) var(--nav-tabs-indicator-easing),
      opacity            var(--transition-fast);
    opacity: 0;
    pointer-events: none;
  }
  .nav-tabs.is-ready .nav-tabs__indicator { opacity: 1; }

  /* Reduced motion */
  @media (prefers-reduced-motion: reduce) {
    .nav-tabs--section,
    .nav-tabs__indicator { transition: none; }
  }
}


/* ================================================================================
   53. SCROLL-SHRINK HEADER HELPER
   --------------------------------------------------------------------------------
   Common pattern: a header that's tall at the top of the page and shrinks
   on scroll. Pure CSS pattern — JS just toggles .is-shrunk on the header
   when scroll passes a threshold.

   This section provides tokens + a base `.scroll-shrink` mixin pattern.
   The actual header structure (logo / nav / CTA layout) is project-level
   — every brand styles its header chrome differently.

   Usage:
     <header class="site-header scroll-shrink" data-shrunk="false">
       <a class="site-header__logo">…</a>
       <nav class="nav-tabs">…</nav>
     </header>

   JS hooks:
     header.classList.toggle('is-shrunk', scrollY > shrinkThreshold);

   ALTERNATIVE: scroll-driven animations (`animation-timeline: scroll()`)
   can do this without JS, but support is still uneven across browsers
   in 2026. JS toggle is the universally-safe approach.
   ================================================================================ */
@layer tokens {
  :root {
    --header-height:           4.5rem;   /* 72px — scrolled / compact */
    --header-height-expanded:  6.5rem;   /* 104px — initial state */
    --header-shrink-threshold: 60px;     /* JS reads this; CSS doesn't apply */
    --header-transition:       var(--duration-slow) cubic-bezier(0.32, 0.72, 0, 1);

    /* Logo helpers (optional — only used if you want logo to scale on shrink) */
    --header-logo-height:           2.25rem;  /* 36px when scrolled */
    --header-logo-height-expanded:  3.5rem;   /* 56px at top of page */
  }
}

@layer components {
  .scroll-shrink {
    block-size: var(--header-height-expanded);
    transition: block-size var(--header-transition);
  }
  .scroll-shrink.is-shrunk {
    block-size: var(--header-height);
  }

  /* Optional logo helper — opt-in via .scroll-shrink__logo on a logo element. */
  .scroll-shrink__logo {
    block-size: var(--header-logo-height-expanded);
    inline-size: auto;
    transition: block-size var(--header-transition);
  }
  .scroll-shrink.is-shrunk .scroll-shrink__logo {
    block-size: var(--header-logo-height);
  }
}


/* ================================================================================
   ████████████████████████████████████████████████████████████████████████████████
   UI PRIMITIVES
   ████████████████████████████████████████████████████████████████████████████████

   Sections 54–60. Tabs + panels, switch, slider, tooltip, dropdown menu,
   avatar, code block.

   Phase 4 added components GDH happened to have already built. Phase 5 adds
   the components every system needs whether or not GDH had them yet.
   ================================================================================ */


/* ================================================================================
   54. TABS + PANELS
   --------------------------------------------------------------------------------
   ARIA-correct tabs pattern: tablist + tab + tabpanel. NOT to be confused
   with .nav-tabs (Section 52, an inline horizontal nav strip). This is for
   in-page content switching: settings panels, product detail tabs, code
   playgrounds.

   Markup contract (JS handles activation):
     <div class="tabs">
       <div class="tabs__list" role="tablist" aria-label="Settings">
         <button class="tabs__tab" role="tab"
                 id="tab-1" aria-controls="panel-1"
                 aria-selected="true" tabindex="0">General</button>
         <button class="tabs__tab" role="tab"
                 id="tab-2" aria-controls="panel-2"
                 aria-selected="false" tabindex="-1">Advanced</button>
       </div>
       <section class="tabs__panel" role="tabpanel"
                id="panel-1" aria-labelledby="tab-1">…</section>
       <section class="tabs__panel" role="tabpanel"
                id="panel-2" aria-labelledby="tab-2" hidden>…</section>
     </div>

   JS contract (controller responsibilities):
     • On click/Enter/Space: set aria-selected="true" on clicked tab,
       aria-selected="false" on others, set tabindex="0" on active and
       tabindex="-1" on inactive (roving tabindex pattern), reveal the
       matching panel (remove hidden), hide the others.
     • Arrow keys (Left/Right or Up/Down) move between tabs.
     • Home/End jump to first/last tab.

   The CSS uses [aria-selected="true"] and [hidden] — no .is-active class.
   That keeps the ARIA state and visual state in lockstep.
   ================================================================================ */
@layer tokens {
  :root {
    --tabs-list-gap:        var(--space-md);
    --tabs-list-border:     var(--border-width-xs) solid var(--border-subtle);
    --tabs-tab-padding-y:   var(--space-xs);
    --tabs-tab-padding-x:   var(--space-sm);
    --tabs-tab-color:       var(--text-muted);
    --tabs-tab-color-hover: var(--text-body);
    --tabs-tab-color-active: var(--text-heading);
    --tabs-tab-font-size:   var(--text-1);
    --tabs-tab-font-weight: var(--weight-semibold);
    --tabs-indicator-h:     2px;
    --tabs-indicator-color: var(--brand-accent);
    --tabs-panel-padding:   var(--space-md);
  }
}

@layer components {
  .tabs { display: flex; flex-direction: column; }

  .tabs__list {
    display: flex;
    gap: var(--tabs-list-gap);
    border-block-end: var(--tabs-list-border);
    overflow-x: auto;
    scrollbar-width: none;
    /* min-inline-size: 0 is non-negotiable — without it, the list
       inherits the flex-default min-inline-size: auto (≈ content's
       intrinsic width) and overflow-x: auto never triggers in any
       column-flex parent. Master's own `.tabs` is `display: flex;
       flex-direction: column`, so this hit *every* consumer using
       .tabs inside a card or constrained container until v3.7.1.

       max-inline-size: 100% is belt-and-braces: explicitly pins
       the list to its parent's inline-size in case an upstream
       layout doesn't already do so via align-items: stretch. */
    min-inline-size: 0;
    max-inline-size: 100%;
  }
  .tabs__list::-webkit-scrollbar { display: none; }

  .tabs__tab {
    position: relative;
    background: none;
    border: none;
    cursor: pointer;
    padding-block: var(--tabs-tab-padding-y);
    padding-inline: var(--tabs-tab-padding-x);
    font-family: inherit;
    font-size: var(--tabs-tab-font-size);
    font-weight: var(--tabs-tab-font-weight);
    color: var(--tabs-tab-color);
    white-space: nowrap;
    transition: color var(--transition-base);
  }
  .tabs__tab:hover { color: var(--tabs-tab-color-hover); }

  /* Active state — ARIA-driven */
  .tabs__tab[aria-selected="true"] {
    color: var(--tabs-tab-color-active);
  }
  .tabs__tab[aria-selected="true"]::after {
    content: "";
    position: absolute;
    inset-block-end: calc(var(--tabs-indicator-h) * -1);
    inset-inline: 0;
    block-size: var(--tabs-indicator-h);
    background-color: var(--tabs-indicator-color);
    border-radius: var(--tabs-indicator-h);
  }

  /* Disabled */
  .tabs__tab[aria-disabled="true"],
  .tabs__tab:disabled {
    opacity: 0.5;
    cursor: not-allowed;
  }

  /* Panel — visible only when not [hidden] (browser native attr).
     Reduced spacing if a panel sits in a tight layout. */
  .tabs__panel {
    padding-block: var(--tabs-panel-padding);
    /* [hidden] handles visibility natively */
  }

  /* Compact variant — for tight UI like settings drawers */
  .tabs--compact .tabs__list { gap: var(--space-sm); }
  .tabs--compact .tabs__tab  { font-size: var(--text-0); padding-inline: var(--space-xs); }
  .tabs--compact .tabs__panel { padding-block: var(--space-sm); }

  /* Vertical variant — tabs on the inline-start, panels on inline-end */
  .tabs--vertical {
    display: grid;
    grid-template-columns: max-content 1fr;
    gap: var(--space-lg);
  }
  .tabs--vertical .tabs__list {
    flex-direction: column;
    gap: 0;
    border-block-end: none;
    border-inline-end: var(--tabs-list-border);
  }
  .tabs--vertical .tabs__tab[aria-selected="true"]::after {
    inset-block: 0;
    inset-inline-end: calc(var(--tabs-indicator-h) * -1);
    inset-inline-start: auto;
    inline-size: var(--tabs-indicator-h);
    block-size: auto;
  }
}


/* ================================================================================
   55. SWITCH / TOGGLE
   --------------------------------------------------------------------------------
   Boolean form control styled as a sliding switch. Uses native <input
   type="checkbox"> for accessibility; CSS reskins it. Keyboard, screen
   reader, form submission all work for free.

   Markup:
     <label class="switch">
       <input type="checkbox" class="switch__input">
       <span class="switch__track" aria-hidden="true">
         <span class="switch__thumb"></span>
       </span>
       <span class="switch__label">Email notifications</span>
     </label>

   The label IS the wrapper (`<label class="switch">`) so any click on
   the row toggles the input. No `for=`/`id=` plumbing needed.
   ================================================================================ */
@layer tokens {
  :root {
    --switch-width:        2.5rem;       /* 40px */
    --switch-height:       1.5rem;       /* 24px */
    --switch-padding:      2px;
    --switch-thumb-size:   calc(var(--switch-height) - (var(--switch-padding) * 2));
    --switch-radius:       var(--radius-pill);
    --switch-bg-off:       var(--bg-subtle);
    --switch-bg-on:        var(--brand-accent);
    --switch-thumb-color:  light-dark(#ffffff, oklch(95% 0 0));
    --switch-thumb-shadow: var(--shadow-sm);
    --switch-gap:          var(--space-xs);
  }
}

@layer components {
  .switch {
    display: inline-flex;
    align-items: center;
    gap: var(--switch-gap);
    cursor: pointer;
    font-size: var(--text-1);
    color: var(--text-body);
    user-select: none;
  }
  .switch:has(.switch__input:disabled) {
    opacity: 0.5;
    cursor: not-allowed;
  }

  /* Hide native input visually but keep it focusable + clickable */
  .switch__input {
    position: absolute;
    inline-size: 1px;
    block-size: 1px;
    padding: 0;
    margin: -1px;
    overflow: hidden;
    clip-path: inset(50%);
    white-space: nowrap;
    border: 0;
  }

  .switch__track {
    position: relative;
    flex-shrink: 0;
    inline-size: var(--switch-width);
    block-size: var(--switch-height);
    background-color: var(--switch-bg-off);
    border-radius: var(--switch-radius);
    padding: var(--switch-padding);
    transition: background-color var(--transition-base);
  }

  .switch__thumb {
    display: block;
    inline-size: var(--switch-thumb-size);
    block-size: var(--switch-thumb-size);
    background-color: var(--switch-thumb-color);
    border-radius: var(--radius-circle);
    box-shadow: var(--switch-thumb-shadow);
    transition: transform var(--transition-base);
  }

  /* Checked state — drives both track colour and thumb position */
  .switch__input:checked ~ .switch__track {
    background-color: var(--switch-bg-on);
  }
  .switch__input:checked ~ .switch__track .switch__thumb {
    transform: translateX(calc(var(--switch-width) - var(--switch-thumb-size) - (var(--switch-padding) * 2)));
  }

  /* Focus ring — drawn on the track when input is keyboard-focused */
  .switch__input:focus-visible ~ .switch__track {
    outline: var(--focus-ring-width) solid var(--focus-ring-color);
    outline-offset: var(--focus-ring-offset);
  }

  /* Compact variant — tighter for inline use */
  .switch--sm {
    --switch-width:  2rem;
    --switch-height: 1.25rem;
  }
}


/* ================================================================================
   56. SLIDER / RANGE INPUT
   --------------------------------------------------------------------------------
   Native <input type="range"> styled. WebKit and Mozilla ship different
   pseudo-elements; both are styled below. Single-thumb only (dual-thumb
   range needs a JS library — out of scope for the master).

   Markup:
     <input type="range" class="form-range" min="0" max="100" value="50">

   Variants: --sm (smaller), --discrete (with tick marks via list= attr).
   ================================================================================ */
@layer tokens {
  :root {
    --form-range-track-bg:     var(--bg-subtle);
    --form-range-track-fill:   var(--brand-accent);
    --form-range-track-height: 0.375rem;     /* 6px */
    --form-range-track-radius: var(--radius-pill);

    --form-range-thumb-bg:     light-dark(#ffffff, oklch(95% 0 0));
    --form-range-thumb-size:   1.25rem;       /* 20px */
    --form-range-thumb-radius: var(--radius-circle);
    --form-range-thumb-shadow: 0 1px 3px rgb(0 0 0 / 0.2),
                               0 0 0 1px var(--brand-accent);
  }
}

@layer components {
  .form-range {
    appearance: none;
    inline-size: 100%;
    background: transparent;
    cursor: pointer;
    /* JS sets --form-range-progress as a percentage (e.g. 60%) for the
       fill effect. If unset, defaults to 0% (track shows empty). */
    --form-range-progress: 0%;
  }
  .form-range:focus { outline: none; }

  /* Track — WebKit */
  .form-range::-webkit-slider-runnable-track {
    block-size: var(--form-range-track-height);
    border-radius: var(--form-range-track-radius);
    background:
      linear-gradient(
        to right,
        var(--form-range-track-fill) 0,
        var(--form-range-track-fill) var(--form-range-progress),
        var(--form-range-track-bg)   var(--form-range-progress),
        var(--form-range-track-bg)   100%
      );
  }
  /* Track — Firefox */
  .form-range::-moz-range-track {
    block-size: var(--form-range-track-height);
    border-radius: var(--form-range-track-radius);
    background-color: var(--form-range-track-bg);
  }
  .form-range::-moz-range-progress {
    block-size: var(--form-range-track-height);
    border-radius: var(--form-range-track-radius);
    background-color: var(--form-range-track-fill);
  }

  /* Thumb — WebKit */
  .form-range::-webkit-slider-thumb {
    appearance: none;
    inline-size: var(--form-range-thumb-size);
    block-size: var(--form-range-thumb-size);
    background-color: var(--form-range-thumb-bg);
    border: none;
    border-radius: var(--form-range-thumb-radius);
    box-shadow: var(--form-range-thumb-shadow);
    /* Centre thumb on track */
    margin-block-start: calc((var(--form-range-track-height) - var(--form-range-thumb-size)) / 2);
    cursor: pointer;
    transition: transform var(--transition-fast);
  }
  /* Thumb — Firefox */
  .form-range::-moz-range-thumb {
    inline-size: var(--form-range-thumb-size);
    block-size: var(--form-range-thumb-size);
    background-color: var(--form-range-thumb-bg);
    border: none;
    border-radius: var(--form-range-thumb-radius);
    box-shadow: var(--form-range-thumb-shadow);
    cursor: pointer;
    transition: transform var(--transition-fast);
  }

  /* Hover + active grow */
  .form-range:hover::-webkit-slider-thumb { transform: scale(1.1); }
  .form-range:hover::-moz-range-thumb     { transform: scale(1.1); }
  .form-range:active::-webkit-slider-thumb { transform: scale(1.15); }
  .form-range:active::-moz-range-thumb     { transform: scale(1.15); }

  /* Focus ring */
  .form-range:focus-visible::-webkit-slider-thumb {
    box-shadow:
      var(--form-range-thumb-shadow),
      0 0 0 var(--focus-ring-offset) var(--bg-page),
      0 0 0 calc(var(--focus-ring-offset) + var(--focus-ring-width)) var(--focus-ring-color);
  }
  .form-range:focus-visible::-moz-range-thumb {
    box-shadow:
      var(--form-range-thumb-shadow),
      0 0 0 var(--focus-ring-offset) var(--bg-page),
      0 0 0 calc(var(--focus-ring-offset) + var(--focus-ring-width)) var(--focus-ring-color);
  }

  /* Small variant */
  .form-range--sm {
    --form-range-thumb-size:   1rem;
    --form-range-track-height: 0.25rem;
  }

  /* Disabled */
  .form-range:disabled {
    opacity: 0.5;
    cursor: not-allowed;
  }
}


/* ================================================================================
   57. TOOLTIP
   --------------------------------------------------------------------------------
   CSS-only tooltip via [data-tooltip] attribute. Reveals on hover and
   :focus-visible. Lightweight: no JS, no positioning library.

   Markup:
     <button class="has-tooltip" data-tooltip="Saves the file">Save</button>
     <button class="has-tooltip has-tooltip--end"
             data-tooltip="Aligned to inline-end">⋯</button>

   ACCESSIBILITY NOTE
     Tooltips set via [data-tooltip] are NOT read by screen readers
     because they're rendered via CSS pseudo-elements. For SR-readable
     tooltips, ALSO set aria-label or aria-describedby pointing to a
     hidden text node. The CSS shows the visual tooltip; the ARIA
     attribute carries the accessible name.

     For critical info (e.g. icon-only buttons), prefer aria-label.
     For supplementary hint text, [data-tooltip] alone is fine.

   Default position: block-start (above the trigger).
   Variants: --end (after, inline-end), --start (before, inline-start),
             --bottom (block-end, below).
   ================================================================================ */
@layer tokens {
  :root {
    --tooltip-bg:          light-dark(oklch(20% 0 0), oklch(85% 0 0));
    --tooltip-color:       light-dark(oklch(98% 0 0), oklch(15% 0 0));
    --tooltip-padding-y:   0.35rem;
    --tooltip-padding-x:   0.6rem;
    --tooltip-radius:      var(--radius-1);
    --tooltip-font-size:   var(--text-_1);
    --tooltip-offset:      0.4rem;          /* gap between trigger + tooltip */
    --tooltip-arrow-size:  0.3rem;
    --tooltip-max-width:   16rem;
  }
}

@layer components {
  .has-tooltip {
    position: relative;
  }

  .has-tooltip::after {
    content: attr(data-tooltip);
    position: absolute;
    z-index: var(--z-popover);
    inset-block-end: calc(100% + var(--tooltip-offset));
    inset-inline-start: 50%;
    transform: translateX(-50%) translateY(2px);
    padding-block: var(--tooltip-padding-y);
    padding-inline: var(--tooltip-padding-x);
    background-color: var(--tooltip-bg);
    color: var(--tooltip-color);
    font-size: var(--tooltip-font-size);
    font-weight: var(--weight-medium);
    line-height: var(--leading-snug);
    border-radius: var(--tooltip-radius);
    white-space: normal;
    max-inline-size: var(--tooltip-max-width);
    inline-size: max-content;
    text-align: center;
    pointer-events: none;
    opacity: 0;
    transition:
      opacity   var(--transition-base),
      transform var(--transition-base);
  }

  /* Arrow */
  .has-tooltip::before {
    content: "";
    position: absolute;
    z-index: var(--z-popover);
    inset-block-end: calc(100% + var(--tooltip-offset) - var(--tooltip-arrow-size));
    inset-inline-start: 50%;
    transform: translateX(-50%) translateY(2px);
    border: var(--tooltip-arrow-size) solid transparent;
    border-block-start-color: var(--tooltip-bg);
    pointer-events: none;
    opacity: 0;
    transition:
      opacity   var(--transition-base),
      transform var(--transition-base);
  }

  /* Reveal on hover + keyboard focus */
  .has-tooltip:hover::after,
  .has-tooltip:hover::before,
  .has-tooltip:focus-visible::after,
  .has-tooltip:focus-visible::before {
    opacity: 1;
    transform: translateX(-50%) translateY(0);
  }

  /* Variant: bottom (below trigger) */
  .has-tooltip--bottom::after {
    inset-block-end: auto;
    inset-block-start: calc(100% + var(--tooltip-offset));
  }
  .has-tooltip--bottom::before {
    inset-block-end: auto;
    inset-block-start: calc(100% + var(--tooltip-offset) - var(--tooltip-arrow-size));
    border-block-start-color: transparent;
    border-block-end-color: var(--tooltip-bg);
  }

  /* Variant: end (after trigger, inline-end side) */
  .has-tooltip--end::after {
    inset-block-end: 50%;
    inset-block-start: auto;
    inset-inline-start: calc(100% + var(--tooltip-offset));
    transform: translateY(50%) translateX(-2px);
  }
  .has-tooltip--end::before {
    inset-block-end: 50%;
    inset-block-start: auto;
    inset-inline-start: calc(100% + var(--tooltip-offset) - var(--tooltip-arrow-size));
    transform: translateY(50%) translateX(-2px);
    border-block-start-color: transparent;
    border-inline-end-color: var(--tooltip-bg);
  }
  .has-tooltip--end:hover::after,
  .has-tooltip--end:hover::before,
  .has-tooltip--end:focus-visible::after,
  .has-tooltip--end:focus-visible::before {
    transform: translateY(50%) translateX(0);
  }

  /* Reduced motion — keep the reveal but skip the slide */
  @media (prefers-reduced-motion: reduce) {
    .has-tooltip::after,
    .has-tooltip::before { transition: opacity var(--transition-fast); transform: translateX(-50%); }
  }
}


/* ================================================================================
   58. DROPDOWN MENU / KEBAB MENU
   --------------------------------------------------------------------------------
   Click-triggered action menu — different from the hover-driven
   .site-nav__submenu (Section 46) and from .mega (Section 38).

   Use cases: kebab menus (⋯), action menus on cards, "More options"
   triggers, profile menus.

   Markup:
     <div class="menu">
       <button class="menu__trigger" aria-haspopup="true" aria-expanded="false">⋯</button>
       <ul class="menu__list" role="menu">
         <li role="none"><button class="menu__item" role="menuitem">Edit</button></li>
         <li role="none"><button class="menu__item" role="menuitem">Duplicate</button></li>
         <li class="menu__separator" role="separator"></li>
         <li role="none">
           <button class="menu__item menu__item--danger" role="menuitem">Delete</button>
         </li>
       </ul>
     </div>

   JS contract:
     • Click trigger → toggle aria-expanded on trigger + show menu list
       (CSS uses aria-expanded="true" on trigger to drive .menu__list visibility)
     • ESC closes the menu
     • Click outside closes
     • Up/Down arrows move focus between menu items
     • Home/End jump to first/last item
     • Enter/Space activates focused item

   The trigger and items are <button> for keyboard + screen-reader correctness.
   Anchor menu via inset-inline-start/-end + inset-block-start to position
   relative to the .menu container.
   ================================================================================ */
@layer tokens {
  :root {
    --menu-min-width:    11rem;
    --menu-bg:           var(--bg-page);
    --menu-shadow:       var(--shadow-lg);
    --menu-radius:       var(--radius-2);
    --menu-padding:      var(--space-2xs);
    --menu-item-padding-y: 0.5rem;
    --menu-item-padding-x: 0.75rem;
    --menu-item-radius:  var(--radius-1);
    --menu-item-color:   var(--text-body);
    --menu-item-bg-hover: var(--bg-surface);
    --menu-item-color-hover: var(--text-heading);
    --menu-item-color-danger: var(--state-error-text);
    --menu-item-bg-danger-hover: var(--state-error-soft);
    --menu-separator-margin: var(--space-2xs);
    --menu-offset:       0.4rem;
  }
}

@layer components {
  .menu {
    position: relative;
    display: inline-block;
  }

  .menu__trigger {
    /* Trigger is just a button — style as needed via .btn classes or
       custom. Default: minimal kebab-style. */
    background: none;
    border: none;
    cursor: pointer;
    padding: var(--space-2xs);
    border-radius: var(--radius-1);
    color: var(--text-muted);
    line-height: 1;
    transition:
      background-color var(--transition-base),
      color            var(--transition-base);
  }
  .menu__trigger:hover {
    background-color: var(--bg-surface);
    color: var(--text-body);
  }

  .menu__list {
    position: absolute;
    inset-block-start: calc(100% + var(--menu-offset));
    inset-inline-end: 0;
    z-index: var(--z-popover);
    min-inline-size: var(--menu-min-width);
    margin: 0;
    padding: var(--menu-padding);
    list-style: none;
    background-color: var(--menu-bg);
    border-radius: var(--menu-radius);
    box-shadow: var(--menu-shadow);
    opacity: 0;
    visibility: hidden;
    transform: translateY(-4px);
    transition:
      opacity    var(--transition-base),
      transform  var(--transition-base),
      visibility var(--transition-base);
  }

  /* Open state — driven by aria-expanded on trigger */
  .menu__trigger[aria-expanded="true"] + .menu__list,
  .menu.is-open .menu__list {
    opacity: 1;
    visibility: visible;
    transform: translateY(0);
  }

  /* Position variants */
  .menu--start .menu__list { inset-inline-end: auto; inset-inline-start: 0; }
  .menu--up    .menu__list {
    inset-block-start: auto;
    inset-block-end: calc(100% + var(--menu-offset));
    transform: translateY(4px);
  }
  .menu--up.menu.is-open .menu__list,
  .menu--up .menu__trigger[aria-expanded="true"] + .menu__list {
    transform: translateY(0);
  }

  /* Items */
  .menu__item {
    display: flex;
    align-items: center;
    gap: var(--space-xs);
    inline-size: 100%;
    padding-block: var(--menu-item-padding-y);
    padding-inline: var(--menu-item-padding-x);
    background: none;
    border: none;
    border-radius: var(--menu-item-radius);
    cursor: pointer;
    font-family: inherit;
    font-size: var(--text-1);
    color: var(--menu-item-color);
    text-align: start;
    text-decoration: none;
    white-space: nowrap;
    transition:
      background-color var(--transition-fast),
      color            var(--transition-fast);
  }
  .menu__item:hover,
  .menu__item:focus-visible {
    background-color: var(--menu-item-bg-hover);
    color: var(--menu-item-color-hover);
  }
  .menu__item--danger {
    color: var(--menu-item-color-danger);
  }
  .menu__item--danger:hover,
  .menu__item--danger:focus-visible {
    background-color: var(--menu-item-bg-danger-hover);
    color: var(--menu-item-color-danger);
  }
  .menu__item:disabled,
  .menu__item[aria-disabled="true"] {
    opacity: 0.5;
    cursor: not-allowed;
  }

  /* Item icons — sized to match text */
  .menu__item svg {
    inline-size: 1em;
    block-size: 1em;
    flex-shrink: 0;
  }

  /* Separator */
  .menu__separator {
    block-size: 1px;
    background-color: var(--border-subtle);
    margin-block: var(--menu-separator-margin);
  }
}


/* ================================================================================
   59. AVATAR
   --------------------------------------------------------------------------------
   Round image with optional initials fallback. Standalone primitive —
   different from .person-card / .team-card (which compose name + role
   around an avatar).

   Markup (image):
     <span class="avatar avatar--md">
       <img src="..." alt="Gareth Esson" class="avatar__img">
     </span>

   Markup (initials fallback when no image):
     <span class="avatar avatar--md" aria-label="Gareth Esson">
       <span class="avatar__initials">GE</span>
     </span>

   Markup (with status badge):
     <span class="avatar avatar--md">
       <img src="..." alt="" class="avatar__img">
       <span class="avatar__status avatar__status--online"></span>
     </span>

   Sizes: --xs (1.5rem), --sm (2rem), --md (3rem), --lg (4rem), --xl (5rem).
   ================================================================================ */
@layer tokens {
  :root {
    --avatar-size-xs: 1.5rem;     /* 24px */
    --avatar-size-sm: 2rem;       /* 32px */
    --avatar-size-md: 3rem;       /* 48px — default */
    --avatar-size-lg: 4rem;       /* 64px */
    --avatar-size-xl: 5rem;       /* 80px */

    --avatar-bg:          var(--bg-subtle);
    --avatar-initials-color: var(--text-heading);
    --avatar-border:      var(--border-width-sm) solid var(--bg-page);
    --avatar-shadow:      var(--shadow-xs);

    /* Status dot is 25% of avatar size — em-relative against the
       avatar's own font-size (which we set to --_avatar-base below). */
    --avatar-status-size: 0.25em;
    --avatar-status-online:  var(--state-success);
    --avatar-status-busy:    var(--state-error);
    --avatar-status-away:    var(--state-warning);
    --avatar-status-offline: var(--text-muted);
  }
}

@layer components {
  /* The avatar's own font-size IS the avatar's pixel size. Initials,
     status dots, group overlap — anything sized in `em` inside this
     element scales with the avatar size automatically.

     Per-size variants flip --_avatar-base; inline-size, block-size,
     and font-size all resolve from it. The previous version had
     inverted-em per-size overrides on .avatar__initials AND a fixed
     `0.75em` status-dot relative to inherited body 16px — the
     comment claimed initials scaled with avatar size, the values
     did the opposite, and status dots stayed 12px regardless of
     avatar size. */
  .avatar {
    --_avatar-base: var(--avatar-size-md);
    position: relative;
    display: inline-flex;
    align-items: center;
    justify-content: center;
    inline-size: var(--_avatar-base);
    block-size: var(--_avatar-base);
    font-size: var(--_avatar-base);    /* 1em = avatar size */
    background-color: var(--avatar-bg);
    border-radius: var(--radius-circle);
    overflow: visible;     /* status dot overflows; img is clipped via __img selector */
    flex-shrink: 0;
    user-select: none;
  }

  .avatar__img {
    inline-size: 100%;
    block-size: 100%;
    border-radius: var(--radius-circle);
    object-fit: cover;
    display: block;
  }

  .avatar__initials {
    font-family: var(--font-display);
    font-size: 0.4em;     /* 40% of avatar size — scales naturally */
    font-weight: var(--weight-bold);
    color: var(--avatar-initials-color);
    line-height: 1;
    text-transform: uppercase;
    letter-spacing: 0.02em;
  }

  /* Sizes — flip --_avatar-base; everything else derives from it. */
  .avatar--xs { --_avatar-base: var(--avatar-size-xs); }
  .avatar--sm { --_avatar-base: var(--avatar-size-sm); }
  .avatar--md { --_avatar-base: var(--avatar-size-md); }
  .avatar--lg { --_avatar-base: var(--avatar-size-lg); }
  .avatar--xl { --_avatar-base: var(--avatar-size-xl); }

  /* Bordered variant (e.g. team grids on tinted bg) */
  .avatar--bordered {
    border: var(--avatar-border);
    box-shadow: var(--avatar-shadow);
  }

  /* Square variant — for product/logo avatars */
  .avatar--square     { border-radius: var(--radius-2); }
  .avatar--square .avatar__img { border-radius: var(--radius-2); }

  /* Status indicator dot (online/away/etc.) */
  .avatar__status {
    position: absolute;
    inset-block-end: 0;
    inset-inline-end: 0;
    inline-size: var(--avatar-status-size);
    block-size: var(--avatar-status-size);
    border-radius: var(--radius-circle);
    border: 2px solid var(--bg-page);
    background-color: var(--avatar-status-offline);
  }
  .avatar__status--online  { background-color: var(--avatar-status-online); }
  .avatar__status--busy    { background-color: var(--avatar-status-busy); }
  .avatar__status--away    { background-color: var(--avatar-status-away); }
  .avatar__status--offline { background-color: var(--avatar-status-offline); }

  /* Avatar group — overlapping stack of avatars */
  .avatar-group {
    display: inline-flex;
    align-items: center;
  }
  .avatar-group > .avatar {
    border: 2px solid var(--bg-page);
    margin-inline-start: -0.5em;   /* overlap by 50% of avatar size */
  }
  .avatar-group > .avatar:first-child {
    margin-inline-start: 0;
  }
  /* Optional "+N more" pill at the end of the group */
  .avatar-group__more {
    display: inline-flex;
    align-items: center;
    justify-content: center;
    inline-size: var(--avatar-size-md);
    block-size: var(--avatar-size-md);
    border-radius: var(--radius-circle);
    background-color: var(--bg-subtle);
    color: var(--text-muted);
    font-size: 0.75em;
    font-weight: var(--weight-bold);
    margin-inline-start: -0.5em;
    border: 2px solid var(--bg-page);
  }
}


/* ================================================================================
   60. CODE BLOCK + INLINE CODE
   --------------------------------------------------------------------------------
   Token-driven styling for <pre><code> blocks and inline <code>.
   Designed to play nicely with syntax-highlighter output (Prism, Shiki,
   highlight.js) — those add per-token classes inside .code-block, but
   this section provides the chrome (background, padding, scroll behaviour,
   optional language label, optional copy button slot).

   Markup (block):
     <figure class="code-block" data-lang="css">
       <pre><code>...</code></pre>
       <button class="code-block__copy" aria-label="Copy code">Copy</button>
     </figure>

   Markup (inline):
     Some <code>inline code</code> in a paragraph.

   The figure wrapper gives the syntax highlighter clean access to <pre>
   and lets us add a language label + copy button without breaking the
   semantic <pre><code> structure.
   ================================================================================ */
@layer tokens {
  :root {
    --code-block-bg:           light-dark(oklch(96% 0.005 250), oklch(20% 0.01 250));
    --code-block-color:        var(--text-body);
    --code-block-padding-y:    var(--space-md);
    --code-block-padding-x:    var(--space-md);
    --code-block-radius:       var(--radius-2);
    --code-block-font-size:    var(--text-0);
    --code-block-line-height:  var(--leading-loose);
    --code-block-border:       var(--border-width-xs) solid var(--border-subtle);
    --code-block-lang-color:   var(--text-muted);

    --code-inline-bg:        color-mix(in oklch, var(--brand-accent) 8%, var(--bg-subtle));
    --code-inline-color:     var(--text-heading);
    --code-inline-padding-y: 0.1em;
    --code-inline-padding-x: 0.35em;
    --code-inline-radius:    var(--radius-1);
    --code-inline-font-size: 0.92em;
  }
}

@layer components {

  /* ── Block ── */
  .code-block {
    position: relative;
    margin-block: var(--space-md);
    background-color: var(--code-block-bg);
    border: var(--code-block-border);
    border-radius: var(--code-block-radius);
    overflow: hidden;
  }

  .code-block pre {
    margin: 0;
    padding-block: var(--code-block-padding-y);
    padding-inline: var(--code-block-padding-x);
    overflow-x: auto;
    font-family: var(--font-mono);
    font-size: var(--code-block-font-size);
    line-height: var(--code-block-line-height);
    color: var(--code-block-color);
    /* Preserve formatting + soft-wrap long lines if needed */
    white-space: pre;
    tab-size: 2;
    -moz-tab-size: 2;
    scrollbar-width: thin;
  }
  .code-block pre code {
    /* Inside a code-block, <code> is just a semantic carrier — strip
       any inline-code styling. */
    background: none;
    padding: 0;
    border-radius: 0;
    font-size: inherit;
    color: inherit;
  }

  /* Soft-wrap variant — for narrow contexts where horizontal scroll is bad */
  .code-block--wrap pre {
    white-space: pre-wrap;
    word-break: break-word;
  }

  /* Language label — pulled from data-lang attribute */
  .code-block[data-lang]::before {
    content: attr(data-lang);
    position: absolute;
    inset-block-start: var(--space-2xs);
    inset-inline-end: var(--space-xs);
    font-family: var(--font-mono);
    font-size: var(--text-_1);
    font-weight: var(--weight-semibold);
    color: var(--code-block-lang-color);
    text-transform: uppercase;
    letter-spacing: 0.05em;
    pointer-events: none;
  }

  /* Copy button — positioned in the top-end corner */
  .code-block__copy {
    position: absolute;
    inset-block-start: var(--space-xs);
    inset-inline-end: var(--space-xs);
    padding-block: 0.25rem;
    padding-inline: 0.5rem;
    background-color: color-mix(in oklch, var(--bg-page) 70%, transparent);
    color: var(--text-muted);
    border: var(--border-width-xs) solid var(--border-subtle);
    border-radius: var(--radius-1);
    font-family: var(--font-body);
    font-size: var(--text-_1);
    font-weight: var(--weight-semibold);
    cursor: pointer;
    opacity: 0;
    transition:
      opacity         var(--transition-base),
      background-color var(--transition-base),
      color           var(--transition-base);
  }
  .code-block:hover .code-block__copy,
  .code-block__copy:focus-visible {
    opacity: 1;
  }
  .code-block__copy:hover {
    background-color: var(--bg-page);
    color: var(--text-body);
  }
  /* Copied state — JS sets aria-label="Copied!" briefly after copy */
  .code-block__copy.is-copied {
    color: var(--state-success-text);
  }

  /* If both data-lang and a copy button are present, push lang label
     left so they don't overlap */
  .code-block:has(.code-block__copy)[data-lang]::before {
    inset-inline-end: 5rem;
  }

  /* ── Inline code ── */
  /* Targets <code> NOT inside a .code-block (block code resets this
     via the .code-block pre code rule above). */
  code {
    font-family: var(--font-mono);
    font-size: var(--code-inline-font-size);
    color: var(--code-inline-color);
    background-color: var(--code-inline-bg);
    padding-block: var(--code-inline-padding-y);
    padding-inline: var(--code-inline-padding-x);
    border-radius: var(--code-inline-radius);
    /* Prevent code from breaking awkwardly across lines */
    word-break: break-word;
  }
}


/* ================================================================================
   ████████████████████████████████████████████████████████████████████████████████
   LOADING + NATIVE SEMANTIC POLISH
   ████████████████████████████████████████████████████████████████████████████████

   Sections 61–64. Skeleton + spinner for loading states; dl/figure/kbd/mark
   element-level styling; truncation utilities.
   ================================================================================ */


/* ================================================================================
   61. SKELETON LOADER
   --------------------------------------------------------------------------------
   Shimmering placeholder boxes shown while real content loads. Three shape
   variants — text, circle, rectangle — and a shimmering gradient that
   sweeps across.

   Markup:
     <div class="skeleton skeleton--text"></div>            (full-width line)
     <div class="skeleton skeleton--text" style="--w: 60%"></div>  (custom width)
     <div class="skeleton skeleton--circle" style="--size: 3rem"></div>
     <div class="skeleton skeleton--rect" style="--size: 12rem"></div>

   For an avatar + 3 lines of text composition:
     <div class="skeleton-group">
       <div class="skeleton skeleton--circle" style="--size: 3rem"></div>
       <div>
         <div class="skeleton skeleton--text" style="--w: 80%"></div>
         <div class="skeleton skeleton--text"></div>
         <div class="skeleton skeleton--text" style="--w: 50%"></div>
       </div>
     </div>

   Honours prefers-reduced-motion (shimmer disabled, skeleton stays static).

   Add aria-hidden="true" or aria-live="polite" with text "Loading…"
   in a visually-hidden sibling, depending on whether you want SRs to
   announce the loading state.
   ================================================================================ */
@layer tokens {
  :root {
    --skeleton-bg:        var(--bg-subtle);
    --skeleton-shimmer:   color-mix(in oklch, var(--bg-page) 70%, var(--bg-subtle));
    --skeleton-radius:    var(--radius-1);
    --skeleton-duration:  1.6s;
    --skeleton-text-h:    1em;             /* default text-line height */
    --skeleton-circle:    2.5rem;          /* default circle size */
  }
}

@layer components {
  .skeleton {
    background-color: var(--skeleton-bg);
    background-image: linear-gradient(
      90deg,
      transparent,
      var(--skeleton-shimmer),
      transparent
    );
    background-size: 200% 100%;
    background-repeat: no-repeat;
    border-radius: var(--skeleton-radius);
    animation: skeleton-shimmer var(--skeleton-duration) infinite linear;
  }

  /* Text-line variant — full-width by default, override via --w inline */
  .skeleton--text {
    inline-size: var(--w, 100%);
    block-size: var(--skeleton-text-h);
    margin-block-end: 0.6em;
  }
  .skeleton--text:last-child { margin-block-end: 0; }

  /* Circle (avatar placeholder) */
  .skeleton--circle {
    inline-size: var(--size, var(--skeleton-circle));
    block-size: var(--size, var(--skeleton-circle));
    border-radius: var(--radius-circle);
  }

  /* Rectangle (image placeholder) */
  .skeleton--rect {
    inline-size: var(--size, 100%);
    block-size: var(--h, 8rem);
    border-radius: var(--radius-2);
  }

  /* Optional grouping helper for "avatar + text" compositions */
  .skeleton-group {
    display: flex;
    align-items: flex-start;
    gap: var(--space-sm);
  }
  .skeleton-group > :not(.skeleton) {
    flex: 1;
    min-inline-size: 0;
  }

  @keyframes skeleton-shimmer {
    from { background-position:  200% 0; }
    to   { background-position: -200% 0; }
  }

  /* Reduced motion — keep the placeholder, drop the shimmer */
  @media (prefers-reduced-motion: reduce) {
    .skeleton {
      background-image: none;
      animation: none;
    }
  }
}


/* ================================================================================
   62. SPINNER
   --------------------------------------------------------------------------------
   Circular rotating loading indicator. Different from <progress> (which has
   a value); spinner is for indeterminate "something's happening."

   Markup:
     <div class="spinner" role="status" aria-label="Loading"></div>
     <div class="spinner spinner--sm"></div>
     <div class="spinner spinner--lg"></div>

   Inline-with-text variant (sized to current font):
     <button class="btn btn--std btn--primary">
       <span class="spinner spinner--inline"></span>
       Saving…
     </button>

   role="status" + aria-label tells screen readers "Loading" without
   announcing every spin. For text-based loading state, pair with a
   visually-hidden <span class="sr-only">Loading data</span>.
   ================================================================================ */
@layer tokens {
  :root {
    --spinner-size:      1.5rem;
    --spinner-size-sm:   1rem;
    --spinner-size-lg:   2.5rem;
    --spinner-thickness: 2px;
    --spinner-color:     var(--brand-accent);
    --spinner-track:     color-mix(in oklch, var(--brand-accent) 20%, transparent);
    --spinner-duration:  0.8s;
  }
}

@layer components {
  .spinner {
    inline-size: var(--spinner-size);
    block-size: var(--spinner-size);
    border-radius: var(--radius-circle);
    border: var(--spinner-thickness) solid var(--spinner-track);
    border-block-start-color: var(--spinner-color);  /* one quadrant carries the accent */
    animation: spinner-rotate var(--spinner-duration) linear infinite;
    display: inline-block;
    flex-shrink: 0;
  }

  .spinner--sm { inline-size: var(--spinner-size-sm); block-size: var(--spinner-size-sm); }
  .spinner--lg {
    inline-size: var(--spinner-size-lg);
    block-size: var(--spinner-size-lg);
    border-width: 3px;
  }

  /* Inline variant — sized to current font (em) so it matches the line it sits in */
  .spinner--inline {
    inline-size: 1em;
    block-size: 1em;
    border-width: 1.5px;
    vertical-align: -0.15em;
    margin-inline-end: 0.4em;
  }

  /* On-brand variant — for spinners on coloured backgrounds */
  .spinner--on-brand {
    --spinner-color: currentColor;
    --spinner-track: color-mix(in oklch, currentColor 20%, transparent);
  }

  @keyframes spinner-rotate {
    to { transform: rotate(360deg); }
  }

  /* Reduced motion — slow the spinner dramatically rather than freeze it
     (a frozen spinner reads as "broken"; a slow spinner still communicates state). */
  @media (prefers-reduced-motion: reduce) {
    .spinner { animation-duration: 4s; }
  }
}


/* ================================================================================
   63. NATIVE SEMANTIC ELEMENTS
   --------------------------------------------------------------------------------
   Element-level styling for native HTML primitives that show up regularly
   in markdown / MDX / CMS-authored content where a class wrapper isn't
   guaranteed:
     • <a> (bare)
     • <blockquote>
     • <hr>
     • <abbr>
     • <dl> / <dt> / <dd>
     • <figure> / <figcaption>
     • <kbd>, <mark>

   Bare <a>, <blockquote>, <hr>, <abbr> were added in Audit Pass 1
   (AUDIT-NOTES.md) — they were "slips through" before, getting browser
   default styling that wasn't brand-aware.

   Brand-overridable via the same tokens used elsewhere (text-heading,
   text-muted, link-color, brand-accent, border-subtle, font-mono, etc.).
   ================================================================================ */
@layer base {

  /* ── <a> (bare) ──
     Targets only anchors WITHOUT a class — `.btn`, `.link-inline`,
     `.card__link` etc. all bring their own colour rules. The `:not([class])`
     selector keeps bare anchors brand-aware (using --link-color) without
     fighting class-styled components. */
  a:not([class]) {
    color: var(--link-color);
    text-decoration: underline;
    text-decoration-color: var(--link-underline-rest);
    text-underline-offset: 0.15em;
    transition:
      color                  var(--transition-base, 0.15s ease),
      text-decoration-color  var(--transition-base, 0.15s ease);
  }
  a:not([class]):hover {
    color: var(--link-color-hover);
    text-decoration-color: var(--link-underline-hover);
  }
  a:not([class]):visited {
    color: var(--link-color-visited);
  }

  /* ── <blockquote> ──
     Editorial pull-quote treatment — left-rule in brand-accent, generous
     inline padding, sensible margin-block. No italic (taste-specific;
     weight + colour does the work). */
  blockquote {
    margin-block: var(--space-lg);
    padding-inline-start: var(--space-md);
    border-inline-start: 3px solid var(--brand-accent);
    color: var(--text-heading);
  }

  /* ── <hr> ──
     Subtle horizontal rule. Browser default is 2px etched grey; we
     replace with a single line in --border-subtle and reset margin so
     it sits flush in editorial flow. */
  hr {
    margin-block: var(--space-xl);
    border: 0;
    border-block-start: 1px solid var(--border-subtle);
  }

  /* ── <abbr> ──
     Acronym / abbreviation with title attribute. Convention is dotted
     underline + cursor:help so users know to hover. */
  abbr[title] {
    text-decoration: underline dotted;
    text-decoration-color: var(--text-muted);
    text-underline-offset: 0.15em;
    cursor: help;
  }

  /* ── <pre> (bare) ──
     Markdown / MDX code fences render as <pre><code>. The .code-block
     component (Section 60) is the styled wrapper with copy button +
     language indicator. Bare <pre> needs the chrome PLUS the inner-pre
     treatment combined into one element, since there's no figure
     wrapper.

     Tokens shared with .code-block so authored and authored-via-markdown
     code reads consistently. The copy button + language indicator are
     opt-in via the .code-block wrapper. */
  pre {
    margin-block: var(--space-md);
    padding-block: var(--code-block-padding-y);
    padding-inline: var(--code-block-padding-x);
    background-color: var(--code-block-bg);
    border: var(--code-block-border);
    border-radius: var(--code-block-radius);
    overflow-x: auto;
    font-family: var(--font-mono);
    font-size: var(--code-block-font-size);
    line-height: var(--code-block-line-height);
    color: var(--code-block-color);
    white-space: pre;
    tab-size: 2;
    -moz-tab-size: 2;
    scrollbar-width: thin;
  }
  /* Inside <pre>, <code> is a semantic carrier — strip the inline-code
     pill chrome (background, padding, radius). Same reset as
     `.code-block pre code`. */
  pre > code {
    background: none;
    padding: 0;
    border-radius: 0;
    font-size: inherit;
    color: inherit;
  }

  /* ── <dl> / <dt> / <dd> — definition list / key-value ──
     Use cases: glossaries, FAQ summary blocks, key-value pairs in cards. */
  dl {
    margin-block: var(--space-md);
  }
  dt {
    font-weight: var(--weight-bold);
    color: var(--text-heading);
    line-height: var(--leading-snug);
  }
  dd {
    margin-inline-start: 0;            /* override browser default 40px indent */
    margin-block-end: var(--space-sm);
    color: var(--text-body);
    line-height: var(--leading-normal);
  }
  dd:last-child { margin-block-end: 0; }
  /* Spacing between successive dt/dd pairs */
  dt + dd { margin-block-start: var(--space-2xs); }
  dd + dt { margin-block-start: var(--space-md); }

  /* ── <figure> / <figcaption> — captioned media ──
     Used in articles, case studies, blog posts. Browser default has
     unwanted left/right margin on <figure>; we strip and let the parent
     container handle horizontal placement. */
  figure {
    margin: 0;
  }
  figure + * { margin-block-start: var(--space-md); }
  figcaption {
    margin-block-start: var(--space-xs);
    font-size: var(--text-micro);
    color: var(--text-muted);
    line-height: var(--leading-snug);
  }

  /* ── <kbd> — keyboard shortcuts ──
     Used in docs, settings, anywhere a shortcut is shown.
     Visual: small inline pill that looks key-like.

     Composing for multi-key combos:
       <kbd>Ctrl</kbd>+<kbd>S</kbd>
       <kbd><kbd>Ctrl</kbd>+<kbd>S</kbd></kbd>   (grouped — outer kbd wraps) */
  kbd {
    display: inline-block;
    padding-block: 0.1em;
    padding-inline: 0.4em;
    font-family: var(--font-mono);
    font-size: 0.85em;
    font-weight: var(--weight-semibold);
    color: var(--text-heading);
    background-color: var(--bg-subtle);
    border: var(--border-width-xs) solid var(--border-medium);
    border-radius: var(--radius-1);
    box-shadow: 0 1px 0 var(--border-medium);
    line-height: 1;
    white-space: nowrap;
  }
  /* Nested kbd (for grouped combos) — drop the chrome on the outer wrapper */
  kbd kbd {
    box-shadow: none;
    border: none;
    padding: 0;
    background: none;
    font-size: 1em;
  }

  /* ── <mark> — text highlight ──
     Browser default is bright yellow (clashes with most brands).
     Token-driven so brand override is one line. */
  mark {
    background-color: color-mix(in oklch, var(--state-warning) 28%, transparent);
    color: var(--text-heading);
    padding-block: 0.05em;
    padding-inline: 0.2em;
    border-radius: var(--radius-1);
  }
}


/* ================================================================================
   64. TRUNCATION UTILITIES
   --------------------------------------------------------------------------------
   Cut text to a fixed line count with ellipsis. Used in cards, lists, table
   cells where copy length varies but layout has a fixed footprint.

   Single-line truncation:
     <h3 class="truncate">Long title that should not wrap</h3>

   Multi-line clamp:
     <p class="line-clamp-2">Two-line max excerpt…</p>
     <p class="line-clamp-3">Three-line max body…</p>
     <p class="line-clamp-4">Four-line max body…</p>

   Custom clamp via custom property:
     <p style="--lines: 5" class="line-clamp">Five-line max…</p>

   ACCESSIBILITY NOTE
     Truncated text is hidden visually but still in the DOM, so screen
     readers read the full content. If the truncation is meaningful
     (e.g. "click to read more"), the full text should ideally be
     available via expand interaction or a tooltip with the full string.
   ================================================================================ */
@layer utilities {
  /* ── .sr-only / .visually-hidden — visually hidden, SR-readable ──
     Universal a11y utility: keeps content in the accessibility tree
     (screen readers announce it) but removes it from visual layout.
     Master's comments reference this pattern in spinner / skeleton /
     tooltip / dropdown docs ("pair with a visually-hidden span") —
     ships the class so consumers don't have to roll their own.

     Two class names for ergonomics (Bootstrap legacy + modern). */
  .sr-only,
  .visually-hidden {
    position: absolute;
    inline-size: 1px;
    block-size: 1px;
    padding: 0;
    margin: -1px;
    overflow: hidden;
    clip-path: inset(50%);
    white-space: nowrap;
    border: 0;
  }

  /* Single-line — overflow + ellipsis */
  .truncate {
    overflow: hidden;
    text-overflow: ellipsis;
    white-space: nowrap;
  }

  /* Multi-line clamping. -webkit-box variants are still required even in
     2026 — the unprefixed line-clamp shorthand isn't fully supported. */
  .line-clamp,
  .line-clamp-1,
  .line-clamp-2,
  .line-clamp-3,
  .line-clamp-4,
  .line-clamp-5,
  .line-clamp-6 {
    display: -webkit-box;
    -webkit-box-orient: vertical;
    overflow: hidden;
  }

  .line-clamp   { -webkit-line-clamp: var(--lines, 3); line-clamp: var(--lines, 3); }
  .line-clamp-1 { -webkit-line-clamp: 1; line-clamp: 1; }
  .line-clamp-2 { -webkit-line-clamp: 2; line-clamp: 2; }
  .line-clamp-3 { -webkit-line-clamp: 3; line-clamp: 3; }
  .line-clamp-4 { -webkit-line-clamp: 4; line-clamp: 4; }
  .line-clamp-5 { -webkit-line-clamp: 5; line-clamp: 5; }
  .line-clamp-6 { -webkit-line-clamp: 6; line-clamp: 6; }
}


/* ================================================================================
   ████████████████████████████████████████████████████████████████████████████████
   END OF GDH MASTER CSS v3.15.1
   ████████████████████████████████████████████████████████████████████████████████

   Brand override quick reference
   --------------------------------------------------------------------------------
   A typical brand file is ~10–30 lines. Drop into the `brand` layer:

     @layer brand {
       :root {
         --brand-primary:   #d10070;
         --brand-secondary: #1a1a2e;
         --brand-accent:    #ffc845;
         --font-body:       'Inter', sans-serif;
         --font-display:    'Fraunces', serif;
         --type-base-min:   1.0625rem;   ← slightly larger body
         --type-ratio:      1.25;        ← major third (more drama)
         --radius-unit:     0.375rem;    ← softer corners
       }
     }

   See templates/brand-starter.css for the annotated template.

   Naming changes vs v2 (clean break — no aliases at the master level)
   --------------------------------------------------------------------------------
     .has-icon-left              → .has-icon-start
     .has-icon-right             → .has-icon-end
     .hero-split--image-left     → .hero-split--image-start
     .split-section--image-left  → .split-section--image-start
     .rb-scroll-lock             → .scroll-lock
     --space-global-*            → --space-*
     --text-{display,prose,card}-size-hN → --text-{display,prose,card}-hN
     --transition-duration / -easing → --transition-base (composed)
     :invalid                    → :user-invalid (live validation)
     HSL component decomposition (--brand-x-h/-s/-l) → RCS via oklch(from ...)
     On-dark variant tokens      → color-scheme: dark subtree scoping

   Optional companion files
   --------------------------------------------------------------------------------
     • theme-layout.css   — header/footer/layout helpers (any framework)
     • gdh-wp.css         — WordPress-only: Gutenberg block overrides + CF7
                             integration. Skip on non-WP projects.

   Documentation
   --------------------------------------------------------------------------------
     • DECISIONS-v3.md         — architectural choices and reasoning
     • JS-HOOKS.md             — state-class / ARIA / custom-property contracts
     • templates/brand-starter.css — annotated brand override starter
   ================================================================================ */
