/* ==========================================================================
   VOLUNTIER — DESIGN TOKENS
   Phase V, Step 1. Single source of truth for color, type, space, and motion.

   ARCHITECTURE — referenced in one direction only:
     PRIMITIVES  raw ramps (--cobalt-600, --neutral-500). Never used in pages.
     ALIASES     hue -> meaning (--color-brand-fill, --color-danger-text).
     ROLES       what pages actually reference (--text-primary, --btn-*).
   Pages reference roles. If a page reaches past a role to a raw ramp step,
   that is a smell — add a role instead.

   BRAND LITERALS (from the brand guide, never derived, never adjusted):
     cobalt #004aad  ->  --cobalt-600
     white  #ffffff  ->  --neutral-0
     black  #000000  ->  --neutral-950
     gray   #d9d9d9  ->  --brand-gray  (see the neutral ramp note below)
   Everything else in this file descends from those four.

   CONTRAST: every ratio in a comment is computed against #ffffff unless
   stated otherwise. Text roles are pinned to steps that actually pass
   WCAG AA — note that green and amber pass at 700, not at 600/500.

   Three questions were opened and closed on design-system.html
   (Layer 3 passed 2026-09-11). This file carries the answers only:
     body font    -> Figtree
     neutral ramp -> cobalt-tinted
     info color   -> cobalt
   ========================================================================== */


/* --------------------------------------------------------------------------
   FONTS
   Headings: Zodiak (Indian Type Foundry, via Fontshare), SELF-HOSTED.

   LICENSE — Fontshare Free Font EULA, assets/fonts/zodiak/License/FFL.txt:
     - Sec. 01 grants commercial use "in any media (including Print, Web,
       Mobile, Digital, Apps...)", and the package ships a WEB/ folder of
       woff2 files with a README whose instructions are how to self-host
       @font-face. Self-hosting is the documented, intended use.
     - Sec. 02 forbids transmitting the font "in font serving or for font
       replacement by means of technologies such as but not limited to EOT,
       Cufon, sIFR". Read as targeting RE-SERVING the font to third parties
       and legacy replacement shims, not first-party self-hosting -- the
       shipped WEB/ folder would be unusable otherwise. If we ever want zero
       ambiguity, Sec. 01 explicitly blesses the Fontshare CDN
       (api.fontshare.com) and this @font-face becomes a one-line swap.
     - Sec. 02 also forbids MODIFYING the font. So: NO SUBSETTING. The file
       ships exactly as distributed -- 37,352 bytes, verified byte-identical
       as served from production.

   We load ONE file: the variable woff2, 37KB, covering the full 100-900
   weight axis. The 12 static faces stay in assets/ as the source package
   but are never requested.

   Body: Figtree, via Google Fonts. Each page's <head> needs:
     <link rel="preconnect" href="https://fonts.googleapis.com">
     <link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
     <link rel="stylesheet" href="https://fonts.googleapis.com/css2?family=Figtree:ital,wght@0,300..900;1,300..900&display=swap">
   plus, to avoid a flash on the headings:
     <link rel="preload" href="/assets/fonts/zodiak/Fonts/WEB/fonts/Zodiak-Variable.woff2" as="font" type="font/woff2" crossorigin>
   -------------------------------------------------------------------------- */

@font-face {
  font-family: 'Zodiak';
  src: url('/assets/fonts/zodiak/Fonts/WEB/fonts/Zodiak-Variable.woff2') format('woff2-variations'),
       url('/assets/fonts/zodiak/Fonts/WEB/fonts/Zodiak-Variable.woff2') format('woff2');
  font-weight: 100 900;
  font-style: normal;
  font-display: swap;
}

@font-face {
  font-family: 'Zodiak';
  src: url('/assets/fonts/zodiak/Fonts/WEB/fonts/Zodiak-VariableItalic.woff2') format('woff2-variations'),
       url('/assets/fonts/zodiak/Fonts/WEB/fonts/Zodiak-VariableItalic.woff2') format('woff2');
  font-weight: 100 900;
  font-style: italic;
  font-display: swap;
}


