/* ================================================================
   DESIGN TOKENS — single source of truth (two-tier architecture)
   Load order: this file MUST be the first stylesheet in every entry
     point — index.html gets it via style.css @import (first line),
     admin.html via a direct <link> before all other CSS.
   Tier 1 · PRIMITIVES : raw palette values. Theme-agnostic.
     Component CSS should not reference these directly, except for
     brand-decorative uses (gradients, spinners) that stay identical
     in every theme.
   Tier 2 · SEMANTIC   : role tokens (text / surface / border /
     accent / status). Component CSS consumes these. A future dark
     theme overrides ONLY this tier via
       html[data-theme="dark"] { --surface-card: …; }
     — never the primitives.
   Compat: retained names preserve existing consumer contracts. Retire a
     token only after reviewing CSS, inline markup and JavaScript consumers.
   Naming note: --text-<size> (--text-xs, --text-md, …) is the TYPE
     SCALE; colour roles for text live in the --text-<role> group
     in Tier 2 (ink/strong/body/secondary/…).
   ================================================================ */
/* The second selector keeps .public-home (login/landing) a LIGHT ISLAND under
   the dark theme: it re-applies these light values with higher specificity
   than the html[data-theme="dark"] block at the bottom of this file, so the
   page's Tier 2 text roles can never invert against its light public surfaces. */
