/* ============================================================================
   @fgl/vituno design tokens — the theming contract (single source).
   Extracted from expeal.com (the donor site), Story 1.1.2, 2026-07-18.

   Every custom property here has a matching entry in
   payload/_includes/fgl/tokens.json — the machine manifest powering the
   site-override lint, the contrast gate's values, catalog swatches, and
   breaking-change detection. test/tokens.test.mjs pins the equivalence both
   ways (names AND values). A token removal or rename in the tokens.json
   diff = BREAKING release.

   Sites brand by OVERRIDE: <site>.css re-declares token VALUES (the entire
   supported brand surface); component rules consume tokens only and the
   vendored fgl/ payload is never edited in-site. The default values below
   are the donor's, contrast-verified as a set (measured ratios noted
   inline); a site that overrides colors re-verifies its own set via the
   shared contrast gate (Story 1.3.2). Contrast targets: body text >=4.5:1,
   large text & non-text UI >=3:1 (WCAG 2.x).

   The default face (Atkinson Hyperlegible Next, OFL 1.1) is self-hosted —
   no font CDN. Its @font-face below points at the vendored files (Story
   1.2.4, fonts/ beside this file); --font falls through to the system stack
   while it loads (font-display: swap).
   ============================================================================ */

/* ── BREAKPOINTS — framework CONSTANTS, deliberately NOT custom properties
   (@media conditions cannot read custom props; a --bp-* token would drift
   from the literals that actually govern layout — the donor site proved it:
   its published 46rem token sat unused while every live query said 48rem).
   The authoritative constant set lives in docs/census.md; framework CSS
   hard-codes these literals in queries, mobile-first min-width:
     md  48rem  (768px)  — single-col → multi-col; mobile-nav teardown
     lg  68rem  (1088px) — desktop nav unfolds; sidebar-capable
     xl  90rem  (1440px) — container hits --content-full; margins grow only
   Container queries are the primary component-responsiveness mechanism;
   use these only for page-frame shifts (nav unfold, column count). */

/* ── Default face: Atkinson Hyperlegible Next (Braille Institute, 2025;
   OFL 1.1 — license text beside the files), SELF-HOSTED, vendored by Story
   1.2.4. One variable woff2 covers the 400–700 range the scale uses;
   font-display: swap so first paint uses the system fallback and never
   blocks render. The URL is ABSOLUTE so a site's css bundling/inlining
   cannot break relative resolution. */
@font-face {
  font-family: "Atkinson Hyperlegible Next";
  src: url("/assets/css/fgl/fonts/atkinson-hyperlegible-next/AtkinsonHyperlegibleNextVF-Variable.woff2")
       format("woff2-variations");
  font-weight: 400 700;
  font-style: normal;
  font-display: swap;
}