/* ==========================================================================
   PRIMITIVES
   ========================================================================== */

:root {

  /* --- Cobalt. Brand hue 214, anchored so #004aad lands on 600 (the
         primary-action slot), leaving 700/800 for hover/active. -------- */
  --cobalt-50:  #f0f6ff;   /* 1.09 */
  --cobalt-100: #e0eeff;   /* 1.18 */
  --cobalt-200: #bddafe;   /* 1.44 */
  --cobalt-300: #8cbdfd;   /* 1.94 -- 7.99 AAA on cobalt-900, dark-bg link */
  --cobalt-400: #4895f9;   /* 3.02 */
  --cobalt-500: #0468ec;   /* 5.00  AA */
  --cobalt-600: #004aad;   /* 8.05  AAA  <-- BRAND LITERAL */
  --cobalt-700: #003e8f;   /* 10.07 AAA */
  --cobalt-800: #022f6e;   /* 12.85 AAA */
  --cobalt-900: #05234d;   /* 15.51 AAA */

  /* --- Neutral. Cobalt-tinted: a few percent of saturation pulled toward
         hue 214 at every step. Locked 2026-09-11 over a pure-gray ramp --
         pure gray next to fully-saturated cobalt reads cold and flat, and
         the tint is what Linear / Stripe / Calendly all do.

         White and black are held exactly. The guide's light gray #d9d9d9
         is NOT in this ramp (300 is its tinted equivalent at identical
         lightness) -- it is preserved as --brand-gray below, for brand
         collateral where the guide is quoted directly. --------------- */
  --neutral-0:   #ffffff;   /* BRAND LITERAL */
  --neutral-50:  #f8fafc;
  --neutral-100: #f2f4f8;
  --neutral-200: #e3e7ed;
  --neutral-300: #d3d8de;   /* tinted equivalent of brand #d9d9d9 */
  --neutral-400: #a6afb9;   /* 2.22 -- non-text only */
  --neutral-500: #7e8895;   /* 3.59 -- AA large / disabled text */
  --neutral-600: #5f6a77;   /* 5.51  AA */
  --neutral-700: #47505c;   /* 8.17  AAA */
  --neutral-800: #2b323b;   /* 12.95 AAA */
  --neutral-900: #161b22;   /* 17.30 AAA */
  --neutral-950: #000000;   /* BRAND LITERAL */

  /* Brand collateral only -- the guide's literal light gray. Not a UI
     surface token; use --neutral-300 for interface work. */
  --brand-gray: #d9d9d9;

  /* --- Semantic hues. Deliberately PARTIAL ramps: semantic colors need
         role coverage (surface / border / fill / text), not ten steps.
         Hues picked to sit with a cool, fully-saturated cobalt. -------- */

  /* Success -- h152, a blue-leaning green. A yellow-green fights cobalt. */
  --green-50:  #ebfaf3;
  --green-100: #d0f1e2;
  --green-200: #aee5cb;
  --green-500: #26a66a;   /* 3.11 -- fill only */
  --green-600: #1a8955;   /* 4.42 -- fill only, JUST under AA for text */
  --green-700: #137245;   /* 5.96  AA  <-- the text step */
  --green-900: #0b3d25;   /* 12.29 AAA */

  /* Warning -- h38 amber. Amber never passes AA as a bright fill; 700 is
     the only text-safe step and 500 is decorative. This is normal. */
  --amber-50:  #fff6e5;
  --amber-100: #fee4b9;
  --amber-200: #fcd188;
  --amber-500: #f9a006;   /* 2.09 -- fill only */
  --amber-600: #d08606;   /* 2.96 -- fill only */
  --amber-700: #a26107;   /* 4.94  AA  <-- the text step */
  --amber-900: #553206;   /* 11.38 AAA */

  /* Danger -- h358, cool-leaning red to sit with a cool brand. */
  --red-50:  #feecec;
  --red-100: #fcd4d5;
  --red-200: #f9b3b5;
  --red-500: #db242a;   /* 4.89  AA */
  --red-600: #b81e23;   /* 6.47  AA */
  --red-700: #9b171c;   /* 8.29  AAA  <-- the text step */
  --red-900: #570f12;   /* 14.17 AAA */
}