:root,
html[data-theme="dark"] .public-home {


  /* `only` pins the USED colour scheme to the value we declare. Without it a
     UA holding an overriding dark preference (Chrome force-dark / Auto Dark
     Mode) may return dark instead, and every light-dark() call site would then
     take its dark half while these tokens stayed light — a half-themed page.
     Plain declaration first: engines predating the `only` keyword drop the
     second line and keep exactly today's behaviour. */
  color-scheme: light;
  color-scheme: only light;

  /* ── Viewport correction (not a colour token) ──
   * How far the layout viewport's bottom edge sits below the *visible* one:
   * the mobile keyboard, or an in-app browser's toolbar (LINE/FB/IG)
   * overlapping the layout viewport. Synced live by core/iab-viewport.js;
   * bottom-anchored UI (quiz/exam sheet footer, pwc + esm bottom sheets)
   * subtracts it so its buttons stay on screen. 0px is both the desktop
   * value and the no-JS fallback. */
  --vv-bottom-inset: 0px;

  /* ── Font role tokens — single source of truth (Phase 1) ──
   * --font-sans   : body / UI text        (canonical app font)
   * --font-display: headings / emphasis   (geometric, for prominence)
   * Change a typeface here, not across 100+ scattered declarations. */
  --font-sans: 'Sarabun', 'Noto Sans Thai', sans-serif;
  --font-display: 'Kanit', 'Sarabun', 'Noto Sans Thai', sans-serif;
  --font-mono: ui-monospace, 'SFMono-Regular', Menlo, Consolas, monospace; /* code, ids and call types on admin surfaces */

  /* ── Tier 1 · Primitives ── */
  --pink: #ff5e91;
  --pink-dark: #ff2e6e;
  --pink-deeper: #d4005a;    /* deepest brand pink — link hover and public home */
  --pink-muted: #bd4058;     /* quiz choice label glyph */
  --pink-500: #ec4899;       /* midterm exam-type pill */
  --pink-blush: #fbcfe8;     /* activity heatmap ramp, step 2 */
  --pink-light: #ffe8f5;     /* Pink-100 (also absorbed #ffe4ef) */
  --pink-100: #fce7f3;       /* final-exam scope chip fill */
  --pink-200: #ffb8d4;       /* feature-icon gradient end */
  --pink-50: #fdf2f8;        /* faintest pink wash (absorbed #fff5f8, #fff0f5) */
  --magenta: #d14bce;        /* hero headline gradient accent */
  --pink-400: #f472b6;       /* weak-topic arrow gradient start */
  --pink-700: #be185d;       /* weak-topic icon / pill text */
  --pink-vivid: #e91e63;     /* weak-topic arrow gradient end */
  /* Rose — the "needs attention / urgent" ramp (dashboard urgent card,
     weak-topic pill). Deliberately distinct from --red-* (errors) and
     --pink-* (brand). */
  --rose-25: #fff5f7;        /* faintest urgent wash */
  --rose-50: #fff1f2;
  --rose-100: #ffe4e6;       /* absorbed #ffe4e9 */
  --rose-200: #fecdd3;
  --rose-400: #fb7185;
  --rose-500: #f43f5e;
  --rose-600: #e11d48;
  --rose-700: #be123c;
  --rose-800: #9f1239;
  --yellow: #ffc107;
  --yellow-100: #fef9c3;     /* feature-icon gradient start */
  --yellow-600: #ca8a04;     /* history percentage and quiz difficulty */
  --blue: #3b82f6;
  --blue-light: #dbeafe;     /* Blue-100 */
  --blue-300: #93c5fd;       /* feature-icon gradient end */
  --blue-400: #60a5fa;       /* "new topic" status icon */
  --blue-600: #2563eb;       /* feature-icon glyph, midterm badge */
  --blue-700: #1d4ed8;       /* midterm scope chip text */
  --purple: #a855f7;
  --purple-light: #f3e8ff;   /* Purple-100 */
  --purple-dark: #7c3aed;    /* interactive/active purple (Violet-600) */
  --purple-deeper: #6d28d9;  /* darkest purple text on white (Violet-700) */
  --purple-50: #faf5ff;      /* faintest purple wash (inputs, selects) */
  --purple-200: #e9d5ff;     /* tinted input border */
  --purple-300: #c4b5fd;     /* chip hover border, soft chevrons (absorbed #d8b4fe) */
  --purple-400: #c084fc;     /* avatar hover border, stat-number gradient end */
  --purple-600: #9333ea;     /* clip-bookmark modal header gradient start */
  --purple-800: #5b21b6;     /* deepest accent text (link hover) */
  --purple-900: #4c1d95;     /* stats section heading */
  --purple-75: #f5f0fe;      /* accent surface, hover step above --purple-50 */
  /* Violet stops that are NOT in the --purple-* ramp above (Tailwind Violet) */
  --violet-100: #ece0ff;     /* history icon tile */
  --violet-400: #a78bfa;     /* avatar / final-pill gradient start */
  --violet-500: #8b5cf6;     /* final exam-type pill */
  --purple-gray: #6b5b95;    /* muted purple-grey control text */
  --green: #10b981;
  --green-light: #d1fae5;
  --green-100: #dcfce7;      /* success chip fill */
  --green-300: #86efac;      /* trend-up text on dark hero */
  --green-400: #34d399;      /* easy difficulty pill gradient end (Emerald-400) */
  --green-vivid: #4ade80;    /* grade-scale track (Green-400 — brighter than above) */
  --green-mid: #22c55e;      /* create / positive action (Green-500) */
  --green-dark: #16a34a;     /* success emphasis on light bg (Green-700) */
  --green-50: #f0fdf4;       /* success surface */
  --green-200: #bbf7d0;      /* success border */
  --green-strong: #059669;   /* success toast gradient start (Emerald-600) */
  --green-700: #15803d;      /* "all clear" text on green surface */
  --green-deep: #047857;     /* success toast gradient end (Emerald-700) */
  --green-800: #065f46;      /* deepest success text (Emerald-800) */
  --orange: #f97316;
  --orange-light: #fff7ed;   /* orange surface (corrected; was #fee2e2 = red) */
  --orange-200: #fed7aa;     /* notice border */
  --orange-400: #fb923c;     /* grade-scale track */
  --orange-600: #ea580c;     /* midterm exam card badge text */
  --orange-800: #9a3412;     /* notice body text */
  --orange-900: #7c2d12;     /* notice heading text */
  --amber: #f59e0b;          /* warning (Amber-400) */
  --amber-50: #fffbeb;       /* faintest warning wash ("next quiz" CTA) */
  --amber-light: #fef3c7;    /* warning surface */
  --amber-200: #fde68a;      /* feature-icon / home-button gradient start */
  --amber-300: #fcd34d;      /* final-exam scope badge border */
  --amber-400: #fbbf24;      /* home-button gradient mid */
  --amber-bright: #feb813;   /* admin role badge gradient start */
  --amber-strong: #d97706;   /* vivid warning numerals (Amber-600) */
  --amber-deep: #b45309;     /* warn toast gradient start (Amber-700) */
  --amber-deeper: #92400e;   /* warning text on --amber-light, warn toast gradient end (Amber-800) */
  --red: #ef4444;            /* error / danger (Red-500) */
  --red-dark: #dc2626;       /* error emphasis (Red-600) */
  --red-light: #fee2e2;      /* error surface (Red-100) */
  --red-50: #fef2f2;         /* faintest error surface */
  --red-200: #fecaca;        /* error border */
  --red-300: #fca5a5;        /* error hover border */
  --red-400: #f87171;        /* hard difficulty pill gradient end */
  --red-800: #991b1b;        /* wrong-answer text on a red surface */
  --red-deep: #b91c1c;       /* strong error text on tinted surface (Red-700) */
  --indigo-50: #eef2ff;      /* icon-button hover wash */
  --indigo: #6366f1;         /* SW update toast, "continue" hero arrow */
  --indigo-dark: #4f46e5;
  --indigo-700: #4338ca;     /* "continue" hero icon */
  --cyan: #06b6d4;
  --cyan-light: #cffafe;
  --cyan-600: #0891b2;       /* review-answers secondary button glyph */
  --sky-50: #f0f9ff;         /* review-answers button hover wash */
  --sky-200: #bae6fd;        /* review-answers button border */
  /* YouTube brand red — third-party, never re-tinted by a theme */
  --youtube: #ff0000;
  --youtube-bright: #ff2222;
  --youtube-dark: #cc0000;
  --youtube-deep: #b30000;
  --dark: #1a1a2e;
  --dark-deeper: #0f0f1a;    /* footer gradient end */
  --dark-light: #27272a;
  --gray: #6b7280;           /* secondary/helper text (Gray-500) */
  --gray-strong: #4b5563;    /* strong secondary text (Gray-600) */
  --gray-cool: #9ca3af;      /* muted text, secondary icons (Gray-400) */
  --gray-dim: #d1d5db;       /* disabled text, placeholders (Gray-300) */
  --gray-border: #e5e7eb;    /* dividers, default input borders (Gray-200) */
  --gray-subtle: #f9fafb;    /* surface-hover, row alternate bg (Gray-50) */
  --gray-light: #f3f4f6;     /* light surface bg (Gray-100) */
  --gray-lighter: #fafbfc;   /* near-white bg (lighter than Gray-50) */
  --white: #ffffff;

  /* Elevation */
  --shadow: 0 2px 12px rgba(0,0,0,0.08);
  --shadow-md: 0 4px 20px rgba(0,0,0,0.1);
  --shadow-lg: 0 8px 32px rgba(0,0,0,0.15);
  --shadow-xl: 0 12px 48px rgba(0,0,0,0.2);

  /* Radius */
  --radius-2xs: 4px;    /* micro: dot indicators, progress nubs */
  --radius-xs: 8px;     /* small controls, chips, table cells */
  --radius-sm: 12px;
  --radius-md: 16px;
  --radius: 20px;
  --radius-lg: 24px;
  /* Semantic radius aliases — use these in components */
  --r-card: var(--radius);
  --r-control: var(--radius-sm);
  --r-control-lg: 14px;         /* roomier control radius — polished inputs / tab strips (ui-polish) */
  --r-chip: var(--radius-xs);   /* small badges / tags */
  --r-pill: 999px;

  /* Typography scale — Thai + Kanit/Sarabun; use .ec-text-* / .ec-modal-* helpers in templates */
  --text-micro: 0.68rem;
  --text-2xs: 0.72rem;
  --text-xs: 0.75rem;
  --text-caption-sm: 0.78rem;
  --text-caption: 0.8125rem;
  --text-sm: 0.85rem;
  /* NOTE: there is deliberately no --text-muted here. It used to be a font
     size (0.88rem) that nothing consumed, while six rules used it as a COLOUR —
     making `color: var(--text-muted)` invalid-at-computed-value-time. Colour
     roles live in the --text-<role> group below. */
  --text-body-sm: 0.9rem;
  --text-banner: 0.92rem;
  --text-ui: 0.95rem;
  --text-md: 1rem;
  --text-lg: 1.05rem;
  --text-xl: 1.125rem;
  --text-subtitle: 1.15rem;
  --text-title-sm: 1.25rem;
  --text-avatar-row: 1.4rem;
  --text-emoji-row: 1.5rem;
  --text-display-sm: 1.6rem;
  --text-avatar-preview: 2.5rem;
  --text-icon-xl: 3rem;
  --text-overline: 0.7rem;

  --leading-tight: 1.25;
  --leading-snug: 1.35;
  --leading-normal: 1.5;

  /* ── Tier 2 · Semantic roles ──
     Component CSS consumes these. A future dark theme overrides this
     tier only (html[data-theme="dark"] { … }). */
  /* Learner surfaces: canonical mood-tone, also available before gym CSS loads. */
  --learner-action-primary: #a82f64;
  --learner-action-hover: #922652;
  --learner-on-action: #ffffff;
  --learner-surface-workspace: #faf8fa;
  --learner-surface-selected: #fbf1f6;
  --learner-surface-support: #f1edf8;
  --learner-surface-card: #ffffff;
  --learner-text-ink: #242433;
  --learner-text-secondary: #626475;
  --learner-border-default: #e8dfe7;
  --learner-border-interactive: #8d8595;
  --learner-quiz-header: #9f3263;


  /* Text roles */
  --text-ink: #1e1333;              /* brand-tinted near-black: app primary text, headings */
  --text-ink-soft: #3b3158;         /* softened ink — outline controls, secondary-emphasis text */
  --text-strong: #111827;           /* pure near-black: numbers, strong emphasis */
  --text-body: #374151;             /* body copy, secondary headings (Gray-700) */
  --text-secondary-strong: var(--gray-strong); /* Gray-600 — control labels, neutral chip text */
  --text-default: var(--dark-light);/* document body copy (pre-login shell) */
  --text-secondary: var(--gray);    /* helper text, sublabels */
  --text-tertiary: var(--gray);     /* 4.83 card / 4.63 sunken — --gray-cool
                                       (Gray-400) read 2.54 / 2.43, under AA on
                                       both. The DARK tier was repaired on its
                                       own (see its note below); this is the
                                       light half of the same pair, left behind
                                       until it was measured in a browser.
                                       Points at an existing primitive rather
                                       than a new literal: tokens.css sits at
                                       its hex ceiling, and a token file is the
                                       one place where "spend a hex" and "use a
                                       token" are the same sentence — so the
                                       ceiling is what decides.
                                       --gray-cool itself is NOT darkened: it
                                       is read directly elsewhere (locked
                                       rows in plan.css, the footer), which
                                       a change here would move too.
                                       AA leaves light no room to sit quieter
                                       than --text-secondary, which is this same
                                       value: in this theme the tertiary role is
                                       carried by size and weight. Dark has the
                                       room and keeps the gap. */
  --text-disabled: var(--gray-dim);
  --text-inverse: var(--white);     /* text on filled/dark surfaces */
  --text-accent: var(--purple-deeper);
  --text-brand: var(--pink-deeper);  /* brand-pink TEXT: raw --pink is 2.89:1 on white */

  /* Surface roles — neutral ladder: subtle → sunken → sunken-strong */
  --surface-body: var(--white);         /* document background */
  --surface-page: #fdf6ff;              /* post-login page wash */
  --surface-page-tint: #f5eeff;
  --surface-card: #ffffff;
  --surface-input: var(--white);         /* form field background */
  --surface-glass: rgba(255, 255, 255, 0.78);
  --surface-glass-strong: rgba(255, 255, 255, 0.9); /* frosted card surface — polished cards/modals, admin header */
  --surface-subtle: var(--gray-subtle); /* faintest neutral fill (resting chips, rows) */
  --surface-hover: var(--surface-subtle);/* row/list hover */
  --surface-sunken: var(--gray-light);  /* recessed controls, wells, tab strips */
  --surface-sunken-strong: var(--gray-border); /* neutral chip fill, placeholder gradient end */
  --surface-muted: var(--gray-dim);     /* strongest neutral fill (progress dots, tracks) */
  --surface-accent-soft: var(--purple-50);
  --surface-accent: var(--purple-light);/* selected chips, tinted icon boxes */
  --surface-accent-hover: var(--purple-75); /* hover step on a tinted row */
  --surface-brand-soft: var(--pink-50); /* faintest brand wash (alt sections, selected brand chip) */
  --surface-control-neutral: rgba(15, 23, 42, 0.05); /* ghost icon-button fill */

  /* Course accents — one per calculus course, used by [data-course] chips */
  --course-calc1-surface: #dcf8e6;
  --course-calc1-fg: #5eb76c;
  --course-calc2-surface: #fff3ce;
  --course-calc2-fg: #a86b20;
  --course-calc3-surface: #f1e7ff;
  --course-calc3-fg: #8f55ed;

  /* Scrim — modal/overlay backdrops, by weight (must deepen in dark theme) */
  --scrim-soft: rgba(15, 23, 42, 0.44); /* blurred drawer backdrop */
  --scrim: rgba(0, 0, 0, 0.5);
  --scrim-strong: rgba(0, 0, 0, 0.7);   /* blocking overlays (access expired) */
  --scrim-brand: rgba(31, 19, 51, 0.58);/* brand-tinted backdrop (quiz modal) */
  --scrim-video: rgba(15, 15, 30, 0.65);/* video-list backdrop */
  --scrim-modal: rgba(15, 10, 30, 0.82);/* opaque backdrop (result, chart, embed) */

  /* Border / divider roles — ladder: faint → default → strong */
  --border-faint: var(--gray-light);    /* barely-there row separators */
  --border-default: var(--gray-border); /* neutral input borders, control outlines */
  --border-strong: var(--gray-dim);     /* dropzone dashes, emphasised outlines */
  --border-subtle: rgba(168, 85, 247, 0.14);
  --border-card: rgba(30, 19, 51, 0.09);
  --border-accent: var(--purple-200);   /* tinted input borders */
  --border-accent-strong: var(--purple-300); /* accent control hover border */
  --divider: var(--purple-light);       /* hairline list dividers */

  /* Interactive / brand roles */
  --accent: var(--purple);
  --accent-strong: var(--purple-dark);
  --accent-deep: var(--purple-deeper);
  --accent-deepest: var(--purple-800);  /* accent link hover */
  --brand: var(--pink);
  --brand-strong: var(--pink-dark);
  --brand-deep: var(--pink-deeper);
  --gradient-brand: linear-gradient(135deg, var(--pink), var(--purple));
  /* The brand gradient clipped to TEXT (background-clip: text). The fill stops
     above are 2.9:1 / 3.4:1 on a light surface, so text takes the deep pair
     (4.96 / 6.62 on --surface-accent-soft); dark swaps in a light pair. */
  --gradient-brand-text: linear-gradient(135deg, var(--pink-deeper), var(--purple-deeper));
  --gradient-cta: linear-gradient(135deg, var(--pink), var(--pink-dark));
  --gradient-accent: linear-gradient(135deg, var(--accent), var(--accent-strong));

  /* Status roles */
  --success-fg: var(--green-dark);
  --success-fg-strong: var(--green-deep);  /* strong success text — passes AA on light and dark surfaces */
  --success-fg-deepest: var(--green-800);  /* 10px success text sharing its box with a colour emoji (7.34 on --success-surface) */
  --success-surface: var(--green-50);
  --success-border: var(--green-200);
  --warning-fg: var(--amber-deeper);
  --warning-fg-strong: var(--amber-strong); /* warning-tier numerals on a card — vivid amber that still reads on light (3.2:1 vs raw --amber's 2.2:1) */
  --warning-surface: var(--amber-light);
  --warning-border: var(--amber-200);   /* completes the warning role — the only status
                                           family with no border, which is why
                                           .bulk-topic-fix reaches past it for a raw
                                           --amber-300. Both values are aliases, so this
                                           costs the hex ratchet nothing. */
  --danger: var(--red);
  --danger-strong: var(--red-dark);     /* filled-control hover */
  --danger-fg: var(--red-dark);         /* danger text on light surface */
  --danger-fg-strong: var(--red-deep);  /* danger text, hover / high emphasis */
  --danger-surface: var(--red-50);
  --danger-border: var(--red-200);
  --danger-border-strong: var(--red-300);
  /* Notice — orange attention level, distinct from amber --warning-* */
  --notice-fg: var(--orange-900);
  --notice-fg-soft: var(--orange-800);
  --notice-surface: var(--orange-light);
  --notice-border: var(--orange-200);

  /* Elevation (thin shadow + hairline depth, Stripe-like) */
  --shadow-soft: 0 1px 0 rgba(30, 19, 51, 0.04), 0 1px 2px rgba(124, 58, 237, 0.05);
  --shadow-raise: 0 2px 8px rgba(124, 58, 237, 0.08);
  --shadow-card: 0 10px 28px rgba(121, 86, 190, 0.08), 0 1px 2px rgba(30, 19, 51, 0.04); /* resting card elevation (polished surfaces) */

  /* Motion — quiet / Linear-like (~150–200ms ease-out) */
  --duration-instant: 0.12s;
  --duration-fast: 0.16s;
  --duration-normal: 0.19s;
  --ease-out-soft: cubic-bezier(0.22, 1, 0.36, 1);
  --transition: var(--duration-normal) var(--ease-out-soft);

  /* Spacing (4px base grid) — ใช้ใน layout ใหม่หรือตอนค่อย refactor ค่าพิกเซลเดิม
     Half-steps (-1-5, -2-5, -3-5) fill common gaps that don't land on multiples of 4. */
  --space-1:   4px;
  --space-1-5: 6px;    /* tight: icon+label gap, badge inner margin */
  --space-2:   8px;
  --space-2-5: 10px;   /* compact: cell padding, small chip padding */
  --space-3:   12px;
  --space-3-5: 14px;   /* between compact (12) and normal (16) */
  --space-4:   16px;
  --space-5:   20px;
  --space-6:   24px;
  --space-7:   28px;   /* medium-large: section internal gaps */
  --space-8:   32px;
  --space-10:  40px;   /* large: modal padding, card stacks */
  --space-12:  48px;   /* xl: hero sections, page-level breathing room */

  /* Responsive layout contract
     CSS variables cannot be used inside @media in a production-safe way, so
     media queries should mirror these values explicitly:
     phone <= 759px, workspace >= 900px, compact/tablet 760px-899px. */
  --layout-phone-max: 759px;
  --layout-compact-max: 899px;
  --layout-workspace-min: 900px;
  --layout-workspace-max: 1200px;
  --layout-panel-min: 360px;
  --layout-panel-comfort: 320px;

  /* Focus (a11y). Two types, never interchangeable:
   * --focus-outline is an `outline` shorthand — use it as
   *   outline: var(--focus-outline); outline-offset: var(--focus-ring-offset);
   * --focus-ring-soft is a `box-shadow` value (a glow beside
   *   the outline). Given to `outline`, a shadow value is invalid and the
   *   browser draws no outline at all (audit F1, 2026-11-01).
   * The outline ink is the gym's own focus colour (--gm-focus-ring), so a
   *   ring reads the same inside and outside the gym. */
  --focus-outline: 2px solid var(--accent-strong);
  --focus-ring-soft: 0 0 0 3px rgba(168, 85, 247, 0.18); /* soft focus glow — box-shadow companion to the outline ring */
  --focus-ring-offset: 2px;

  /* z-index stack — quiz-modal/exam-inline 10100 · result-modal 10200 ·
     diff-picker 10300 · profile 10400 · toast 10500 */
  --z-modal-shell: 1000;
  --z-quiz-modal: 10100;
  --z-result-modal: 10200;
  --z-diff-picker: 10300;
  --z-modal-profile: 10400;
  --z-toast: 10500;

  /* Six semantic type tiers (map existing tokens — use in new/refactored CSS) */
  --type-micro: var(--text-2xs);
  --type-caption: var(--text-caption-sm);
  --type-body: var(--text-body-sm);
  --type-ui: var(--text-md);
  --type-title: var(--text-subtitle);
  --type-hero: var(--text-display-sm);
  /*
   * ตัวอย่างจาก design guide ที่ไม่ใช่ชื่อเดียวกับสเกลเดิม:
   * - 1.25rem “title card” → ใช้ --text-title-sm (เท่ากันอยู่แล้ว)
   * - UI body ปกติ → --type-body / --text-body-sm (≈0.9rem)
   * - Body เนื้อหายาวโตขึ้นเล็กน้อย (~15px) → --text-reading
   */
  --text-reading: 0.9375rem;
  --text-display-fluid: clamp(1.75rem, 5vw, 2.5rem);

  /* Domain-specific Semantic Tokens for Topic Cards */
  --text-topic-title: var(--text-caption-sm);
  --font-weight-topic-title: 700;
}

