/* ============================================================================
 * Heliomath tokens — "Sunset" (CANONICAL, production).
 *
 * The real, going-forward Heliomath foundation, promoted from palette G
 * ("Sunset") in the throwaway style-guide explorer. SELF-CONTAINED: it holds the
 * COLOUR tokens (two tiers, below), the TYPOGRAPHY tokens (font, weights, type
 * scale) and the STRUCTURE tokens (radius, shadow, border) — no dependency on
 * any other file. Consume every value via var(--token) — never hard-code a hex.
 *
 * TWO TIERS
 *   1. PRIMITIVES — memorable names for the actual paints (--midnight, --coral,
 *      --white …). This IS the palette: to rebrand, change values here.
 *   2. SEMANTIC — role names (--surface-*, --text-*, --accent-* …) that ALIAS
 *      primitives. Components consume THESE, never the primitives, so a value or
 *      a whole palette can change underneath without touching a single consumer
 *      (and, later, a dark theme can re-point them without the names lying).
 *
 * Rule of thumb: primitives are the paint; semantics are where you point. If you
 * are writing a component, reach for a Tier-2 name.
 *
 * True-blue primary on light + cornflower on dark, solar/sunbeam warm accent, on a
 * soft beige with a deep indigo night surface. (The warm + light-blue roles are a
 * TRIAL wired 2026-09-14; every retired paint is still defined in Tier 1.)
 * ========================================================================== */