/* ==========================================================================
   ALIASES — hue to meaning
   ========================================================================== */

:root {

  --font-heading: 'Zodiak', Georgia, 'Times New Roman', serif;
  --font-body: 'Figtree', -apple-system, BlinkMacSystemFont, 'Segoe UI',
               Roboto, 'Helvetica Neue', Arial, sans-serif;
  --font-mono: ui-monospace, SFMono-Regular, 'SF Mono', Menlo, Consolas,
               'Liberation Mono', monospace;

  --color-brand-surface: var(--cobalt-50);
  --color-brand-border:  var(--cobalt-200);
  --color-brand-fill:    var(--cobalt-600);
  --color-brand-hover:   var(--cobalt-700);
  --color-brand-active:  var(--cobalt-800);
  --color-brand-text:    var(--cobalt-600);

  /* Info IS cobalt. Locked 2026-09-11 over a separate cyan: there is no
     competing blue to disambiguate from, so a second "info blue" next to a
     blue brand is noise. */
  --color-info-surface: var(--cobalt-50);
  --color-info-border:  var(--cobalt-200);
  --color-info-fill:    var(--cobalt-600);
  --color-info-text:    var(--cobalt-700);   /* 9.27 on its own surface */

  --color-success-surface: var(--green-50);
  --color-success-border:  var(--green-200);
  --color-success-fill:    var(--green-600);
  --color-success-text:    var(--green-700);

  --color-warning-surface: var(--amber-50);
  --color-warning-border:  var(--amber-200);
  --color-warning-fill:    var(--amber-500);
  --color-warning-text:    var(--amber-700);

  --color-danger-surface: var(--red-50);
  --color-danger-border:  var(--red-200);
  --color-danger-fill:    var(--red-600);
  --color-danger-hover:   var(--red-700);
  --color-danger-text:    var(--red-700);
}


/* ==========================================================================
   ROLES.  This is what pages reference.
   ========================================================================== */