/* ================================================================
   DARK THEME — overrides Tier 2 ONLY, never Tier 1 primitives
   (enforced by tests/theme-contract.test.js).
   Applied via html[data-theme="dark"], set before first paint by
   core/theme.js (linked in <head> ahead of every stylesheet).
   Scope notes:
   - admin.html does not load core/theme.js — its 31k of inline
     styles predate the token system, so admin stays light until
     that debt is paid.
   - .public-home (login) keeps its own light identity palette; its
     public palette is themed separately when design decides to. The
     island is enforced by grouping html[data-theme="dark"] .public-home
     into the :root block above: it re-anchors every token to its
     light value with higher specificity than this block, from the
     same single source of truth (no value duplication).
   ================================================================ */
html[data-theme="dark"] {
  /* See the `only` rationale in the :root block above — same reasoning, dark
     side. Higher specificity than :root, so this wins on <html>. */
  color-scheme: dark;
  color-scheme: only dark;

  /* Learner surfaces: canonical mood-tone, also available before gym CSS loads. */
  --learner-action-primary: #e593b8;
  --learner-action-hover: #f0b1cf;
  --learner-on-action: #2b1722;
  --learner-surface-workspace: #211c27;
  --learner-surface-selected: #392738;
  --learner-surface-support: #383049;
  --learner-surface-card: #2d2633;
  --learner-text-ink: #f4edf5;
  --learner-text-secondary: #c7bdca;
  --learner-border-default: #54485c;
  --learner-border-interactive: #a79bb3;
  --learner-quiz-header: #563044;

  /* Text roles.
     Contrast figures below are WCAG 2.1 ratios against --surface-card. */
  --text-ink: #ede9f8;              /* 14.19 */
  --text-strong: #e8e6f2;           /* 13.19 — lowered from a near-pure white
                                       (16.18), which haloes on Thai strokes */
  --text-body: #cdc8dd;             /* 9.99 */
  --text-secondary-strong: #b6b0c9;
  --text-default: #d9d5e6;
  --text-secondary: #a09ab5;        /* 6.03 */
  --text-tertiary: #968fae;         /* 5.29 card / 4.74 sunken — the previous
                                       value read 3.97 / 3.59, under AA */
  --text-disabled: #6f688b;         /* 3.12 — previous value read 2.05 */
  --text-accent: #c4b5fd;
  --text-brand: #ff7ca6;
  --text-ink-soft: #c9c2df;

  /* Surface roles — brand-tinted deep purples, one ladder.
     body was deepened and card lifted so a card reads as a real plane above
     the page; the old pair sat 1.11:1 apart, which is below the threshold
     where an edge is perceptible without a border. Glass mirrors --surface-card
     as rgb — keep the two in sync when either moves. */
  --surface-body: #0d0b14;
  --surface-page: #121020;
  --surface-page-tint: #191430;
  --surface-card: #221d33;
  --surface-input: #272138;
  --surface-glass: rgba(34, 29, 51, 0.78);
  --surface-glass-strong: rgba(34, 29, 51, 0.9);
  --surface-subtle: #241f36;
  --surface-sunken: #2b2540;
  --surface-sunken-strong: #362f4d;
  --surface-muted: #3e3754;
  --surface-accent-soft: #261e3d;
  --surface-accent: #2f2449;
  --surface-accent-hover: #2a2143;
  --surface-brand-soft: #2c1b28;
  --surface-control-neutral: rgba(255, 255, 255, 0.06);

  /* Course accents */
  --course-calc1-surface: #14301e;
  --course-calc1-fg: #6cd287;
  --course-calc2-surface: #33260d;
  --course-calc2-fg: #d9a24a;
  --course-calc3-surface: #2a1d45;
  --course-calc3-fg: #b18aff;

  /* Border / divider roles — faint is an alpha so hairlines keep the same
     weight at every step of the surface ladder instead of vanishing on the
     lighter ones. */
  --border-faint: rgba(255, 255, 255, 0.06);
  --border-default: #3b3456;
  --border-strong: #4f4770;
  --border-subtle: rgba(168, 85, 247, 0.3);
  --border-card: rgba(255, 255, 255, 0.08);
  --border-accent: #45356b;
  --border-accent-strong: #5d4791;
  --divider: #2c2344;

  /* Interactive — accents brighten so text/icons keep contrast;
     filled-control gradients stay on the vivid brand primitives. */
  --accent-strong: #a37cf7;
  --accent-deep: #b795f8;
  --accent-deepest: #cdb9fb;
  /* Text gradient: judge the whole interpolation against the bubble's actual
     background, not just its two stops on --surface-accent-soft. */
  --gradient-brand-text: linear-gradient(135deg, var(--pink), var(--purple-400));

  /* Status roles */
  --success-fg: #4ade80;
  --success-fg-strong: var(--green-300);  /* strong success text — passes AA on light and dark surfaces */
  --success-fg-deepest: var(--green-300);
  --success-surface: #132b1d;
  --success-border: #235c38;
  --warning-fg: #fbbf24;
  --warning-fg-strong: var(--amber-400);
  --warning-surface: #33290f;
  --warning-border: var(--amber-deeper); /* deep amber reads as an edge on the dark warning
                                            surface; the light Amber-200 does not. */
  --danger-fg: #f87171;
  --danger-fg-strong: #fca5a5;
  --danger-surface: #301616;
  --danger-border: #5f2525;
  --danger-border-strong: #7c3030;
  --notice-fg: #fdba74;
  --notice-fg-soft: #fb923c;
  --notice-surface: #2e2012;
  --notice-border: #57391b;
}

/* Approved public-home identity; isolated from learner theme tokens. */
:root {
  --public-home-action: #c91d69;
  --public-home-ink: #171421;
  --public-home-footer: #141318;
  --public-home-brand: #eb287f;
  --public-home-paper: #ffffff;
}

:root { --font-public-display:PublicKanit, var(--font-display); }