:root {
    /* ========================================================================
     * TIER 1 — PRIMITIVES (the paints). Hue-agnostic where a hue tweak shouldn't
     * make the name lie (a deep blue is "night", not "blue").
     * ===================================================================== */

    /* --- Neutrals & darks ------------------------------------------------ */
    --white: #FFFFFF;
    --cream: #F9F4E4;     /* warm off-white — light text on dark surfaces */
    --beige: #F6F5F2;     /* the default page surface */
    --sand: #EDEAE0;      /* deeper beige — the recessed "sunken" band */
    --sand-deep: #DFDCD4; /* a touch darker than --sand — a subtle warm edge tone for where white or beige meets the sand surface */
    --night: #0A1A52;     /* deep indigo — dark, but not the darkest */
    --twilight: #0B163F;  /* midway between --midnight and --night — the top end of the dark hero fade */
    --midnight: #0B122C;  /* the darkest colour */

    /* --- Cornflower (the blue accent + its lighter on-dark link tints) ---- */
    --cornflower: #5081F3;          /* the primary accent */
    --cornflower-deep: #3D70E3;     /* + hover */
    --cornflower-deeper: #2159DF;   /* + active */
    --cornflower-soft: #6C8CD7;     /* lighter — links on the night surface */
    --cornflower-softer: #85A0E0;   /* + hover */
    --cornflower-softest: #9FB6E8;  /* + active */
    --cornflower-pale: #B5BDDC;     /* palest + desaturated — muted supporting text on dark surfaces (blue-grey, cooler than the grey --text-on-dark-muted) */

    /* --- True blue (the LIGHT-surface interactive blue — buttons + links) --- *
     * TRIAL, wired 2026-09-14. Cornflower reads well as a fill on the dark bands but
     * washes out on beige; this is the darker, denser blue for everything interactive
     * on a LIGHT surface. 7.42:1 on beige, so it clears AA at body size — which azure
     * (4.96:1) only just did. Cornflower is untouched and is now the ON-DARK family. */
    --true-blue: #3636CF;         /* light-surface accent + link */
    --true-blue-deep: #2B2BB2;    /* + hover */
    --true-blue-deeper: #23238E;  /* + active */

    /* --- Azure (the link blue — brighter/more saturated than cornflower, so
     * links stay distinct from accent UI) --------------------------------- *
     * RETIRED from the roles below (--link now points at --true-blue), kept defined:
     * heliomath_home.css:208 still reaches for --azure-deeper directly. */
    --azure: #255FE7;         /* body links on light surfaces */
    --azure-deep: #2E5ACA;    /* + hover */
    --azure-deeper: #284DAE;  /* + active */

    /* --- Solar (the warm accent — TRIAL, wired 2026-09-14) ---------------- *
     * Replaces the ember/marigold/coral/peach family in the --flare roles. The old
     * warm family sits at 20-25deg, close enough to a signalling red to read as
     * "wrong" to a learner; solar is 40deg — the orange side of YELLOW, which carries
     * no error connotation.
     *
     * The cost of that hue: solar is LIGHT. As text or as a bare fill on beige it is
     * 1.57:1 — invisible — so on light surfaces it needs --flare-border (below), and
     * it must never be used as text there. On the dark bands it is excellent:
     * 10.76:1 on midnight, and a midnight LABEL on a solar fill is 10.76:1. */
    --solar: #FFB92E;          /* 40deg — fill on light (with the edge), display type on dark */
    --solar-deep: #F2A916;     /* + hover on LIGHT (fills darken toward the ground) */
    --solar-deeper: #DE9A0B;   /* + active on light */
    --sunbeam: #FFC24B;        /* the on-dark tier — fill on midnight */
    --sunbeam-lift: #FFCF6B;   /* + hover (the on-dark family brightens on interaction) */
    --sunbeam-lifter: #FFD98C; /* + active */
    --solar-edge: #B87800;     /* 3.35:1 on beige — the WCAG 1.4.11 boundary for solar fills */
    --dawn: #FFF3D6;           /* warm tinted surface; --midnight on it is 16.75:1. No role points here yet. */
    /* Solar is never TEXT on a light surface. If warm ink is ever needed there,
     * #7A4E0A reaches 6.56:1 — but it reads bronze, not sun. */

    /* --- Warm & Intense (ember + its brighter on-dark marigold) ----------------------- *
     * RETIRED from the roles below (--flare now points at --solar), kept defined:
     * heliomath_home.css:444 still reaches for --marigold directly. */
    --ember: #F86800;      /* the ember accent */
    --marigold: #FF8000;
    
    /* --- Warm (coral + its brighter on-dark peach) ----------------------- *
     * RETIRED from the roles below, kept defined: heliomath_marketing.css:585 still
     * reaches for --peach directly. */
    --coral: #D96D54;        /* warm secondary accent, light surfaces */
    --coral-deep: #C85F47;   /* + hover */
    --coral-deeper: #B7513A; /* + active */
    --peach: #EF7C52;        /* brighter warm for dark surfaces */
    --peach-soft: #F59B70;   /* + hover */
    --peach-softer: #FBB489; /* + active (brighter still — the on-dark family brightens on interaction) */

    /* --- Winter-green (the POSITIVE/value accent — the third accent beside cornflower
     * + coral, promoted from the pricing page: "you get this" / included / good). --- */
    --winter-green: #009070;          /* the positive accent */
    --winter-green-deep: #007a5d;     /* + hover, and the readable tier for small text on light */
    --winter-green-deeper: #006b52;   /* + active */

    /* --- Teal (the TEXT accent — added 2026-09-14) ----------------------- *
     * A light blue-green. Its job is the one thing the warm accent cannot do: carry
     * colour on LIGHT surfaces as TEXT. Solar is 1.57:1 on beige, so a coloured title
     * had no option but the blues (which read as interactive) or winter-green.
     *
     * TUNED ON CHROMA, NOT LIGHTNESS. Five cuts of the same hue (188deg) were compared
     * in place, and the deciding axis turned out to be SATURATION: the lighter, duller
     * tones failed to hold their own against white, which is where this colour has to
     * work hardest.
     *   #0C6370  S65 L24 — 6.32:1 beige. Safe at any size; read as near-black.
     *   #15707D  S71 L29 — 5.26:1 beige / 4.79:1 sand. Most contrast available while
     *            still clearing body size everywhere, but still too muted.
     *   #3095A4  S55 L42 — lighter but DESATURATED; washed out on white.
     *   #408F9B  S42 L43 — duller still (rejected for the same reason).
     *   #0B7889  S85 L29 — CHOSEN. The #15707D weight with the chroma pushed up.
     * Measured: beige 4.72:1 | white 5.17:1 | sunken sand 4.29:1 | dawn 4.69:1. Body
     * size is clear on beige and white; the sand band (4.29:1) is a shade under the
     * 4.5:1 body threshold and comfortably over AA-large. #008094 is the same weight at
     * FULL chroma (S100) if it should pop harder: 4.25 / 4.65 / 3.86.
     *
     * It is 3.57:1 from midnight body copy, so a teal heading separates clearly from
     * the text around it — which is what lets a title pull attention without going up
     * a size step.
     *
     * NOT usable on the dark bands: 3.57:1 on midnight, 3.17:1 on night, and no
     * -on-dark peer is defined — those use --flare-on-dark (sunbeam, 11.49:1). */
    --teal: #0B7889;

    /* --- Jade (TRIAL 2026-09-14) ----------------------------------------- *
     * The midpoint between --teal (H188) and --winter-green (H167): H176, i.e. the
     * blue-green exactly between the text accent and the positive accent. Computed as
     * the perceptual (CIE Lab) midpoint; the plain sRGB average lands 8 units away at
     * #06847D, so the choice between the two methods is invisible here.
     *
     * NAME: jade, not emerald — emerald conventionally names a warmer green (~H140),
     * and a palette entry whose name sits 36deg from its hue misleads whoever reaches
     * for it next. Jade is the blue-green in that range.
     *
     * VALUE: #009990 — the computed Lab midpoint was #0E847D (S81 L29); this is the same
     * hue taken to full chroma and one step lighter (S100 L30), chosen by eye.
     *
     * ⚠ That lift costs a lot of contrast: beige 3.22:1 | white 3.52:1 | sunken sand
     * 2.93:1 (the Lab midpoint was 4.16 / 4.55 / 3.78; teal was 4.72 / 5.17 / 4.29).
     * DISPLAY AND HEADING SIZES ONLY (>=24px, or >=19px bold) — and on the sunken sand
     * band it is under even AA-large, which is where the classroom-deployment lead-ins
     * sit at body size. These are the same figures that #3095A4 measured when it was
     * tried as the teal value and rejected for reading too light.
     *
     * At this lightness it DOES read on the dark bands (5.24:1 on midnight), unlike the
     * deeper cuts — but --accent-ink still declares no -on-dark peer.
     *
     * It also sits close to --winter-green, the POSITIVE accent — 1.14:1 apart in
     * luminance and 9deg in hue — so a jade heading beside a green "included" mark may
     * read as one colour rather than two roles. */
    --jade: #009990;

    /* --- Neutral grays (cool blue undertone, hue ~207deg; saturation on a
     * U-curve so bluishness stays even and the darks never look muddy). A
     * numeric ramp is the one place we keep numbers, not names. ----------- */
    --gray-50: #F5F7F9;
    --gray-100: #E9ECEF;
    --gray-200: #D4DBE0;
    --gray-300: #AEB9C2;
    --gray-400: #8896A2;
    --gray-500: #697885;
    --gray-600: #536371;
    --gray-700: #394855;
    --gray-800: #25313B;

    /* ========================================================================
     * TIER 2 — SEMANTIC (the roles). Components consume these. Each aliases a
     * primitive; the few that hold a literal are one-off derived values noted
     * inline. Names & meanings are unchanged from the flat palette, so every
     * existing --surface-* / --text-* / --accent-* / --flare* / --link* consumer
     * keeps working untouched.
     * ===================================================================== */

    /* --- Surfaces / backgrounds ------------------------------------------ */
    --surface-default: var(--beige);      /* default light surface/background */
    --surface-sunken: var(--sand);        /* recessed section band; makes white cards pop */
    --surface-dark: var(--midnight);      /* primary dark background */
    /* The dark HERO field, as a fade rather than a flat fill. The BOTTOM HALF is flat
     * --midnight; only the top half lifts, and it lifts to --twilight (the midpoint
     * between midnight and night) rather than to --night — so the fade is both shorter
     * and gentler than a corner-to-corner one. 5deg is measured the CSS way (clockwise
     * from "to top"), i.e. 5deg off vertical — so the fade runs almost straight up the
     * page with a slight rightward lean, and the gradient's start corner is the bottom
     * left. The two ends are only 1.06:1 apart (it was 1.13:1 to --night), so it reads
     * as depth rather than as a colour change, and cream text holds everywhere on it
     * (16.79:1 on midnight, 15.9:1 on twilight). Applied per page, NOT globally:
     * `.light-hero` is a DARK surface on plans/about but a LIGHT one on home, so a
     * shared rule on that class would repaint the home hero.
     *
     * NOTE the stops are proportional, so "bottom half" means half of whatever element
     * carries this: the full body on the auth pages, but only the band on the three
     * heroes that also use it (about / plans / learning-differences). */
    --surface-dark-gradient: linear-gradient(5deg, var(--midnight) 0%, var(--midnight) 50%, var(--twilight) 100%);
    --surface-dark-alt: var(--night);     /* alt dark surface — the "secondary panel" */
    --surface-elevated: var(--white);     /* elevated cards/panels/inputs. Never a page bg. (Labels on fills are --text-on-dark-fill / --text-on-light-fill.) */
    /* The question figure's own surface. Read from an INLINE style the question JS
     * sets on #figureOneContainer (createFigure.ts / loadQuestion.ts), as
     * `var(--figure-surface, #f6f7f7)` -- inline styles outrank stylesheets, so a
     * plain rule cannot reach it, and an !important one would also clobber the
     * `background: none` those same call sites set for interstitials. Brands that do
     * not define this token get the #f6f7f7 fallback, i.e. Grokkoli is unchanged. */
    --figure-surface: #F6F7F7;

    /* --- Accents (titles, buttons, highlights) --------------------------- */
    /* TRIAL 2026-09-14: the light-surface accent moved cornflower -> true-blue. The
     * ON-DARK trio below is deliberately NOT moved — cornflower stays the blue for
     * fills on the dark bands, where it reads well. To revert, point these three back
     * at --cornflower / -deep / -deeper (and the two rings at the bottom of this block).
     * ⚠ A .btn--accent fill takes the base's MIDNIGHT label, which is 2.27:1 on
     * true-blue (1.84:1 on the hover tone) — it needs --text-on-dark-fill (white,
     * 8.12:1) to be legible. That is a component change, so it is NOT done here. */
    --accent-primary: var(--true-blue);             /* primary / positive / call-to-action */
    --accent-primary-hover: var(--true-blue-deep);
    --accent-primary-active: var(--true-blue-deeper);
    /* The accent BUTTON ramp, which runs the OPPOSITE way to the ramp above: a button
     * starts on the DARKEST of the three true-blues and gets lighter as you interact
     * (rest -> hover -> press), where a link starts light and deepens. Deliberate —
     * a fill and a piece of text read the reverse of each other, and on a light page
     * the darkest tone is what gives a button its weight at rest.
     * These alias the ROLE tokens, not the primitives, so a re-pointed accent (see
     * the trial note above) carries the buttons with it. */
    --btn-accent-fill:  var(--accent-primary-active);  /* rest  — darkest  (true-blue-deeper) */
    --btn-accent-hover: var(--accent-primary-hover);   /* hover — middle   (true-blue-deep) */
    --btn-accent-press: var(--accent-primary);         /* press — lightest (true-blue) */
    --accent-primary-on-dark: var(--cornflower-soft);      /* primary accent on dark surfaces (mirrors --link-on-dark / --text-on-dark) */
    --accent-primary-on-dark-hover: var(--cornflower-softer);
    --accent-primary-on-dark-active: var(--cornflower-softest);
    /* TRIAL 2026-09-14: the warm accent moved ember/coral -> solar (and marigold/peach
     * -> sunbeam on dark). The hover/active pair now stays inside ONE hue — the old
     * ember -> coral-deep step was a 14deg lurch. To revert, point these six back at
     * --ember / --coral-deep / --coral-deeper / --marigold / --peach-soft / --peach-softer. */
    --flare: var(--solar);                    /* warm secondary accent on LIGHT surfaces, as a button/UI FILL only: a midnight button label on solar is 10.76:1, but the FILL itself is only 1.57:1 against beige, so it needs --flare-border to satisfy WCAG 1.4.11. NOT usable as text here — solar on beige is 1.57:1 (the old ember was 2.75:1, itself large-text-only). A .fill-flare pairs it with --flare-label. */
    --flare-hover: var(--solar-deep);         /* + hover (fill) */
    --flare-active: var(--solar-deeper);      /* + active (fill). ⚠ heliomath_forms.css uses this for validation ERROR TEXT on white: 2.40:1, down from coral-deeper's 4.94:1. */
    --flare-on-dark: var(--sunbeam);             /* the same warm accent for DARK surfaces — brighter than --flare so it reads against night ("-on-dark" mirrors --link-on-dark / --text-on-dark). As a button FILL the midnight label is 11.49:1; as display text on midnight it is 11.49:1. */
    --flare-on-dark-hover: var(--sunbeam-lift);
    --flare-on-dark-active: var(--sunbeam-lifter);
    /* NEW, unconsumed: a 2px edge for a solar FILL over a light surface, where the fill
     * alone is 1.57:1. --solar-edge is 3.35:1 on beige, i.e. exactly the 1.4.11 boundary.
     * No component reads this yet — wiring it into .btn--flare / .fill-flare is a
     * component change. */
    --flare-border: var(--solar-edge);

    /* --- Accent SECONDARY (winter-green) — the POSITIVE / value / included role: the
     * 7-day-free-trial badge, the plan bonuses ("+"), the money-back guarantee callouts,
     * and ✓ "included" marks. A PEER of --flare, distinct by character: secondary =
     * positive-green, flare = warm-coral. Small text on a light surface should reach for
     * the -hover (deep) tier — the base green is a touch light for body-size contrast. */
    --accent-secondary: var(--winter-green);
    --accent-secondary-hover: var(--winter-green-deep);
    --accent-secondary-active: var(--winter-green-deeper);

    /* --- Accent INK — the third accent, and the only one that is TEXT, not a fill: a
     * subdued counterpart to --flare for titles, eyebrows and inline emphasis on LIGHT
     * surfaces. Distinct from its two siblings by ROLE, not just hue:
     *   --accent-primary    interactive — a button fill or a link. Colour means "click".
     *   --accent-secondary  positive/value — "included", guarantees, ✓ marks.
     *   --accent-ink        neither. Pure typographic colour: variety and hierarchy, so
     *                       a title can pull attention without going up a size step.
     * Carries no hover/active tiers — nothing wearing it is interactive. Use it where
     * you would otherwise reach for --flare and discover solar is unreadable on light.
     * SIZE, not surface, is the constraint at the current jade value: display/heading
     * text only (3.22:1 on beige, 2.93:1 on sand) — see the --jade note above. */
    --accent-ink: var(--jade);   /* TRIAL — was var(--teal); both stay defined */

    /* --- Text ------------------------------------------------------------ */
    --text-primary: var(--midnight);        /* body text on light surfaces */
    --text-lively: var(--night);            /* alt primary ink — nearly as dark as --text-primary (marginally lighter, ~15:1 vs ~17:1 on beige) but with a touch of blue chroma. Both sit far above any threshold, so the eye can't rank them: it reads as a tonal shift, NOT a hierarchy step. Use sparingly to keep a large, flat block of text from feeling dead */
    --text-muted: var(--gray-600);          /* muted / de-emphasized body text on light */
    --text-subtle: var(--gray-500);         /* subtle / de-emphasized text on light */
    --text-caption: var(--gray-500);        /* captions / large-text on beige */
    --text-disabled: var(--gray-300);       /* placeholder + disabled text */
    --text-on-dark-fill: var(--white);      /* label on a DARK-enough fill — accent-primary (8.12:1 on true-blue) and cornflower. Decoupled from --surface-elevated. NB: distinct from --text-on-dark, which is body text on a dark SURFACE, not a fill. */
    --text-on-light-fill: var(--night);     /* label on a LIGHT/bright fill too pale for white — e.g. solar/sunbeam (--flare), where white is 1.72:1. */
    --text-on-dark: var(--cream);           /* light text on dark surfaces */
    --text-on-dark-muted: var(--gray-400);  /* muted text on the night surface */

    /* --- Links ----------------------------------------------------------- */
    /* TRIAL 2026-09-14: links moved azure -> true-blue, so one blue now covers
     * everything clickable on a light surface (button fill and link alike). 7.42:1 on
     * beige vs azure's 4.96:1. To revert, point these three back at --azure / -deep /
     * -deeper, plus --focus-ring-link and --link-fill below. */
    --link: var(--true-blue);                      /* body links on light surfaces; always underlined */
    --link-hover: var(--true-blue-deep);
    --link-active: var(--true-blue-deeper);
    --link-on-dark: var(--cornflower-soft);        /* links on the night surface */
    --link-on-dark-hover: var(--cornflower-softer);
    --link-on-dark-active: var(--cornflower-softest);

    /* --- Borders / dividers ---------------------------------------------- */
    --border: var(--gray-300);         /* default hairline on light surfaces — borders (cards, inputs) and dividers */
    --border-subtle: var(--gray-200);  /* lighter hairline on light */
    --border-on-dark: var(--gray-700); /* hairline on the night surface */
    --border-on-sunken: var(--sand-deep); /* warm hairline where white/beige meets the sunken (sand) surface — darker & warmer than --border so the edge reads on sand */
    --border-warm: var(--sand-deep);      /* warm-toned hairline for elements on any light surface (beige, the sunken sand band, or white) — e.g. the .card edge. Names the TONE, not a surface, so it isn't coupled to --border-on-sunken (the sand/white seam): they share the paint today but can be retuned independently. */

    /* --- Notifier / badge — the small inline status pill. bg + border are the
     * secondary surface flattened onto white (12% / 32% alpha) so the pill is
     * opaque and reads the same on any white surface. Kept literal: color-mix()
     * would derive them from --night, but it's above our browser floor (see the
     * focus-ring note below) and a dropped notifier fill is a visible break. */
    --notifier-bg: #E2E4EA;                    /* = --night @ 12% on white, flattened */
    --notifier-border: #B1B6C8;                /* = --night @ 32% on white, flattened */
    --notifier-text: var(--night);  /* points at the primitive, not --surface-dark-alt, so notifier TEXT isn't coupled to a SURFACE role */

    /* --- Focus rings (each is its source colour @ 45%) ------------------- *
     * NAMING: bare `--focus-ring` is the default; the rest are suffixed by the
     * control/accent they pair with, plus "-on-dark" / "-on-accent" for the
     * context they sit against (mirrors --link-on-dark / --text-on-dark):
     *   --focus-ring                default — any control on a light surface
     *   --focus-ring-flare          warm (flare) control, light
     *   --focus-ring-flare-on-dark  warm control / CTA, dark
     *   --focus-ring-link           inline link, light
     *   --focus-ring-link-on-dark   inline link, dark
     *   --focus-ring-on-accent      an accent-COLOURED element (e.g. an accent
     *                               link) — a darker accent tone so the ring
     *                               shows against it
     * Links use their *active* tone for extra salience; comments show the exact
     * primitive (and the semantic token it matches).
     *
     * Kept as literal rgba on purpose. color-mix() (or relative-colour syntax)
     * would derive these straight from the primitives, but BOTH are above our
     * browser floor: color-mix() needs Safari 16.2+ / Chrome 111+ / Firefox 113+,
     * we support Safari 14.1 / Chrome 84 / Firefox 63. On an older browser a
     * color-mix() value makes the whole box-shadow invalid — i.e. NO focus ring,
     * a WCAG 2.4.7 regression, not a cosmetic one — so don't gate it on a too-new
     * feature. If you retune an accent/link primitive, update its ring here too. */
    /* TRIAL 2026-09-14: four of these six moved with the re-pointed roles above.
     * The old literals are kept in the trailing comments so the trial can be undone. */
    --focus-ring: 0 0 0 3px rgba(54, 54, 207, 0.45);                /* --true-blue (--accent-primary). Was rgba(80, 129, 243, 0.45) — cornflower */
    --focus-ring-flare: 0 0 0 3px rgba(184, 120, 0, 0.45);          /* --solar-EDGE, not the fill (--flare). A ring in solar itself is 1.57:1 on beige — invisible. Was rgba(217, 109, 84, 0.45) — coral */
    --focus-ring-flare-on-dark: 0 0 0 3px rgba(255, 194, 75, 0.45); /* --sunbeam (--flare-on-dark). Was rgba(239, 124, 82, 0.45) — peach */
    /* Inline links use 0.30 (not 0.45) so the ring matches the soft PILL FILL below —
     * pressed/focused links get a filled pill, not just a ring. */
    --focus-ring-link: 0 0 0 3px rgba(35, 35, 142, 0.30);           /* --true-blue-deeper (--link-active) @ 30%. Was rgba(40, 77, 174, 0.30) — azure-deeper. Moved in step with --link-fill below: the two must stay identical (ring + pill fill read as one) */
    --focus-ring-link-on-dark: 0 0 0 3px rgba(159, 182, 232, 0.30); /* --cornflower-softest (--link-on-dark-active) @ 30% */
    --focus-ring-on-accent: 0 0 0 3px rgba(35, 35, 142, 0.45);      /* --true-blue-deeper (--accent-primary-active). Was rgba(33, 89, 223, 0.45) — cornflower-deeper */

    /* Inline-link pressed/focus PILL FILL — the link's own colour @ 30% behind the
     * label (same colour + opacity as the ring above, so ring and fill read as one).
     * a:focus (base.css) also drops the underline on press. Surface-overridable: the
     * on-dark contexts re-point --link-fill (and --focus-ring-link) to the on-dark pair
     * where they already re-point the link colour. */
    --link-fill: rgba(35, 35, 142, 0.30);            /* --true-blue-deeper (--link-active) @ 30%. Was rgba(40, 77, 174, 0.30) — azure-deeper */
    --link-fill-on-dark: rgba(159, 182, 232, 0.30);  /* --cornflower-softest (--link-on-dark-active) @ 30% */

    /* ========================================================================
     * TYPOGRAPHY — font family, weights, and the type scale.
     * ===================================================================== */

    /* Poppins for all UI/headings, with DM Sans then Open Sans as fallbacks (Open Sans
     * only renders if the visitor already has it — it is NOT web-loaded) and Arial as the
     * system fallback. Poppins + DM Sans are web-loaded in _foundation_head / _solid_nav_head.
     * Generous line-height and a 16px+ body floor for legibility. */
    --font-family-base: 'Poppins', 'DM Sans', 'Open Sans', Arial, sans-serif;
    --font-family-decorative: 'quicksand', 'Nunito', 'Helvetica Neue', Arial, sans-serif;  /* for the H1/H2/H3 hero display on the home page */

    /* Light-on-dark text blooms, so identical Poppins reads a step heavier on midnight.
     * Hence two ladders: dark surfaces re-point the ACTIVE names to the -on-dark rungs
     * (see the dark-context block in heliomath_components.css). Only call sites that go
     * through the tokens thin — a hardcoded `font-weight: 500` stays 500 on dark. */
    --font-weight-light-on-light: 200;
    --font-weight-regular-on-light: 300;
    --font-weight-medium-on-light: 400;
    --font-weight-semibold-on-light: 500;
    --font-weight-bold-on-light: 600;

    --font-weight-light-on-dark: 100;   /* Poppins Thin — fragile at body size; raise to 200 if on-dark copy reads under-set */
    --font-weight-regular-on-dark: 200;
    --font-weight-medium-on-dark: 300;
    --font-weight-semibold-on-dark: 400;
    --font-weight-bold-on-dark: 500;

    /* THE ACTIVE LADDER — what components consume. */
    --font-weight-light:    var(--font-weight-light-on-light);
    --font-weight-regular:  var(--font-weight-regular-on-light);
    --font-weight-medium:   var(--font-weight-medium-on-light);
    --font-weight-semibold: var(--font-weight-semibold-on-light);
    --font-weight-bold:     var(--font-weight-bold-on-light);

    /* Type scale — FLUID size / fixed line-height pairs.
     * Each size is calc(rem floor + a small vw): text sits at its rem minimum on
     * the narrowest screens and grows gently toward desktop. The rem floor is
     * what keeps it accessible — it still scales with the user's font-size / zoom
     * (a pure-vw or px-based size would NOT, risking WCAG 1.4.4). Growth is capped
     * on very wide screens by the @media block after :root. Bigger elements carry
     * a bigger vw term, so the scale opens up on desktop. Comments show px at the
     * rem floor; the vw coefficients are the tuning knobs. */
    --type-h1-size: calc(2.25rem + 0.65vw);        /* floor 36px */
    --type-h1-line: 1.15;
    --type-h2-size: calc(1.8rem + 0.5vw);         /* floor ~29px */
    --type-h2-line: 1.2;
    --type-h3-size: calc(1.4rem + 0.4vw);         /* floor 24px */
    --type-h3-line: 1.25;
    --type-h4-size: calc(1.15rem + 0.3vw);         /* floor 20px */
    --type-h4-line: 1.3;
    /* FAQ question — a REPEATED UI label, not a heading, so it must not ride the type scale's
     * growth + wide-screen bump (which pushed h4 to ~26px on desktop). NO clamp()/min()/max():
     * unsupported below Firefox 75 (our floor is FF63) — it would fail on the low-end devices we
     * target. Instead: body's 1.05rem floor + a 0.1rem step up (1.15rem), and the SAME tiny
     * 0.25vw growth as body — so it stays a consistent notch above body copy at every width and
     * never balloons. Intentionally OMITTED from the ≥1600px @media bump below. */
    --type-faq-question-size: calc(1.15rem + 0.25vw);
    --type-body-size: calc(1.05rem + 0.25vw);      /* floor ~17px — comfortable default for the audience */
    --type-body-line: 1.6;
    --type-body-small-size: calc(0.95rem + 0.15vw);   /* floor ~15px */
    --type-body-small-line: 1.5;
    --type-caption-size: calc(0.875rem + 0.15vw);  /* floor 14px — captions/labels only, not body copy */
    --type-caption-line: 1.4;

    /* Control label — buttons / inputs. Fluid, floor 18px, cap 24px (0.375vw;
     * cap = 1.125 + 0.375 = 1.5rem in the @media block below).
     * Contrast note: button fills carry a DARK (midnight) label. On solar (10.76:1)
     * and sunbeam (11.49:1) that clears AA comfortably, and on cornflower (5.08:1) it
     * clears AA at body size. ⚠ On the re-pointed --accent-primary (true-blue) it is
     * 2.27:1 and FAILS — an accent fill needs a white label; see the note on
     * --accent-primary above. The vw holds the
     * label >=19.2px from a 320px viewport up, so real devices are safe. Bump the
     * floor to 1.1875rem (19px) to lift the nominal floor above 18.66px too. */
    --type-control-size: calc(1.125rem + 0.375vw);   /* floor 18px */

    /* ========================================================================
     * STRUCTURE — radius, shadow, border, spacing.
     * ===================================================================== */

    --radius-card: 16px;
    --radius-control: 10px;
    /* Pill: a radius larger than any control's half-height, so ends round fully
     * at any height. Buttons are pills by default. */
    --radius-pill: 999px;
    --shadow-elevated: 0 1px 8px rgba(31, 22, 15, 0.05);
    --shadow-elevated-hover: 0 4px 22px rgba(31, 22, 15, 0.07);
    /* White surfaces on beige are only ~1.05:1 apart, so they ALWAYS need a
     * shadow and/or this gray-300 border to separate them from the field. */
    --border-hairline: 1px solid var(--gray-300);

    /* --- Spacing scale — a 4px-grid ramp for padding, gaps, margins -------- *
     * rem so spacing scales with the user's font-size / zoom, like the type.
     * The small steps (2xs–sm, 4–12px) are FIXED — fluidising a tiny gap only
     * adds jitter. From md up they are FLUID (rem floor + a pronounced vw) so
     * layout spacing opens up with the viewport; the rem floor is the compact
     * mobile value and bigger steps carry a bigger vw. Growth is capped at 1200px
     * by the @media block below — spacing has no reason to keep growing once
     * content hits its max-width (hence 1200, not the type scale's 1600).
     * Cap identity at 1200: cap_rem = floor + 0.75 x vw (1vw = 12px there). */
    --space-2xs: 0.25rem;              /* 4px  — fixed */
    --space-xs:  0.5rem;               /* 8px  — fixed */
    --space-sm:  0.75rem;              /* 12px — fixed */
    --space-md:  calc(1rem + 0.5vw);   /* floor 16px */
    --space-lg:  calc(1.5rem + 1vw);   /* floor 24px */
    --space-xl:  calc(2rem + 1.5vw);   /* floor 32px */
    --space-2xl: calc(3rem + 2.5vw);   /* floor 48px */
    --space-3xl: calc(4rem + 3.5vw);   /* floor 64px */
    --space-4xl: calc(6rem + 5vw);     /* floor 96px */

    /* Horizontal page gutter — left/right padding on page-width containers. Its
     * own token (not a scale step): a small mobile floor with a big vw, so edges
     * stay tight on phones and open up generously on desktop. Capped at 1200. */
    --space-gutter: calc(1rem + 6vw);  /* floor 16px */
}

