/* ─── Articulat CF — the ring's brand face ───────────────────────────────
   Same files gluearrow.com serves, copied into our own static so Study Pill
   makes no third-party request for its brand and cannot render in a fallback
   because a font host is slow. Three weights, ~108KB total: DemiBold for card
   titles, Bold for headings, Heavy for the wordmark.
   font-display: swap — text paints immediately in Inter, then swaps. */
@font-face {
  font-family: 'Articulat CF';
  src: url('../fonts/articulat/ArticulatCF-DemiBold.woff2') format('woff2');
  font-weight: 600;
  font-style: normal;
  font-display: swap;
}
@font-face {
  font-family: 'Articulat CF';
  src: url('../fonts/articulat/ArticulatCF-Bold.woff2') format('woff2');
  font-weight: 700;
  font-style: normal;
  font-display: swap;
}
@font-face {
  font-family: 'Articulat CF';
  src: url('../fonts/articulat/ArticulatCF-Heavy.woff2') format('woff2');
  font-weight: 800 900;
  font-style: normal;
  font-display: swap;
}

/* Study Pill — design tokens.
 *
 * The single source of truth for color, type, spacing, radius, shadow, motion.
 * Loaded ahead of every other stylesheet. Two themes: warm-paper (default)
 * and dark. Every screen pulls from these tokens — no parallel palettes.
 *
 * Naming convention: --sp-<category>-<role> for new tokens; legacy --paper /
 * --ink / --coral aliases are preserved so existing CSS keeps working while
 * the migration is incremental, not a big-bang rewrite.
 */