:root {

  /* ---- Text ---- */
  --text-primary:     var(--neutral-900);  /* 17.30 body copy */
  --text-secondary:   var(--neutral-700);  /*  8.17 supporting */
  --text-muted:       var(--neutral-600);  /*  5.51 timestamps, meta */
  --text-placeholder: var(--neutral-500);  /*  3.59 input placeholder */
  --text-disabled:    var(--neutral-500);  /*  3.59 -- NOT 400 (2.22),
                                               too faint even disabled */
  --text-inverse:     var(--neutral-0);
  --text-link:        var(--color-brand-text);
  --text-link-hover:  var(--cobalt-700);
  --text-on-brand:    var(--neutral-0);    /*  8.05 on cobalt-600 */

  /* ---- Surfaces ---- */
  --surface-page:    var(--neutral-0);
  --surface-sunken:  var(--neutral-50);    /* page bg behind cards */
  --surface-raised:  var(--neutral-0);     /* cards, menus */
  --surface-hover:   var(--neutral-50);
  --surface-active:  var(--neutral-100);
  --surface-inverse: var(--neutral-900);
  --surface-brand:   var(--color-brand-fill);
  --surface-brand-subtle: var(--color-brand-surface);
  --surface-overlay: rgba(5, 35, 77, 0.55);     /* cobalt-900 @55%, modal
                                                   scrim -- see shadows */
  --surface-scrim-photo: rgba(5, 35, 77, 0.62); /* photo-hero overlay */

  /* ---- Borders ---- */
  --border-subtle:  var(--neutral-200);
  --border-default: var(--neutral-300);
  --border-strong:  var(--neutral-400);
  --border-brand:   var(--color-brand-fill);
  --border-focus:   var(--cobalt-500);

  /* ---- Focus ring -------------------------------------------------------
     Two-tone by design. A plain cobalt ring is INVISIBLE on a cobalt
     button; the white offset gives it an edge to read against on both a
     light surface and on the primary button itself. Apply as:
       outline: var(--focus-ring-width) solid var(--border-focus);
       outline-offset: var(--focus-ring-offset);
     or, where an outline would be clipped, --focus-ring-shadow.
     ------------------------------------------------------------------- */
  --focus-ring-width: 2px;
  --focus-ring-offset: 2px;
  --focus-ring-color: var(--cobalt-500);
  --focus-ring-shadow: 0 0 0 2px var(--surface-page),
                       0 0 0 4px var(--cobalt-500);


  /* ==== TYPE SCALE =====================================================
     1.200 minor-third ratio from a 16px base. Headings are Zodiak with
     tightened tracking (a display serif set loose at large sizes looks
     unresolved); body is Figtree at default tracking. ================== */
  --text-2xs:  0.6875rem;  /* 11px  badges, table micro-labels */
  --text-xs:   0.75rem;    /* 12px  meta, captions */
  --text-sm:   0.8125rem;  /* 13px  dense dashboard body */
  --text-base: 0.9375rem;  /* 15px  app body */
  --text-md:   1rem;       /* 16px  marketing body */
  --text-lg:   1.125rem;   /* 18px  lead paragraph */
  --text-xl:   1.375rem;   /* 22px  h4 */
  --text-2xl:  1.75rem;    /* 28px  h3 */
  --text-3xl:  2.25rem;    /* 36px  h2 */
  --text-4xl:  3rem;       /* 48px  h1 */
  --text-5xl:  3.75rem;    /* 60px  hero */
  --text-6xl:  4.5rem;     /* 72px  photo hero, desktop */

  --leading-tight:   1.15;  /* headings */
  --leading-snug:    1.3;
  --leading-normal:  1.5;   /* body */
  --leading-relaxed: 1.65;  /* long-form: terms, privacy */

  --tracking-tight:  -0.02em;  /* Zodiak at 36px+ */
  --tracking-snug:   -0.01em;  /* Zodiak at 22-28px */
  --tracking-normal: 0;
  --tracking-wide:   0.06em;   /* small caps, eyebrow labels */

  --weight-light:     300;
  --weight-regular:   400;
  --weight-medium:    500;
  --weight-semibold:  600;
  --weight-bold:      700;
  --weight-extrabold: 800;


  /* ==== SPACING — 4px base ============================================= */
  --space-0:  0;
  --space-1:  0.125rem;  /*  2px */
  --space-2:  0.25rem;   /*  4px */
  --space-3:  0.375rem;  /*  6px */
  --space-4:  0.5rem;    /*  8px */
  --space-5:  0.75rem;   /* 12px */
  --space-6:  1rem;      /* 16px */
  --space-7:  1.25rem;   /* 20px */
  --space-8:  1.5rem;    /* 24px */
  --space-9:  2rem;      /* 32px */
  --space-10: 2.5rem;    /* 40px */
  --space-11: 3rem;      /* 48px */
  --space-12: 4rem;      /* 64px */
  --space-13: 5rem;      /* 80px */
  --space-14: 6rem;      /* 96px */


  /* ==== RADIUS =========================================================
     Deliberately tight. Zodiak's sharp wedge serifs pair badly with
     pill-soft UI -- this sits closer to Linear than to Calendly. ======= */
  --radius-sm:   4px;    /* badges, tags, small inputs */
  --radius-md:   6px;    /* buttons, inputs */
  --radius-lg:   8px;    /* cards */
  --radius-xl:   12px;   /* modals, large panels */
  --radius-2xl:  16px;   /* hero cards, photo frames */
  --radius-full: 9999px; /* avatars, pills, toggles */


  /* ==== SHADOW =========================================================
     Multi-layer, low alpha, tinted with cobalt-900 (5,35,77) rather than
     pure black -- black shadows read dirty over tinted surfaces. ======= */
  --shadow-xs: 0 1px 2px rgba(5, 35, 77, 0.05);
  --shadow-sm: 0 1px 2px rgba(5, 35, 77, 0.06),
               0 1px 3px rgba(5, 35, 77, 0.08);
  --shadow-md: 0 2px 4px rgba(5, 35, 77, 0.05),
               0 4px 8px rgba(5, 35, 77, 0.08);
  --shadow-lg: 0 4px 8px rgba(5, 35, 77, 0.05),
               0 12px 24px rgba(5, 35, 77, 0.10);
  --shadow-xl: 0 8px 16px rgba(5, 35, 77, 0.06),
               0 24px 48px rgba(5, 35, 77, 0.14);
  --shadow-focus-brand:  0 0 0 3px rgba(0, 74, 173, 0.18);
  --shadow-focus-danger: 0 0 0 3px rgba(184, 30, 35, 0.18);
  --shadow-inner: inset 0 1px 2px rgba(5, 35, 77, 0.07);


  /* ==== MOTION =========================================================
     Fast. Nothing over 300ms. Linear-tier. ============================= */
  --duration-instant: 100ms;
  --duration-fast:    150ms;
  --duration-base:    200ms;
  --duration-slow:    300ms;

  --ease-standard: cubic-bezier(0.2, 0, 0, 1);        /* default */
  --ease-out:      cubic-bezier(0.16, 1, 0.3, 1);     /* entrances */
  --ease-in:       cubic-bezier(0.7, 0, 0.84, 0);     /* exits */
  --ease-spring:   cubic-bezier(0.34, 1.56, 0.64, 1); /* toasts, pops */

  --transition-colors: color var(--duration-fast) var(--ease-standard),
                       background-color var(--duration-fast) var(--ease-standard),
                       border-color var(--duration-fast) var(--ease-standard);


  /* ==== DENSITY TIER ===================================================
     A scoped token set for desk-work surfaces (the org dashboard today,
     volunteer-dashboard later). Components reference these instead of
     hard-coding row heights and paddings, so a surface opts into compact
     with ONE attribute -- <body data-density="compact"> -- and every
     component it contains tightens. No markup restructuring, no
     per-component overrides.

     These are the COMFORTABLE defaults, matching what the dashboard
     already shipped. The compact values live in the [data-density]
     block below the :root closing brace.

     Anything that needs DOM restructuring to reach Linear-tier density
     is explicitly OUT of this rollout and gets logged for a later
     density pass. This tier is what can be reached from tokens alone.
     ==================================================================== */
  --dash-row-height:    44px;               /* list/table row min-height */
  --dash-row-pad-y:     var(--space-5);     /* 12px */
  --dash-row-pad-x:     var(--space-6);     /* 16px */
  --dash-cell-font:     var(--text-base);   /* 15px */
  --dash-label-font:    var(--text-xs);     /* 12px, secondary line in a row */
  --dash-card-pad:      var(--space-8);     /* 24px */
  --dash-card-gap:      var(--space-7);     /* 20px between cards */
  --dash-card-radius:   var(--radius-lg);
  --dash-header-gap:    var(--space-7);     /* section-header to content */
  --dash-title-font:    var(--text-lg);     /* section title */
  --dash-control-h:     40px;               /* in-card buttons, toggles */


  /* ==== CATEGORICAL CHART RAMP =========================================
     Eight slices for Chart.js. Built from the palette's own four hues --
     cobalt, amber, red, green -- and NOTHING invented; the page it replaces
     had violet, cyan and magenta in it, none of which are ours.

     Designed against TARGET L* values, not HSL lightness. That distinction
     is the whole trick: a first attempt picked HSL lightness and failed
     greyscale, because amber at HSL-L 50 lands at L* 73, essentially the
     same perceptual lightness as a cobalt at HSL-L 77. Two slices that
     differ only in hue vanish into each other in greyscale and for a good
     share of colour-blind readers.

     Verified, not asserted (scripted checks, 2026-09-12):
       greyscale     every ADJACENT pair, incl. wrap, ΔL* >= 12  -- min 14
       deuteranopia  EVERY pair, Vienot-simulated, ΔE >= 12      -- min 22.5
       normal vision EVERY pair, CIE76 ΔE >= 20                  -- min 35.7
       ordering      no red slice ever adjacent to a green slice

     Slice order matters. Re-ordering this list breaks the greyscale
     guarantee and the red/green rule -- re-run the check if you change it.

     LABELS: the ramp alternates light and dark by design, so a single
     label colour cannot work. Use --chart-label-on-N per slice.
     ==================================================================== */
  --chart-cat-1: #003e8f;   /* cobalt, darkest      L* 28 */
  --chart-cat-2: #fab43b;   /* amber, light         L* 78 */
  --chart-cat-3: #cb1e24;   /* red                  L* 44 */
  --chart-cat-4: #bedafe;   /* cobalt, lightest     L* 86 */
  --chart-cat-5: #0f5132;   /* green, dark          L* 30 */
  --chart-cat-6: #96620a;   /* amber, deep / ochre  L* 46 */
  --chart-cat-7: #4b97f9;   /* cobalt, mid          L* 62 */
  --chart-cat-8: #53d197;   /* green, light         L* 76 */

  /* Label colour per slice, from measured contrast against each fill. */
  --chart-label-on-1: var(--neutral-0);    /* 10.04 */
  --chart-label-on-2: var(--neutral-900);  /* 11.64 */
  --chart-label-on-3: var(--neutral-0);    /*  5.57 */
  --chart-label-on-4: var(--neutral-900);  /* 14.60 */
  --chart-label-on-5: var(--neutral-0);    /*  9.35 */
  --chart-label-on-6: var(--neutral-0);    /*  5.18 */
  --chart-label-on-7: var(--neutral-900);  /*  7.08 */
  --chart-label-on-8: var(--neutral-900);  /* 10.98 */

  /* Chart furniture */
  --chart-grid:      var(--neutral-200);
  --chart-axis-text: var(--text-muted);
  --chart-series:    var(--cobalt-600);   /* single-series bars/lines */


  /* ==== ELEVATION / Z ================================================== */
  --z-base:     0;
  --z-raised:   10;
  --z-sticky:   100;
  --z-nav:      200;
  --z-dropdown: 300;
  --z-overlay:  400;
  --z-modal:    500;
  --z-toast:    600;


  /* ==== LAYOUT ========================================================= */
  --container-sm:  640px;
  --container-md:  768px;
  --container-lg:  1024px;
  --container-xl:  1200px;
  --container-2xl: 1400px;
  --gutter-mobile:  var(--space-6);  /* 16px */
  --gutter-desktop: var(--space-9);  /* 32px */
}