/* ============================================================================
 * FLUID TYPE CAP — freeze the type scale at fixed maxes on very wide screens so
 * calc(rem + vw) doesn't run away on 4K / ultrawide. Each value ≈ its fluid size
 * evaluated at 1600px, so the transition across the breakpoint is seamless. This
 * is the floor-safe stand-in for clamp()'s upper bound (clamp needs Firefox 75,
 * below our floor). Retune these together with the vw coefficients above.
 * ========================================================================== */
@media (min-width: 1600px) {
    :root {
        --type-h1-size: 3rem;             /* 48px */
        --type-h2-size: 2.35rem;          /* ~38px */
        --type-h3-size: 1.95rem;          /* ~31px */
        --type-h4-size: 1.65rem;          /* ~26px */
        --type-body-size: 1.3rem;         /* ~21px */
        --type-body-small-size: 1.1rem;   /* ~18px */
        --type-caption-size: 1.025rem;    /* ~16px */
        --type-control-size: 1.5rem;      /* 24px */
    }
}

/* ============================================================================
 * FLUID SPACING CAP — freeze the fluid spacing steps at 1200px (earlier than the
 * type's 1600: once content reaches its max-width, spacing needn't keep growing).
 * Each value = its fluid size at 1200px, so the freeze is seamless. Because
 * 1vw = 12px = 0.75rem at 1200, each cap = floor + 0.75 x vw. Retune together
 * with the vw coefficients above.
 * ========================================================================== */
@media (min-width: 1200px) {
    :root {
        --space-md:  1.375rem;   /* 22px */
        --space-lg:  2.25rem;    /* 36px */
        --space-xl:  3.125rem;   /* 50px */
        --space-2xl: 4.875rem;   /* 78px */
        --space-3xl: 6.625rem;   /* 106px */
        --space-4xl: 9.75rem;    /* 156px */
        --space-gutter: 5.5rem;  /* 88px */
    }
}