:root {
  /* ---- color: paper / surface ---- */
  --sp-paper:        oklch(96.5% 0.018 85);
  --sp-paper-2:      oklch(94% 0.025 82);
  --sp-paper-edge:   oklch(88% 0.035 80);
  --sp-cream:        oklch(92% 0.04 85);

  /* ---- color: ink / text ---- */
  --sp-ink:          oklch(18% 0.02 60);
  --sp-ink-2:        oklch(32% 0.02 60);
  --sp-ink-soft:     oklch(48% 0.02 60);

  /* ---- color: rule / border ---- */
  --sp-rule:         oklch(84% 0.025 75);
  --sp-rule-soft:    oklch(88% 0.02 75 / .6);

  /* ---- color: brand + state ----
   *
   * WCAG 2.2 AA is a hard constraint on this palette, not a review note. The
   * values below were solved, not eyeballed: oklch -> OKLab -> sRGB -> WCAG
   * relative luminance, by tests/test_design_standard.py, which fails the
   * build if any pair below drops under its required ratio.
   *
   * What changed on 2026-08-05 and why (measured against --sp-paper):
   *   --sp-coral was oklch(60% …)  -> 3.97:1 as text, and white on it 4.39:1.
   *     Both under 4.5. It is the accent for links, the active tab and the
   *     primary button, so the app's single most-used colour failed AA on
   *     every surface it appeared on. Lightness 60% -> 55%. CHROMA IS
   *     UNCHANGED (0.215) — this is the same hue at the same saturation, five
   *     points darker. Text 4.90:1, on paper-2 4.55:1, white on it 5.42:1.
   *   --sp-pine was #10B981 -> 2.29:1 as text. Emerald cannot reach 4.5:1 on
   *     near-white paper at any usable chroma, so it SPLITS: --sp-pine stays
   *     the fill/graphic colour (3:1 applies to non-text UI), and
   *     --sp-pine-text is the readable one. Do not set type in --sp-pine.
   *
   * The rule this encodes: a colour used for TEXT and a colour used for FILL
   * are different tokens even when they are the same hue. Reaching for the
   * fill because it looks right is how 2.29:1 shipped.
   */
  --sp-coral:        oklch(55% 0.215 25);   /* text, icons, active tab, button fill */
  --sp-coral-display: oklch(60% 0.215 25);  /* ONLY >=24px display type (3:1 applies) */
  --sp-coral-2:      oklch(70% 0.2 30);     /* hover/lift only — never text */
  --sp-coral-deep:   oklch(35% 0.15 25);
  --sp-on-coral:     #ffffff;               /* label ON a coral fill — 5.42:1 */
  /* The one soft tint. It replaces three separate wash colours (amber-soft for
     warnings, rose-soft for errors, highlight for the read-aloud marker), all
     of which were different hues doing the same job: tinting a background so a
     line stands out. One tint, one hue, three uses. */
  --sp-coral-soft:   oklch(96% 0.05 25);
  /* #10B981 is gone. Emerald measures 2.29:1 on warm paper — it failed not
     only the 4.5:1 text bar but the 3:1 NON-TEXT bar too, so it was unusable
     even as a plain graphic here. It reads fine on a white screen in an
     office, which is exactly why nobody noticed; on this paper, in sunlight,
     it is close to invisible. */
  --sp-pine:         oklch(62.5% 0.13 162); /* FILL / graphic — 3.03:1 */
  --sp-pine-text:    oklch(51% 0.11 162);   /* success TEXT — 4.91:1 on paper */
  /* Label ON a pine fill — the "Top up" and "Class Rep" buttons. White on
     green measures 3.35:1 light and 1.66:1 dark, so white was never the
     answer; dark ink on green is 5.62:1 and is what a green button normally
     wears anyway. Same on-colour rule as --sp-on-coral: it is a token so it
     can flip, because a literal cannot. */
  --sp-on-pine:      oklch(18% 0.02 60);    /* 5.62:1 on the pine fill */
  --sp-pine-soft:    oklch(95% 0.06 160);


  /* ---- reader chrome: the BLACK bar that frames what you are consuming ----
   *
   * Owner, on the Acrobat reference: "the ui header i gave you were fixed
   * header … should be black … same with icons on it." The first attempt put
   * the reading screen's bar in warm paper like every other screen — our house
   * style applied where a different convention rules.
   *
   * It IS a different convention, and it earns its place. Every serious reader
   * — Acrobat, Kindle, Books, a presenter view — frames content in near-black,
   * because chrome around a page has to stop competing with it. Study Pill
   * already agreed with this once: cram mode is a deliberate near-black surface
   * (`--c-bg:#0a0a0a`, a private literal in one template). This makes it a
   * token, so the reading surfaces can share one black instead of each picking
   * their own.
   *
   * These do NOT flip with the theme, deliberately. A reader bar is black in
   * both, exactly as Acrobat's is. Flipping it would make the light theme a
   * different product from the dark one on the single screen students live in.
   *
   * Measured on --sp-reader-bg: ink 18.26:1, ink-2 9.95:1, coral 6.95:1.
   */
  --sp-reader-bg:    oklch(14% 0.005 60);
  --sp-reader-ink:   oklch(97% 0 0);      /* titles, active icons */
  --sp-reader-ink-2: oklch(78% 0 0);      /* resting icons, the sub-line */
  --sp-reader-rule:  oklch(30% 0.005 60);
  --sp-reader-hover: oklch(24% 0.005 60);

  /* ---- focus ----
   * WCAG 2.2 adds 2.4.11 (focus not obscured) and 2.4.13 (focus appearance).
   * Before this token the whole app had ONE :focus-visible rule, so keyboard
   * and switch users had essentially no way to tell where they were. The ring
   * needs 3:1 against BOTH the control and the surface behind it; this is
   * 5.99:1 on paper, so it clears that with room on either.
   */
  --sp-focus:        oklch(50% 0.19 25);
  --sp-focus-width:  3px;
  --sp-focus-offset: 2px;

  /* ---- typography: families ----
   *
   * TWO faces, which is what gluearrow.com uses: Articulat CF for display and
   * Inter for everything else. Study Pill was loading SIX — Newsreader,
   * Instrument Serif, Literata, Inter, JetBrains Mono and Caveat — in one
   * Google Fonts request. Six families is the main reason the two products did
   * not read as the same company: a serif headline above a monospace label
   * above a sans paragraph looks like three brands on one card.
   *
   * Articulat CF is the ring's brand face and is served from our own
   * static/fonts, so it is not a third-party request and cannot go missing
   * when a font host is slow.
   *
   * The old token names are kept and pointed at the two survivors. They are
   * used across a dozen stylesheets, and renaming them in the same change that
   * alters what they render as would make any visual regression impossible to
   * attribute. Mono resolves to the platform's own monospace, so columns that
   * must align still can without downloading a sixth family.
   */
  --sp-font-display: 'Articulat CF', 'Inter', system-ui, -apple-system, sans-serif;
  --sp-font-ui:      'Inter', system-ui, -apple-system, 'Segoe UI', Roboto, sans-serif;
  --sp-font-italic:  var(--sp-font-display);
  --sp-font-prose:   var(--sp-font-ui);
  --sp-font-hand:    var(--sp-font-display);
  --sp-font-mono:    ui-monospace, 'SF Mono', Menlo, Consolas, monospace;

  /* ---- spacing scale (4px base) ---- */
  --sp-space-1:  4px;
  --sp-space-2:  8px;
  --sp-space-3:  12px;
  --sp-space-4:  16px;
  --sp-space-5:  20px;
  --sp-space-6:  24px;
  --sp-space-7:  32px;
  --sp-space-8:  40px;
  --sp-space-9:  48px;
  --sp-space-10: 64px;

  /* ---- radius ---- */
  --sp-radius-xs: 6px;
  --sp-radius-sm: 10px;
  --sp-radius-md: 14px;
  --sp-radius-lg: 20px;
  --sp-radius-xl: 28px;

  /* ---- elevation ---- */
  --sp-lift-1: 0 1px 2px oklch(18% 0.02 60 / .05);
  --sp-lift-2: 0 2px 4px oklch(18% 0.02 60 / .04), 0 8px 24px oklch(18% 0.02 60 / .06);
  --sp-lift-3: 0 4px 8px oklch(18% 0.02 60 / .05), 0 20px 48px oklch(18% 0.02 60 / .10);

  /* ---- motion ---- */
  --sp-ease-out: cubic-bezier(0.16, 1, 0.3, 1);
  --sp-ease-spring: cubic-bezier(0.2, 0.9, 0.3, 1.1);
  --sp-dur-fast: .12s;
  --sp-dur-base: .22s;
  --sp-dur-slow: .35s;

  /* ---- legacy aliases (kept until every callsite migrates) ---- */
  --paper:        var(--sp-paper);
  --paper-2:      var(--sp-paper-2);
  --paper-edge:   var(--sp-paper-edge);
  --cream:        var(--sp-cream);
  --ink:          var(--sp-ink);
  --ink-2:        var(--sp-ink-2);
  --ink-soft:     var(--sp-ink-soft);
  --rule:         var(--sp-rule);
  --rule-soft:    var(--sp-rule-soft);
  --coral:        var(--sp-coral);
  --coral-2:      var(--sp-coral-2);
  --coral-deep:   var(--sp-coral-deep);
  --highlight:      var(--sp-coral-soft);
  --highlight-soft: var(--sp-coral-soft);
  --pine:         var(--sp-pine);
  --pine-soft:    var(--sp-pine-soft);
  --lift-1:       var(--sp-lift-1);
  --lift-2:       var(--sp-lift-2);
  --lift-3:       var(--sp-lift-3);
  --radius-xs:    var(--sp-radius-xs);
  --radius-sm:    var(--sp-radius-sm);
  --radius-md:    var(--sp-radius-md);
  --radius-lg:    var(--sp-radius-lg);
  --ease-out:     var(--sp-ease-out);
}

