/* ============================================================================
 * 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 */
    /* --- Dusk (the blue-violet accent family — the evening sky after sunset) --- *
     * Added 2026-10-12. Two tones of one 240deg hue, split by USE: small text reads as less
     * saturated than the same colour in a large block, so the small-text tone is pushed more
     * saturated and the large/fill tone is pulled back.
     *   --dusk        small text — links, labels, bullets (the --accent-primary text tone).
     *                 57% sat. On white 5.84:1, beige 5.36:1, cream 5.31:1, sand 4.85:1 (AA body);
     *                 midnight 3.16:1 (AA large only).
     *   --dusk-muted  large titles, backgrounds and fills. 46% sat, a touch darker, so a
     *                 white/cream label on it is the stronger pairing: white 6.09:1, cream 5.54:1;
     *                 as text on beige 5.59:1, sand 5.06:1; midnight on it 3.03:1 (AA large only). */
    --dusk: #5555D0;
    --dusk-muted: #5555C0;
    --dusk-muted-lift: #6060C4;     /* the lighter dusk-muted: same hue/sat, +3% L. White label 5.28:1. Peak of the lesson progress-bar pulse (via --accent-primary-fill-hover) */
    /* Lavender — a pale violet (267deg, 25% sat, 76% L), more violet than the dusks, so it
     * reads as its own hue rather than a lighter dusk. Added 2026-10-12, meant as a soft BUTTON
     * fill on white with a NIGHT label (8.12:1; midnight 9.16:1). The fill itself is only 2.02:1
     * against white (1.85:1 on beige), so — like the solar fills and their --flare-edge — give
     * it a dusk or dusk-muted edge (5.84:1 / 6.09:1 on white) and draw its focus/press ring in
     * dusk or night, not in lavender. No role points here yet. */
    --lavender: #BFB1D0;          /* 2026-10-13: #BFB1D0 (was #BFB1D6, before that #BFB1DC) */
    --lavender-deep: #B2A1C6;     /* + hover — same hue/sat, -5% L (fills on LIGHT darken on interaction, as solar does). Night label 6.87:1 */
    --lavender-deeper: #A591BD;   /* + active — -10% L. Night label 5.76:1 */
    --lavender-edge: #9F8ACB;     /* the LIGHTEST lavender (260deg, 38% sat — the original lavender's hue/sat, kept when the fill moved to #BFB1D6 and then #BFB1D0) that clears 3:1 against white — 3.01:1. The WCAG 1.4.11 boundary for a lavender fill on white (cf. --solar-edge). 2.76:1 on beige, so on beige it falls short */

    /* --- Cornflower (the blue accent + its lighter on-dark link tints) ---- */
    --cornflower: #5081F3;          /* the primary accent */
    --cornflower-deep: #4460F2;     /* + hover */
    --cornflower-deeper: #3344DF;   /* + 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) */
    /* The ON-DARK interaction tiers for a cornflower FILL: same hue + saturation, +6% lightness
     * per step — the sunbeam/-lift/-lifter rule (the on-dark family brightens on interaction).
     * Midnight label: 5.08:1 base -> 6.94:1 lift -> 8.05:1 lifter -> 10.06:1 liftest. Unlike the
     * -soft tiers these stay fully saturated, so a pressed button still reads as cornflower. */
    --cornflower-lift: #7B9DED;     /* +7% L, and softened toward --cornflower-softer: 76% sat, between the ramp's 87% and softer's 60% (2026-10-13; was #6D96F5). Midnight label 6.94:1 */
    --cornflower-lifter: #89AAF7;   /* +12% L */
    --cornflower-liftest: #A6BFF9;  /* +18% L (2026-10-13 — the press tone once the on-dark button ramp moved up a step) */

    /* --- 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. 5.80:1 on beige (7.42:1 before the 2026-10-12 re-hue), 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. */
    /* 2026-10-12: re-hued 240deg -> 223.5deg (fully saturated). Was #3636CF / #2B2BB2 / #23238E.
     * 2026-10-13: --true-blue-deep is the OKLab midpoint of the other two (perceptually
     * equidistant: ΔE 4.6 to each), re-derived after --true-blue-deeper moved to #0030CE. On beige:
     * 5.80:1 / 6.96:1 / 8.32:1. White label on them: 6.33:1 / 7.59:1 / 9.08:1. */
    --true-blue: #0046FF;         /* light-surface accent + link */
    --true-blue-deep: #003BE6;    /* + hover. 2026-10-13: #003BE6 (was #003BD6) */
    --true-blue-deeper: #0030CE;  /* + active. 2026-10-12: #0030CE (was #0030AE). White label 9.08:1 */

    /* --- 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 */

    /* --- Sky (the cloudscape artwork's own sky blue) --------------------------- *
     * Added 2026-10-12. --sky is the SVG's top sky stop (the colour the cloud heroes fill
     * any strip above the artwork with — still hard-coded as #0962c6 in
     * heliomath_marketing.css and heliomath_account_access.css). Cream (--text-on-dark)
     * on it: 5.34:1. */
    --sky: #0962C6;

    /* --- 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 account-access page background (body.auth-page: login, signup, …) and the
     * learning-differences hero. Cornflower-soft at the bottom-left, through
     * true-blue-deep at 30%, up to --twilight. 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, starting at the bottom-left corner. Applied per
     * page, NOT globally.
     *
     * NOTE the stops are proportional, so "30%" means 30% of whatever element carries
     * this: the full body height on the auth pages (a long signup form stretches it), but
     * only the band on the learning-differences hero. */
    --body-gradient: linear-gradient(5deg, var(--cornflower-soft) 0%, var(--true-blue-deep) 30%, var(--twilight) 100%);
    /* The MIDNIGHT field (midnight across the bottom half -> twilight top-right): the hero
     * A/B's default "manips" look for the about / plans / learning-differences heroes and
     * the account-access field. The "sky" variant (body.hero-sky / .sky-hero) paints the
     * cloudscape instead. See site_pages/hero_variant.py. */
    --surface-dark-gradient: linear-gradient(5deg, var(--midnight) 0%, var(--midnight) 50%, var(--twilight) 100%);
    /* The solid top nav bar (heliomath_components.css `.header:not(.transparent)`): the TOP
     * QUARTER of --sky-horizon-gradient (the About / LD hero fade, 0-25%), stretched over the
     * bar and kept on the bar's lean. The angle runs bottom -> top, so the stops are listed in
     * reverse: the fade's 25% point (#2E88CF, interpolated) sits bottom-LEFT, and its top blue
     * (--sky) top-RIGHT, behind the white nav links. Cream text is 3.73:1 at #2781CE and
     * 5.34:1 on --sky. 2026-10-12. (Also tried the top 15% — #1973CA 0%, #116AC8 36.7%,
     * --sky 100% — but it reads almost flat.) */
    --top-nav-gradient: linear-gradient(15deg,
        #2E88CF 0%,
        #2D87CF 1.6%,
        #2781CE 10.8%,
        #1A74CA 36.4%,
        #116AC8 62%,
        var(--sky) 100%);
    /* The cloudscape artwork's own sky fade (the SVG's `#sky` linearGradient), top sky ->
     * pale aqua -> cream -> gold horizon, straight down. Trimmed to start where the fade
     * starts (the SVG holds --sky flat above it) and rescaled to 0-100%. The mid stops are
     * the artwork's colours, which have no tokens of their own. Used by the About and
     * learning-differences heroes.
     * ⚠ Light below ~25%: cream text is 3.73:1 at #2781CE and falls off fast after it, so
     * copy in the lower part of the band wants dark ink (--night / --midnight). */
    --sky-horizon-gradient: linear-gradient(to bottom,
        var(--sky) 0%,
        #116AC8 9.5%,
        #1A74CA 15.9%,
        #2781CE 22.3%,
        #2D87CF 24.6%,
        #61ADD3 39.7%,
        #A6CBD0 54.8%,
        #D9DCC1 69.9%,
        #F8E3B1 84.9%,
        #FFE9AD 100%);
    /* The SKIES — the whole sky in one token: --sky-horizon-gradient with a transparent cloud
     * SVG over it, as one `background` shorthand value, so every page that shows the
     * cloudscape paints it from one place: `background: var(--sky-full-clouds);`. --sky (the
     * fade's top stop) is the fallback colour. The gradient spans the element it's painted
     * on, top to bottom; each SVG keeps its aspect (width-sized) and is pinned to the bottom.
     *   --sky-full-clouds         the full cloudscape, full width (home / educators / plans
     *                             heroes, login / signup fields)
     *   --sky-right-clouds        the right-hand cloud bank at 75% width, bottom-right (About
     *                             hero, the post-signup .horizon-page fields)
     *   --sky-right-clouds-wide   the right-hand cloud bank at full width (learning-differences
     *                             hero) */
    --sky-full-clouds:
        url("/static/img/brands/heliomath/heliomath-cloud-background-production.e9765210184d.svg") center bottom / 100% auto no-repeat,
        var(--sky-horizon-gradient) 0 0 / auto no-repeat var(--sky);
    --sky-right-clouds:
        url("/static/img/brands/heliomath/heliomath-right-clouds-production.2c00375fc83a.svg") right bottom / 75% auto no-repeat,
        var(--sky-horizon-gradient) 0 0 / auto no-repeat var(--sky);
    --sky-right-clouds-wide:
        url("/static/img/brands/heliomath/heliomath-right-clouds-production.2c00375fc83a.svg") center bottom / 100% auto no-repeat,
        var(--sky-horizon-gradient) 0 0 / auto no-repeat var(--sky);
    /* The marketing hero field (home, educators, plans, about). Same colours and 5deg
     * lean as --body-gradient, but the true-blue stop sits at 50%, not 30%, so the light
     * cornflower end covers more of the band. */
    --hero-background-gradient: linear-gradient(5deg, var(--cornflower-soft) 0%, var(--true-blue-deep) 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(--dusk);                  /* primary / positive accent for text, borders and markers — NOT buttons (they use --btn-accent-*, below). 2026-10-12: dusk (was --true-blue). Dusk: 5.84:1 on white, 5.36:1 on beige; white label on it 5.84:1 */
    /* The accent as a NON-BUTTON fill — selected states, progress bars, badges, any accent
     * background that isn't a button (buttons use the true-blue --btn-accent-* ramp below, never
     * this). Points at the less-saturated --dusk-muted, which reads better as a block of colour
     * than the small-text --dusk (and takes a white label at 6.09:1). Text, borders and markers
     * stay on --accent-primary. */
    --accent-primary-fill: var(--dusk-muted);
    /* …and its lighter tone, staying in dusk-muted's hue. Its one consumer is the lesson
     * progress-bar glow pulse (heliomath_system.css `greenGlowPulse`). The press tone
     * (--accent-primary-fill-active / --dusk-muted-lifter) was retired 2026-10-13 with the
     * dusk buttons that used it. */
    --accent-primary-fill-hover: var(--dusk-muted-lift);
    --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.
     * Rest + hover alias the ROLE tokens; press names the PRIMITIVE, because
     * --accent-primary moved to dusk (2026-10-12) and a blue button must not turn
     * purple while held. Every BUTTON that wants the accent points here, never at the
     * dusk-muted --accent-primary-fill* (that ramp is for non-button fills). */
    --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(--true-blue);              /* press — lightest (true-blue). Was var(--accent-primary), which went dusk */
    --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);
    /* A CORNFLOWER BUTTON ON A DARK SURFACE — the default ramp: rest -> lighter on hover ->
     * lighter still on press (the on-dark family brightens on interaction, as the
     * --flare-on-dark/sunbeam buttons do). Midnight label throughout. Consumed by the
     * account-access submits (--auth-btn-* in heliomath_account_access.css); point any new
     * cornflower-on-dark button here. Moved up one step 2026-10-13 (was cornflower / lift /
     * lifter): a lighter button at rest. */
    --btn-on-dark-fill:  var(--cornflower-lift);      /* 6.94:1 under the midnight label */
    --btn-on-dark-hover: var(--cornflower-lifter);    /* 8.05:1 */
    --btn-on-dark-press: var(--cornflower-liftest);   /* 10.06:1 */
    /* 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(0, 48, 206, 0.30);            /* --true-blue-deeper (--link-active) @ 30%. Was rgba(0, 48, 174, 0.30) before the 2026-10-12 #0030CE change; was rgba(35, 35, 142, 0.30) before the 2026-10-12 re-hue; before that 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(0, 48, 206, 0.45);       /* --true-blue-deeper (--accent-primary-active). Was rgba(0, 48, 174, 0.45) before the 2026-10-12 #0030CE change; was rgba(35, 35, 142, 0.45) before the 2026-10-12 re-hue. 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(0, 48, 206, 0.30);             /* --true-blue-deeper (--link-active) @ 30%. Was rgba(0, 48, 174, 0.30) before the 2026-10-12 #0030CE change; was rgba(35, 35, 142, 0.30) before the 2026-10-12 re-hue; before that 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 */
    /* The face the Heliomath wordmark is drawn in — for text that must sit WITH the logo (the
     * About eyebrow "About"). Self-hosted 300-700 (css/fonts/comfortaa.css, loaded by the
     * marketing bases; alpha_base pages must link it themselves). 700 is its heaviest. */
    --font-family-wordmark: 'Comfortaa', var(--font-family-decorative);

    /* 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);
    /* --shadow-elevated at double the opacity, for a borderless card that has to lift off
     * the beige field on its shadow alone (the plan tiles). */
    --shadow-elevated-strong: 0 1px 8px rgba(31, 22, 15, 0.1);
    /* 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 */
    }
}