/* ==========================================================================
   DENSITY — COMPACT TIER

   Opt in with <body data-density="compact">. One attribute, no structural
   change; every component reading --dash-* tightens at once.

   REVERTS TO COMFORTABLE ON TOUCH AND ON NARROW VIEWPORTS, deliberately.
   A 36px row is correct for desk work with a mouse and wrong for a thumb --
   this page relies on a 44px minimum touch target in 29 places, and
   compacting those on a phone would break them. `pointer: coarse` catches
   touch regardless of width; the width query catches a narrow window on a
   trackpad, where the layout is stacked and the density buys nothing anyway.
   This is why the tier is tokens rather than a global font-size change:
   it can be conditionally withdrawn.
   ========================================================================== */

[data-density="compact"] {
  --dash-row-height:  36px;
  --dash-row-pad-y:   var(--space-4);     /*  8px */
  --dash-row-pad-x:   var(--space-5);     /* 12px */
  --dash-cell-font:   var(--text-sm);     /* 13px */
  --dash-label-font:  var(--text-2xs);    /* 11px */
  --dash-card-pad:    var(--space-7);     /* 20px */
  --dash-card-gap:    var(--space-6);     /* 16px */
  --dash-header-gap:  var(--space-6);     /* 16px */
  --dash-title-font:  var(--text-md);     /* 16px */
  --dash-control-h:   32px;
}