/* Dark theme — opt-in via `html[data-theme="dark"]`. */
html[data-theme="dark"] {
  color-scheme: dark;
  --sp-paper:      oklch(16% 0.015 60);
  --sp-paper-2:    oklch(20% 0.018 60);
  --sp-paper-edge: oklch(26% 0.02 60);
  --sp-cream:      oklch(22% 0.02 82);
  --sp-ink:        oklch(96% 0.015 85);
  --sp-ink-2:      oklch(86% 0.02 80);
  --sp-ink-soft:   oklch(72% 0.025 78);
  --sp-rule:       oklch(32% 0.02 70);
  --sp-rule-soft:  oklch(30% 0.02 70 / .6);
  --sp-coral:      oklch(72% 0.2 28);
  --sp-coral-display: oklch(72% 0.2 28);
  --sp-coral-2:    oklch(78% 0.2 32);
  --sp-coral-deep: oklch(60% 0.18 26);
  --sp-coral-soft: oklch(28% 0.07 25);
  /* On dark, coral is LIGHT, so a white label on it is 2.87:1 — worse than the
     light theme's failure. The fix is not a different coral, it is flipping
     the label to near-black: 6.78:1. An "on-colour" token has to flip with the
     theme; a hard-coded #fff cannot, which is exactly why this bug existed. */
  --sp-on-coral:   oklch(16% 0.015 60);
  --sp-pine:       #2DE3A8;
  --sp-pine-text:  #2DE3A8;   /* 11.72:1 on dark paper — readable as-is here */
  --sp-on-pine:    oklch(16% 0.015 60);   /* 11.72:1 on the bright dark-theme pine */
  --sp-pine-soft:  oklch(30% 0.09 160);
  /* Rose flips exactly like coral does: on dark paper the fill is LIGHT, so
     the label on it has to go near-black. A #fff literal here would measure
     2.3:1 — the same bug --sp-on-coral was created to fix. */
  --sp-focus:      oklch(75% 0.19 28);
  --sp-lift-1: 0 1px 2px oklch(0% 0 0 / .35);
  --sp-lift-2: 0 2px 4px oklch(0% 0 0 / .35), 0 8px 24px oklch(0% 0 0 / .45);
  --sp-lift-3: 0 4px 8px oklch(0% 0 0 / .40), 0 20px 48px oklch(0% 0 0 / .55);
}