:root {
  /* ── Brand anchors ───────────────────────────────────────────────────────
     The two hues a site's brand hangs on. Defaults are the donor's; every
     adopting site overrides these first. */
  --brand-primary:   #3C3B6E;  /* logo, primary-CTA bg, active nav states */
  --brand-secondary: #B22234;  /* emphasis only. NEVER a primary CTA or a form error */

  /* ── Primary interaction ramp ────────────────────────────────────────────
     Interaction states DARKEN --brand-primary — no tint ramp, no transform.
     Disabled state = reduced opacity on the neutral spine, not a tint
     (defined with the components, Epic 1.2). Sites overriding
     --brand-primary override these two with it, as a set. */
  --brand-primary-hover:  #302F58;  /* white label 12.55:1 on default */
  --brand-primary-active: #26253F;  /* white label 14.80:1 on default */

  /* ── Warm-neutral spine ──────────────────────────────────────────────────
     Warm off-white page + true-white elevated surfaces. */
  --surface-primary:  #FBFAF5; /* page background — warm off-white, not stark #FFF */
  --surface-elevated: #FFFFFF; /* cards, modals, surfaces that sit proud of the page */
  --ink-body:  #1A1A22;        /* body copy.            16.54:1 on page · 17.29:1 on card */
  --ink-muted: #5A5A66;        /* captions, metadata.    6.51:1 on page ·  6.80:1 on card */

  /* ── Borders — two tokens, two obligations ───────────────────────────────
     --border-subtle is a DECORATIVE hairline (card edges, dividers): WCAG
     1.4.11 exempt because it is not the sole means of identifying a
     component (the surface shift is). --border-strong is for FORM INPUT
     chrome, whose boundary carries state — it must clear 3:1. */
  --border-subtle: #E4E2DB;    /* card/divider hairline (decorative, exempt) */
  --border-strong: #8C887E;    /* input border. 3.38:1 on page · 3.54:1 on card (>=3:1 ok) */

  /* ── Informational accent ────────────────────────────────────────────────
     A separate link blue so in-text links don't collapse into brand color. */
  --accent-info: #4A6FA5;      /* links, "learn more". 4.89:1 on page · 5.11:1 on card */

  /* ── Functional status accents — status only, never decoration ───────────
     Each status pairs a tinted bg with a darker ink for its text. Color is
     never the sole signal (icon + word accompany it). */
  --color-success:    #2E7D52; /* positive result — forest green */
  --success-bg:       #E7F1EA;
  --success-border:   #D3E4D9;
  --success-ink:      #1F5C3B; /*  6.85:1 on --success-bg ok */
  --color-warning:    #8A6510; /* qualified result — warm amber */
  --warning-bg:       #F6EFD9;
  --warning-border:   #E8DCB8;
  --warning-ink:      #6E5210; /*  6.35:1 on --warning-bg ok */
  --color-neutral:    #5A5A66; /* negative/neutral result — neutral, NOT red */
  --neutral-bg:       #F1F0EC;
  --neutral-ink:      #3A3A44; /*  9.86:1 on --neutral-bg ok */
  --color-error-form: #D63626; /* FORM VALIDATION ERRORS ONLY. 4.76:1 on card ok.
                                  Distinct from --brand-secondary so brand never
                                  reads as "you erred". */

  /* ── Focus ring (WCAG 2.4.11) ────────────────────────────────────────────
     2px --accent-info; >=3:1 against both surfaces on the default palette. */
  --focus-ring: 0 0 0 2px var(--accent-info);

  /* ══ TYPOGRAPHY ═══════════════════════════════════════════════════════════
     One family, humanist sans. 18px body floor. The default face loads via
     Story 1.2.4's @font-face; the stack below works without it. */
  --font: "Atkinson Hyperlegible Next", system-ui, -apple-system, BlinkMacSystemFont,
          "Segoe UI", Roboto, Helvetica, Arial, sans-serif;

  /* Weights — the default variable face supplies all four. */
  --fw-regular: 400; --fw-medium: 500; --fw-semibold: 600; --fw-bold: 700;

  /* Modular 1.25 scale. 10 size tokens, each with its own line-height.
     Display sizes use clamp() to scale fluidly between the md and xl
     breakpoint constants; static sizes scale uniformly via root font-size.
        token          size (desktop)   line-height   weight   role */
  --fs-display-1: clamp(2.25rem, 0.94rem + 2.84vw, 3.5rem);  /* 36→56px  1.10  700  hero */
  --fs-display-2: clamp(2rem, 1.22rem + 1.70vw, 2.75rem);    /* 32→44px  1.15  700  page H1 */
  --fs-h1:    2.125rem;   /* 34px   1.20  700  content-page H1 */
  --fs-h2:    1.75rem;    /* 28px   1.25  600  section heading */
  --fs-h3:    1.375rem;   /* 22px   1.30  600  sub-section heading */
  --fs-h4:    1.125rem;   /* 18px   1.40  600  minor heading / inline label */
  --fs-body:  1.125rem;   /* 18px   1.60  400  default body — the floor */
  --fs-body-lg: 1.25rem;  /* 20px   1.55  400  intro paragraph / dek */
  --fs-caption: 0.875rem; /* 14px   1.50  500  metadata, stamps */
  --fs-micro: 0.75rem;    /* 12px   1.40  500  legal fine print only */

  --lh-display-1: 1.1;  --lh-display-2: 1.15;
  --lh-h1: 1.2;  --lh-h2: 1.25;  --lh-h3: 1.3;  --lh-h4: 1.4;
  --lh-body: 1.6;  --lh-body-lg: 1.55;  --lh-caption: 1.5;  --lh-micro: 1.4;

  --measure: 43.75rem;  /* 700px — long-form line length, 65–75ch */

  /* ══ SPACING ══════════════════════════════════════════════════════════════
     8pt base, modular. Usage bands:
       within a component  --space-2…4   between components  --space-5…6
       between sections    --space-7…8   hero→first block    --space-8 min.
     Authored in rem (÷16) so spacing scales with the user's root font-size;
     px equivalents noted. */
  --space-1: 0.25rem;  /*   4px */
  --space-2: 0.5rem;   /*   8px */
  --space-3: 1rem;     /*  16px */
  --space-4: 1.5rem;   /*  24px */
  --space-5: 2rem;     /*  32px */
  --space-6: 3rem;     /*  48px */
  --space-7: 4rem;     /*  64px */
  --space-8: 6rem;     /*  96px */
  --space-9: 8rem;     /* 128px */

  /* ══ LAYOUT WIDTHS ════════════════════════════════════════════════════════
     Long-form measure caps at --content; breakouts widen to media/feature
     rows; --content-full is the outer cap past which margins grow but
     content holds steady (the xl-breakpoint behavior). */
  --content:      var(--measure); /* 700px — default reading column */
  --content-wide: 60rem;          /* 960px — breakout rows (media, 2-up cards) */
  --content-full: 80rem;          /* 1280px — page container cap */

  /* ══ RADII ════════════════════════════════════════════════════════════════
     New in the framework (the donor hardcoded 8px in component rules — the
     exact literal Story 1.2.1's no-raw-values lint exists to kill). Two
     steps only; anything rounder is a component decision, not a token. */
  --radius-sm: 0.25rem;  /* 4px — small controls, chips, inputs */
  --radius-md: 0.5rem;   /* 8px — buttons, cards (the donor's observed value) */

  /* ══ ELEVATION ════════════════════════════════════════════════════════════
     Exactly three levels — no more. Default is FLAT; shadow is reserved for
     true elevation, never decoration. Cards use --border-subtle, NOT
     --shadow-1. Soft, low-alpha shadows tinted toward the ink so they read
     calm, not glossy. */
  --shadow-0: none;                              /* Level 0 — flat (page, sections) */
  --shadow-1: 0 1px 4px rgba(26, 26, 34, 0.08);  /* Level 1 — elevated: optional on cards, sticky nav */
  --shadow-2: 0 4px 16px rgba(26, 26, 34, 0.12); /* Level 2 — floating: modals, dropdowns */

  /* ══ ICONOGRAPHY ══════════════════════════════════════════════════════════
     LIBRARY = Lucide (ISC — license text ships beside the vendored SVGs,
     Story 1.2.4). One cohesive set, no icon-mixing. Line icons only, 1.5px
     stroke at 24px default (Lucide ships 2px — override to 1.5 via
     --icon-stroke when inlining). Self-hosted static SVGs, no CDN. */
  --icon-size:   1.5rem;  /* 24px default */
  --icon-stroke: 1.5;     /* unitless; applied as stroke-width on inlined SVGs */

  /* ══ MOTION ═══════════════════════════════════════════════════════════════
     "Motion is friction" — these tokens keep the permitted set SMALL and
     uniform, never invite animation. Motion is OPT-IN per component using
     these durations. Permitted: hover = color/background only; focus ring =
     opacity; dropdown/accordion = height+opacity; scroll-reveal = <=8px
     translate + opacity. Every transition MUST use one of these duration
     tokens so the global prefers-reduced-motion rule catches it. FORBIDDEN
     regardless: autoplay, parallax, infinite spinners, decorative/looping
     animation. */
  --dur-fast:  100ms;  /* focus-ring opacity */
  --dur-hover: 150ms;  /* hover color/background; scroll-reveal lower bound */
  --dur-panel: 200ms;  /* dropdown/accordion height+opacity; scroll-reveal upper bound */
  --ease-out: cubic-bezier(0.22, 0.61, 0.36, 1);  /* the ONLY curve — decelerating, never springy */
  --reveal-shift: 8px; /* max scroll-reveal translate distance */
}