@media (pointer: coarse), (max-width: 860px) {
  [data-density="compact"] {
    --dash-row-height:  44px;
    --dash-row-pad-y:   var(--space-5);
    --dash-row-pad-x:   var(--space-6);
    --dash-cell-font:   var(--text-base);
    --dash-label-font:  var(--text-xs);
    --dash-card-pad:    var(--space-8);
    --dash-card-gap:    var(--space-7);
    --dash-header-gap:  var(--space-7);
    --dash-title-font:  var(--text-lg);
    --dash-control-h:   40px;
  }
}


/* ==========================================================================
   COMPONENT TOKENS
   One block per component, every state named. Component CSS references
   only these, never the ramps above.
   ========================================================================== */

:root {

  /* ---- BUTTON ---- */
  --btn-font: var(--font-body);
  --btn-weight: var(--weight-semibold);
  --btn-radius: var(--radius-md);
  --btn-transition: var(--transition-colors),
                    box-shadow var(--duration-fast) var(--ease-standard),
                    transform var(--duration-instant) var(--ease-standard);

  --btn-height-sm: 32px;
  --btn-height-md: 40px;
  --btn-height-lg: 48px;
  --btn-padding-sm: 0 var(--space-5);
  --btn-padding-md: 0 var(--space-7);
  --btn-padding-lg: 0 var(--space-9);
  --btn-font-size-sm: var(--text-sm);
  --btn-font-size-md: var(--text-base);
  --btn-font-size-lg: var(--text-md);

  /* primary */
  --btn-primary-bg:            var(--cobalt-600);
  --btn-primary-bg-hover:      var(--cobalt-700);
  --btn-primary-bg-active:     var(--cobalt-800);
  --btn-primary-bg-disabled:   var(--neutral-200);
  --btn-primary-text:          var(--neutral-0);   /* 8.05 AAA */
  --btn-primary-text-disabled: var(--text-disabled);
  --btn-primary-border:        transparent;
  --btn-primary-shadow:        var(--shadow-xs);
  --btn-primary-shadow-focus:  var(--focus-ring-shadow);

  /* secondary — outlined */
  --btn-secondary-bg:              var(--neutral-0);
  --btn-secondary-bg-hover:        var(--cobalt-50);
  --btn-secondary-bg-active:       var(--cobalt-100);
  --btn-secondary-bg-disabled:     var(--neutral-50);
  --btn-secondary-text:            var(--cobalt-600);
  --btn-secondary-text-hover:      var(--cobalt-700);
  --btn-secondary-text-disabled:   var(--text-disabled);
  --btn-secondary-border:          var(--border-default);
  --btn-secondary-border-hover:    var(--cobalt-300);
  --btn-secondary-border-disabled: var(--border-subtle);

  /* ghost — no chrome until hover */
  --btn-ghost-bg:            transparent;
  --btn-ghost-bg-hover:      var(--cobalt-50);
  --btn-ghost-bg-active:     var(--cobalt-100);
  --btn-ghost-text:          var(--text-secondary);
  --btn-ghost-text-hover:    var(--cobalt-700);
  --btn-ghost-text-disabled: var(--text-disabled);
  --btn-ghost-border:        transparent;

  /* danger */
  --btn-danger-bg:           var(--red-600);
  --btn-danger-bg-hover:     var(--red-700);
  --btn-danger-bg-active:    var(--red-900);
  --btn-danger-bg-disabled:  var(--neutral-200);
  --btn-danger-text:         var(--neutral-0);   /* 6.47 AA */
  --btn-danger-border:       transparent;
  --btn-danger-shadow-focus: var(--shadow-focus-danger);

  /* shared states */
  --btn-disabled-opacity: 1;      /* we change color, not opacity --
                                     opacity on a disabled button over a
                                     tinted surface muddies the label */
  --btn-active-transform: translateY(1px);
  --btn-loading-spinner: var(--neutral-0);


  /* ---- INPUT ---- */
  --input-height-sm: 32px;
  --input-height-md: 40px;
  --input-height-lg: 48px;
  --input-padding-x: var(--space-5);
  --input-font: var(--font-body);
  --input-font-size: var(--text-base);
  --input-radius: var(--radius-md);
  --input-bg:            var(--neutral-0);
  --input-bg-hover:      var(--neutral-0);
  --input-bg-disabled:   var(--neutral-100);
  --input-bg-readonly:   var(--neutral-50);
  --input-text:          var(--text-primary);
  --input-text-disabled: var(--text-disabled);
  --input-placeholder:   var(--text-placeholder);
  --input-border:          var(--border-default);
  --input-border-hover:    var(--border-strong);
  --input-border-focus:    var(--cobalt-600);
  --input-border-error:    var(--red-600);
  --input-border-success:  var(--green-600);
  --input-border-disabled: var(--border-subtle);
  --input-shadow-focus: var(--shadow-focus-brand);
  --input-shadow-error: var(--shadow-focus-danger);
  --input-label-color:  var(--text-secondary);
  --input-label-size:   var(--text-sm);
  --input-label-weight: var(--weight-medium);
  --input-help-color:   var(--text-muted);
  --input-help-size:    var(--text-xs);
  --input-error-color:  var(--color-danger-text);


  /* ---- CARD ---- */
  --card-bg:              var(--surface-raised);
  --card-bg-hover:        var(--neutral-50);
  --card-border:          var(--border-subtle);
  --card-border-hover:    var(--border-default);
  --card-border-selected: var(--cobalt-600);
  --card-radius:          var(--radius-lg);
  --card-padding:         var(--space-8);
  --card-padding-compact: var(--space-6);
  --card-shadow:          var(--shadow-sm);
  --card-shadow-hover:    var(--shadow-md);
  --card-shadow-raised:   var(--shadow-lg);


  /* ---- BADGE ---- */
  --badge-radius: var(--radius-sm);
  --badge-padding: 2px var(--space-4);
  --badge-font-size: var(--text-2xs);
  --badge-weight: var(--weight-semibold);
  --badge-tracking: var(--tracking-wide);
  --badge-neutral-bg:   var(--neutral-100);
  --badge-neutral-text: var(--text-secondary);
  --badge-brand-bg:     var(--color-brand-surface);
  --badge-brand-text:   var(--cobalt-700);
  --badge-success-bg:   var(--color-success-surface);
  --badge-success-text: var(--color-success-text);
  --badge-warning-bg:   var(--color-warning-surface);
  --badge-warning-text: var(--color-warning-text);
  --badge-danger-bg:    var(--color-danger-surface);
  --badge-danger-text:  var(--color-danger-text);
  --badge-info-bg:      var(--color-info-surface);
  --badge-info-text:    var(--color-info-text);


  /* ---- MODAL ---- */
  --modal-bg:      var(--surface-raised);
  --modal-radius:  var(--radius-xl);
  --modal-padding: var(--space-9);
  --modal-shadow:  var(--shadow-xl);
  --modal-border:  var(--border-subtle);
  --modal-overlay: var(--surface-overlay);
  --modal-width-sm: 400px;
  --modal-width-md: 520px;
  --modal-width-lg: 720px;
  --modal-enter-duration: var(--duration-base);
  --modal-enter-ease:     var(--ease-out);
  --modal-exit-duration:  var(--duration-fast);
  --modal-title-font: var(--font-heading);
  --modal-title-size: var(--text-xl);


  /* ---- TOAST ---- */
  --toast-bg:      var(--surface-inverse);
  --toast-text:    var(--text-inverse);
  --toast-radius:  var(--radius-lg);
  --toast-padding: var(--space-5) var(--space-7);
  --toast-shadow:  var(--shadow-lg);
  --toast-width:   360px;
  --toast-gap:     var(--space-5);
  --toast-font-size: var(--text-base);
  --toast-enter-duration: var(--duration-slow);
  --toast-enter-ease:     var(--ease-spring);
  --toast-exit-duration:  var(--duration-fast);
  --toast-accent-success: var(--green-500);
  --toast-accent-warning: var(--amber-500);
  --toast-accent-danger:  var(--red-500);
  --toast-accent-info:    var(--cobalt-400);
}


/* ==========================================================================
   ACCESSIBILITY
   ========================================================================== */

@media (prefers-reduced-motion: reduce) {
  :root {
    --duration-instant: 1ms;
    --duration-fast:    1ms;
    --duration-base:    1ms;
    --duration-slow:    1ms;
    --btn-active-transform: none;
  }
}
