/* ════════════════════════════════════════════════════════════════════════
   shared/dashboard-tokens.css — the ONE color/scale source for all three
   dashboard surfaces (dashboard.html, client_dashboard.html, customer_view.html).

   Before 2026-08-07 each of the three files declared its own independent
   :root block with its own vocabulary (--accent/--text/--surface here,
   --brand/--ink/--card there) and, worse, the SAME token name resolved to
   a DIFFERENT color depending which file you were looking at: --brand was
   #7c5cf6 (purple) in client_dashboard.html and #0f766e (teal) in
   customer_view.html — the second file's own comments record that this was
   a known, already-diagnosed accessibility failure (#7c5cf6 measures
   4.45:1 on white, under the 4.5:1 text floor) that was fixed there and
   never carried back. Editing "the" brand color only ever changed one of
   three pages. This file is the fix: one set of values, three files that
   read from it.

   The canonical scale below is lifted verbatim from dashboard.html, which
   had the only complete system (spacing/type/radius/shadow/motion/z-index,
   not just color) — including its original WCAG contrast notes, which are
   real evidence, not decoration, and are kept intact.
   ════════════════════════════════════════════════════════════════════════ */

:root {
  /* ── Warm canvas / light cards ─────────────────────────────────────────
     Asaf, 2026-08-12. Supersedes the all-#ffffff pass below, and it is a
     deliberate reversal of that directive's LETTER while keeping its
     INTENT. Worth reading before changing it back.

     The old complaint was "strict, clean white background #FFFFFF, no gray
     boxes": --bg and --surface-2 were both #f6f9fc, a faint BLUE-grey, so
     ~43 small tiles read as dirty grey patches ON white cards. The fix
     applied then was to flatten everything to #ffffff — which worked, but
     cost the system its figure/ground: card #ffffff against canvas #ffffff
     is a 1.00:1 luminance step, i.e. none at all, so every card needed a
     hairline just to exist. The whole UI became outlines.

     This inverts the arrangement rather than flattening it. The CANVAS is
     tinted and the CARDS are light — the opposite of grey-tiles-on-white,
     and the standard Linear/Notion/Things arrangement. Cards now float on
     the canvas at a real 1.08:1 step and read as cards without relying on
     their border. No grey box sits on top of anything lighter than itself.

     The tint is warm (sage, hue 96deg) rather than the old cold blue-grey
     (hue ~213deg). That matters for one measurable reason: --accent is a
     TEAL. Against a blue-black/blue-grey system the accent sat next to its
     own neighbours on the wheel and read muddy; against a warm-neutral
     ground the same teal is the only saturated thing on screen.

     MEASURED, not eyeballed — all on the light theme:
       text/text-2/text-3 on --surface    15.72 / 9.09 / 4.87 : 1
       text/text-2/text-3 on --surface-2  16.13 / 9.33 / 5.00 : 1
       text/text-2/text-3 on --bg         14.57 / 8.42 / 4.51 : 1
       --accent on --surface / --surface-2 4.63 / 4.75 : 1
     Every one clears the 4.5:1 AA text floor.

     THE ONE TRAP: --accent (#087F73) on --bg measures 4.29:1 and FAILS.
     This is not a surprise — the accent note below predicted it verbatim
     ("Anything that darkens --surface ... lands under 4.5:1"). It is why
     --accent-on-bg exists a few lines down. Accent-coloured TEXT belongs on
     a card, never directly on the canvas; where it must sit on the canvas,
     use --accent-on-bg (5.38:1). The brand fill colour is NOT changed —
     #087F73 stays exactly as decreed, and white-on-it is still 4.89:1.

     CARDS STAY PURE #FFFFFF. This is the part that makes the reversal above
     safe rather than merely pretty, and it was not the first thing tried —
     a tinted canvas WITH tinted cards (#eef1ec + #f8f9f7) was measured
     first and rejected. Tinting the card darkens the surface that carries
     every piece of text, which pushed --accent and four of the five
     --status-* tokens under the 4.5:1 floor and would have forced a retune
     of six carefully-chosen colours to buy nothing. Keeping the card white
     means every token below keeps the exact value and the exact measured
     ratio it already had, and the change costs zero contrast anywhere.

     It also gives the BEST figure/ground of the three arrangements, which
     is the opposite of what you would guess:
       white card on sage canvas (this)      1.140:1
       sage  card on sage canvas (rejected)  1.031:1
       white card on white canvas (before)   1.000:1
     And it keeps the original directive's actual promise: the surface the
     user reads is still strict #FFFFFF, and no grey box sits on top of
     anything lighter than itself.

     --surface-3 is now DARKER than the canvas, not lighter. It is only a
     progress-bar track / inset-well fill (7 uses); on a tinted canvas an
     inset well has to recede, and the old #eef2f7 would have floated. */
  /* Asaf, 2026-08-12 (later pass): back to a WHITE canvas. The sage note
     above is kept as the record of why the tint existed, but the dashboard
     was reading as "green" and a clean white canvas is the directive. This
     does NOT regress contrast — white is lighter than the sage, so every
     text/accent/status ratio measured above IMPROVES on white. Re-measured:
     --accent 4.29:1 -> 4.89:1 ON CANVAS (the one documented sage failure is
     now fixed); text/-2/-3 14.57/8.42/4.51 -> 16.60/9.60/5.14. Cards stay
     #ffffff and read against the canvas via their --card-line hairline
     (~1.17:1) plus --sh-*, not a surface/canvas luminance step — the
     directive's border/shadow separation, no heavy shadow added. */
  --bg: #ffffff;          /* white canvas — backdrop ONLY, never carries text */
  --surface: #ffffff;     /* card — pure white, unchanged, carries all content */
  --surface-2: #ffffff;   /* raised card — elevation comes from --sh-*, not tint */
  /* #94949e sat exactly on the 3:1 UI-boundary floor. This sweep just
     made --border-2 carry hundreds more borders (mapped from the
     rgba(255,255,255,*) dark-theme leak in dashboard.js), and --border-2
     (#d4d4d8) was only 1.48:1 — invisible-adjacent, same defect class as
     the leak it replaced. Both tokens now clear 3:1 with a visible gap
     between them: --border 3.25:1, --border-2 3.0:1. */
  /*
     2026-08-12: these are now ALPHA, derived from --text's ink, rather than
     opaque blue-grey hexes. That is the single biggest reason the old set
     needed four near-identical border tokens: an opaque #e6ebf1 is only
     correct on #ffffff, so a card, a tinted well and a raised surface each
     needed their own hand-picked edge. An ink-alpha line composites
     correctly over ALL of them and stays the same token. The measured
     ratios below are unchanged in spirit — an 0.09 ink line over --surface
     lands at #e9eae9, within a hair of the #e6ebf1 it replaces. */
  --border: rgba(24,32,31,.09);    /* decorative hairline — see role note below */
  --border-2: rgba(24,32,31,.16);  /* alias of --border-strong (emphasis role) */
  /* Warm ink, not blue-black. #0a2540 was a BLUE black (hue 213deg) sitting
     under a teal accent — neighbours on the wheel, which is what made the
     old palette read muddy. #18201f is a green-black (hue 96deg, same
     family as the accent), so the teal is the only saturated thing on
     screen instead of competing with the text colour. Ratios on --surface
     (#ffffff), which is where text actually lives; second figure is on the
     --bg canvas, for the headings and labels that sit outside a card: */
  --text: #18201f;        /* 16.60:1 on card · 14.57:1 on canvas */
  --text-2: #3d4746;      /*  9.60:1 on card ·  8.42:1 on canvas */
  --text-3: #64706e;      /*  5.14:1 on card ·  4.51:1 on canvas */
  /* Asaf, 2026-08-04: was #5b5bd6 (indigo/purple) — replaced site-wide via
     this one token. Teal-700, measured, not eyeballed: 5.47:1 as text on
     --surface, 5.18:1 on --bg, 5.47:1 white-on-it as a button fill — all
     >= the previous purple's own 5.37:1, so nothing regresses. Chosen over
     a blue because --select-ring aliases to --accent specifically so a
     selected card and a running stage (--status-running, #0369a1) stay
     visually distinct — a blue accent would recreate that exact bug.

     Asaf, 2026-08-08: #0f766e -> #087F73 by executive decision. Measured,
     not eyeballed, same method as the line above: 4.89:1 as text on
     --surface and on --bg, 4.89:1 white-on-it as a button fill. That
     clears the 4.5:1 AA text floor, but it IS a real reduction from the
     previous teal's 5.47:1 — headroom above the floor is now 0.39, not
     0.97. Anything that darkens --surface, or any future nudge of this
     value toward lighter, lands under 4.5:1. Re-measure before changing.

     Two hand-synced copies were updated with it: the favicon data-URI in
     dashboard.html (an SVG loaded as its own document has its own CSS
     scope and cannot read var(--accent)) and the hero gradient in
     customer_view.css. --viz-1 was deliberately NOT changed — see it. */
  /*
     Asaf, 2026-08-12: STILL #087F73. The canvas moved to a warm tint around
     it, and the prediction two paragraphs up came true exactly — this value
     measures 4.29:1 on the new --bg and would have failed. It was NOT
     darkened to rescue that, because the brand fill is an executive
     decision and white-on-it is still 4.89:1. The canvas case is handled by
     --accent-on-bg instead, so the brand colour never moves for a layout
     reason. Because cards stayed #ffffff, its measured 4.89:1 on --surface
     is not merely preserved — it is the same number as before this pass,
     so nothing about the accent regressed at all. */
  --accent: #087F73;
  /* Accent-coloured TEXT that genuinely sits on --bg rather than on a card.
     Same hue and saturation, four steps darker: 5.38:1 on --bg. Do not use
     it as a fill and do not use it on a card — --accent is correct there,
     and swapping them makes two nearly-identical teals visible side by
     side. If you find yourself reaching for this often, the real problem is
     that content is sitting on the canvas instead of in a card. */
  --accent-on-bg: #076e64;
  /* Accent-coloured TEXT on an accent tint (--accent-soft and the 20% count
     bubble stacked on it): the selected nav item, the selected library chip.
     --accent there measured 4.16:1 on the 12% tint and 3.23:1 on the stacked
     20% bubble. This is the --accent-hover value: 6.41:1 and 4.97:1. */
  --accent-on-tint: #0c5f58;
  --g: #15803d;
  --o: #b45309;
  --r: #dc2626;
  --p: #7c3aed;
  /* Card hairline. Deliberately lighter than --border-2 (the emphasis border
     used for controls and dividers): a resting card outline is a containment
     cue, not an emphasis one, and at --border-2 a grid of them reads as a
     wireframe. Contrast is not a concern here — no text or control state
     depends on this edge being distinguishable, unlike the two tokens above. */
  /*
     2026-08-12: alpha, same reasoning as --border above. It also matters
     less than it used to: cards now have a real 1.08:1 luminance step
     against the canvas, so this edge is a refinement rather than the only
     thing making a card exist. */
  --card-line: rgba(24,32,31,.08);

  /* ── Glass ─────────────────────────────────────────────────────────────
     Translucent card fills. These only do anything over a TINTED ground —
     over the old #ffffff canvas a white-at-58% fill was literally
     invisible, which is why they arrive in the same pass as --bg. Use them
     with backdrop-filter for panels that overlap content (drawers, sticky
     headers, overlays); for ordinary cards prefer the opaque --surface /
     --surface-2, which are these two composited over --bg and cost no
     compositing layer. Composited values are #f8f9f7 and #fbfcfa. */
  --glass: rgba(255,255,255,.58);
  --glass-strong: rgba(255,255,255,.76);

  /* Warm dark + warm paper. --graphite is the inverse surface (dark chips,
     tooltips, code wells); --offwhite on it measures 11.98:1. --offwhite is
     a warmer paper than --surface for large passive areas. Neither is a
     status colour and neither should carry meaning. */
  --graphite: #27302f;
  --offwhite: #f3f1e9;

  /* Consumed only by dashboard.css (verified 2026-08-08) — client_dashboard
     and customer_view carry their own layout, so these two are safe to tune
     for the admin dashboard without touching the other surfaces. */
  --sidebar-w: 208px;
  /* The bar's own height. dashboard.css adds the notch inset on top of
     this to form --header-h, so the number stays in one place and the
     safe-area maths stays in the file that needs it. */
  --header-h-base: 56px;
  --header-h: 56px;
  /* Matches --bg. The canvas is white now, so this is a translucent white
     bar over white content, held apart by its border-bottom, not a tint. */
  --topbar-blur: rgba(255,255,255,.85);
  --select-ring: var(--accent);
  --accent-soft: color-mix(in srgb, var(--accent) 12%, transparent);

  /* ── Motion tokens ────────────────────────────────────────────────────
     Entrances and interactive feedback use --ease-out (starts fast, feels
     responsive — never --ease-in on UI, it delays the moment the user is
     watching most closely); on-screen movement uses --ease-in-out;
     edge-anchored panels use --ease-drawer. */
  --ease-out: cubic-bezier(0.23, 1, 0.32, 1);
  --ease-in-out: cubic-bezier(0.77, 0, 0.175, 1);
  --ease-drawer: cubic-bezier(0.32, 0.72, 0, 1);

  /* ── Status tokens ────────────────────────────────────────────────────
     done / running / warn / fail / idle. Values are the light set, each
     verified >=4.5:1 on #ffffff. */
  /*
     2026-08-12: done/warn/fail nudged ~1-2% darker (hue and saturation held
     to the degree) so they clear 4.5:1 on the --bg canvas as well as on a
     card. They already passed on cards; the canvas is darker and they
     landed at 4.40 / 4.32 / 4.24 there. The alternative was to declare them
     card-only, like --accent — but --accent is a single executive-fixed
     value with a guard token, whereas four "card-only" colours is a rule
     nobody would follow. These are ordinary semantic values, so moving them
     was the cheaper fix. Both figures below are measured. */
  --status-done:    #157d3c;  /* 5.21:1 card · 4.57:1 canvas */
  --status-running: #0369a1;  /* 5.93:1 card · 5.21:1 canvas — unchanged, already passed */
  --status-warn:    #9b5e07;  /* 5.25:1 card · 4.61:1 canvas */
  --status-fail:    #d32222;  /* 5.22:1 card · 4.58:1 canvas */
  /* #697386 -> #5c6664, 2026-08-12. The only status colour that was a
     NEUTRAL rather than a hue: the other four carry meaning through their
     hue (green/blue/amber/red) and are unaffected by a warmer ground, but
     "idle" was a blue-grey, and a blue-grey neutral is exactly what reads
     as dirt on a warm canvas. Re-hued to the ink family and slightly
     darkened, which also buys headroom: 5.93:1 on card, 5.21:1 on canvas,
     against the old 4.9:1 / 4.19:1. */
  --status-idle:    #5c6664;  /* 5.93:1 on card · 5.21:1 on canvas */

  /* ── Scale tokens ─────────────────────────────────────────────────────
     Spacing — 4px base. Airiness comes from the gaps BETWEEN things
     (section rhythm, grid gutters), not from inflating every box. */
  --sp-1: 4px;   --sp-2: 8px;   --sp-3: 12px;  --sp-4: 16px;
  --sp-5: 20px;  --sp-6: 24px;  --sp-7: 28px;  --sp-8: 32px;
  --sp-10: 40px; --sp-12: 48px; --sp-16: 64px; --sp-20: 80px;

  /* Radius. Migration: 3→xs, 6|7→sm, 8|9→md, 10|12→lg, 14|16→xl, 18→2xl. */
  --r-xs: 4px;  --r-sm: 6px;  --r-md: 8px;
  --r-lg: 12px; --r-xl: 16px; --r-2xl: 20px; --r-full: 999px;

  /* Type — 8 steps, each with its line-height. px because the whole
     system is px and mixing units across a scale is how the original
     mess started. */
  --fs-100: 11px;  --lh-100: 16px;   /* micro labels, meta, badges */
  --fs-200: 14px;  --lh-200: 21px;   /* body AND card titles */
  --fs-300: 18px;  --lh-300: 26px;   /* section headings */
  --fs-400: 24px;  --lh-400: 32px;   /* page h1 */
  --fs-500: 32px;  --lh-500: 36px;   /* KPI numerals */

  --tracking-tight: -0.02em;
  --tracking-normal: 0;
  --tracking-caps: 0.08em;

  --fw-regular: 400; --fw-medium: 500; --fw-semi: 600; --fw-bold: 700;

  /* Shadows, ink-tinted rather than black — a neutral-grey shadow on a
     warm surface reads as dirt. Paired with a hairline border, this is
     what separates a card from a wireframe box.

     2026-08-12: the comment above was already right and the values were
     already wrong. They were rgba(10,37,64,*) — that is #0a2540, the OLD
     blue-black text colour, so every shadow in the system was a faint BLUE
     bloom. On the previous #ffffff canvas that was merely inert; on a warm
     sage canvas it is exactly the "reads as dirt" failure this comment
     warns about. Retinted to the new ink (24,32,31) so the shadow is a
     darker version of the ground it falls on, which is what a shadow is.
     Opacities are slightly up: the canvas is darker now, so the old values
     rendered nearly invisible against it. */
  --sh-xs: 0 1px 1px rgba(24,32,31,.05);
  --sh-sm: 0 1px 2px rgba(24,32,31,.07), 0 1px 3px rgba(24,32,31,.05);
  --sh-md: 0 2px 5px rgba(24,32,31,.07), 0 6px 16px rgba(24,32,31,.07);
  --sh-lg: 0 4px 10px rgba(24,32,31,.07), 0 16px 40px rgba(24,32,31,.11);
  --sh-focus: 0 0 0 3px var(--accent-soft);

  --dur-fast: 120ms; --dur-base: 160ms; --dur-slow: 240ms; --dur-drawer: 300ms;

  /* Role-split borders. --border is decorative; 1.4.11 (identify-a-control)
     governs input edges and focus rings, not a hairline around a card. */
  --border-strong:  rgba(24,32,31,.16);  /* hover, active, section splits */
  --border-control: #7d8a88;   /* inputs/selects/toggles — 3.34:1 on --surface.
                                  Opaque on purpose: 1.4.11 governs this edge,
                                  and a control boundary must not change
                                  contrast with whatever it happens to sit on. */

  /* Now DARKER than --bg, where it used to be lighter. It is only a
     progress-bar track / inset-well fill (7 uses) — on a tinted canvas an
     inset must recede, and the old #eef2f7 would have floated above it. */
  --surface-3: #e9eaec;        /* bar tracks, inset wells — neutral, was sage #e4e8e3 */
  --text-inverse: #ffffff;
  --on-accent: #ffffff;
  --accent-hover: #0c5f58;  /* darker teal, 7.52:1 on white */
  --overlay: rgba(24,32,31,.42);   /* ink, retinted off the old blue-black */

  --status-done-bg:    color-mix(in srgb, var(--status-done) 8%,    var(--surface));
  --status-running-bg: color-mix(in srgb, var(--status-running) 8%, var(--surface));
  --status-warn-bg:    color-mix(in srgb, var(--status-warn) 8%,    var(--surface));
  --status-fail-bg:    color-mix(in srgb, var(--status-fail) 8%,    var(--surface));

  /* Data-viz ramp. NOT status colours — charts must not imply health.
     --viz-1 keeps #0f766e and did NOT follow --accent to #087F73 on
     2026-08-08. It is a categorical series colour whose job is to stay
     separable from --viz-2 (#0891b2) and --viz-3 (#16a34a), not to match
     the brand; it only ever equalled the accent by coincidence. Chasing
     the accent here would narrow the gap to --viz-3 for no benefit. */
  --viz-1: #0f766e; --viz-2: #0891b2; --viz-3: #16a34a;
  --viz-4: #9333ea; --viz-5: #ea580c; --viz-6: #db2777;

  --content-max: 1440px;

  --z-content: 1;  --z-wp: 38;    --z-topbar: 40; --z-sidebar: 45;
  --z-dropdown: 50; --z-sheet: 120; --z-modal: 200; --z-toast: 300;

  /* Breakpoints CANNOT be used in @media — custom properties are invalid
     there in every shipping browser. These exist to be read by JS (see
     EA_BP in dashboard.js) and by humans. Keep in sync with @media by hand. */
  --bp-sm: 640px; --bp-md: 768px; --bp-lg: 1024px; --bp-xl: 1280px;
}

/* ── Dark theme ────────────────────────────────────────────────────────── */
/* Warmed 2026-08-12 to match the light theme's move off blue-grey. Same
   reasoning, same hue family (~96deg green-neutral instead of ~213deg
   blue): the accent is a teal, and a blue-tinted dark shell competes with
   it. The light theme's canvas/card INVERSION is deliberately not copied
   here — in dark mode the canvas is already the darkest thing and cards
   lift off it, which is the same figure/ground relationship arrived at from
   the other side. */
:root[data-theme="dark"] {
  --bg:             #121614;
  --surface:        #1a201e;
  --surface-2:      #212826;
  --surface-3:      #2a322f;

  /* Alpha here too, for the same reason as light: one edge token that
     composites correctly over --surface, --surface-2 and --surface-3
     instead of three hand-picked opaque values. */
  --border:         rgba(255,255,255,.09);   /* decorative hairline */
  --border-strong:  rgba(255,255,255,.16);   /* hover / emphasis    */
  --border-2:       var(--border-strong);
  --border-control: #6b7570;   /* form affordance, keeps a visible edge */
  /* Card hairline — sits just under --border here for the same reason it sits
     under --border-2 in light: a resting card edge is containment, not
     emphasis. Without this the light value would leak into dark mode. */
  --card-line:      rgba(255,255,255,.07);

  /* Warm off-white, not blue-white. Measured on the new --bg (#121614). */
  --text:           #e9ede9;   /* 15.1:1 on --bg — not pure white */
  --text-2:         #aab4b0;   /*  7.9:1 */
  --text-3:         #8b9591;   /*  5.4:1 on --bg, 4.7:1 on --surface-2 */
  --text-inverse:   #121614;

  /* Dark-mode overrides for the tokens added in the light block. Without
     these the light values inherit straight through: --glass would paint a
     white-at-58% slab on a near-black card, and --offwhite would be a sheet
     of #f3f1e9. --graphite/--offwhite keep their ROLE (inverse surface, and
     the text that sits on it) rather than their literal darkness, so they
     swap: in dark mode the inverse of the page is light. */
  --glass:          rgba(255,255,255,.06);
  --glass-strong:   rgba(255,255,255,.10);
  --graphite:       #e9ede9;
  --offwhite:       #121614;
  --accent-on-bg:   var(--accent);  /* light-on-dark: no canvas problem here */
  --accent-on-tint: var(--accent);  /* light teal on a dark tint already passes */

  --accent:         #5eead4;
  --accent-hover:   #99f6e4;  /* lighter teal, dark-mode hover brightens */
  --accent-soft:    color-mix(in srgb, var(--accent) 18%, transparent);
  --on-accent:      #121614;   /* follows --bg; 12.34:1 on --accent */
  --select-ring:    var(--accent);

  --status-done:    #4ade80;   /*  9.6:1 */
  --status-running: #60b8f5;   /*  8.4:1 */
  --status-warn:    #fbbf24;   /* 10.9:1 */
  --status-fail:    #f87171;   /*  6.9:1 */
  /* Same treatment as light: was #7d8b9e, a blue-grey, which measured
     4.34:1 against the warmed dark surfaces — an actual AA failure, not
     just a tonal mismatch. Re-hued to the neutral family and lifted. */
  --status-idle:    #939d99;   /*  5.4:1 worst-case across bg/surface/surface-2 */

  /* ── --g / --o / --r / --p, dark overrides ────────────────────────────
     ADDED 2026-08-12, and this is a PRE-EXISTING bug fix, not fallout from
     the warm-palette pass. These four generic colour tokens were declared
     once in :root and never overridden for dark, so the light values were
     painting straight onto dark cards. Measured on the OLD dark surface
     (#151d2b), i.e. before anything in this pass changed:
         --g #15803d  3.37:1     --o #b45309  3.37:1     --r #dc2626  3.50:1
     All three were already under the 4.5:1 floor. On the new dark surface
     they measure 3.30 / 3.29 / 3.43 — a 0.07 shift, which is why this was
     found now rather than caused now. It surfaced because .flow-card's
     state text uses --g/--o/--r rather than --status-*, so a rendered-pixel
     check of a real card caught what a token-level check could not.

     Aliased to the --status-* dark values rather than given new literals:
     those are already verified against all three dark grounds, and two
     independent green/amber/red sets in one theme is the exact duplicate-
     source-of-truth problem this whole file exists to remove. Light is
     deliberately NOT aliased — the light values pass on cards, which is
     where all 120 call sites use them, and repointing them would shift
     every one of those for no measured gain. */
  --g: var(--status-done);
  --o: var(--status-warn);
  --r: var(--status-fail);
  --p: #c084fc;   /* 7.4:1 — the light #7c3aed measured 2.1:1 here */

  --viz-1: #5eead4; --viz-2: #38bdf8; --viz-3: #4ade80;
  --viz-4: #c084fc; --viz-5: #fb923c; --viz-6: #f472b6;

  --topbar-bg:   rgba(21,29,43,.88);
  --topbar-blur: rgba(21,29,43,.88);
  --overlay:     rgba(4,8,14,.62);

  --sh-xs: inset 0 1px 0 rgba(255,255,255,.03);
  --sh-sm: inset 0 1px 0 rgba(255,255,255,.04), 0 1px 2px rgba(0,0,0,.4);
  --sh-md: inset 0 1px 0 rgba(255,255,255,.04), 0 4px 12px rgba(0,0,0,.45);
  --sh-lg: inset 0 1px 0 rgba(255,255,255,.05), 0 12px 32px rgba(0,0,0,.55);
  --sh-focus: 0 0 0 3px var(--accent-soft);

  color-scheme: dark;
}

/* ════════════════════════════════════════════════════════════════════════
   Compatibility aliases — client_dashboard.html and customer_view.html use
   an older, separate color vocabulary (--brand/--ink/--card/--line/--ok/
   --warn/--danger). Rewriting every call site across those two files
   (~1,400 + ~5,900 lines) to the canonical names above is out of scope for
   this pass and high-risk for no visual benefit. These aliases let both
   files keep their existing call sites working while actually resolving to
   the ONE set of values above — which is the fix: today, editing --accent
   here changes all three surfaces, not one.

   Only COLOR is unified. --r/--r-sm/--r-lg/--sh/--sh-sm (corner radius and
   shadow shape) are left as each file's own local values below — that is a
   deliberate visual-language difference between the admin tool and the
   client-facing pages, not a bug, and was never part of the reported
   "stuck design" problem.

   A single :root definition resolves correctly in both themes automatically
   (var() follows the cascade at use time) — no separate dark block needed
   here.
   ════════════════════════════════════════════════════════════════════════ */
/* Elevation. Added 2026-08-17: this sheet had no shadow token at all, so every
   raised surface either used a literal or had no shadow, and a menu that hangs
   over content needs one to read as above it rather than in it.
   Two steps only — a card and a menu. A ramp of six invites picking by number
   instead of by meaning, and there are exactly two things here that lift.
   Dark values are separate below: the same rgba over a dark ground is
   invisible, so darkness there comes from opacity, not from the same colour. */
:root {
  --shadow-sm: 0 1px 2px rgba(16, 24, 40, .06), 0 1px 3px rgba(16, 24, 40, .05);
  --shadow-md: 0 4px 6px -2px rgba(16, 24, 40, .05), 0 12px 16px -4px rgba(16, 24, 40, .10);
}
:root[data-theme="dark"] {
  --shadow-sm: 0 1px 2px rgba(0, 0, 0, .40), 0 1px 3px rgba(0, 0, 0, .32);
  --shadow-md: 0 4px 6px -2px rgba(0, 0, 0, .36), 0 12px 16px -4px rgba(0, 0, 0, .48);
}

:root {
  --card: var(--surface);
  --line: var(--border);
  --line-2: var(--surface-3);
  --ink: var(--text);
  --ink-2: var(--text-2);
  --ink-3: var(--text-3);
  /* This is the fix for the documented purple/teal split: client_dashboard.html
     used to declare its own --brand:#7c5cf6 (4.45:1 on white — under the 4.5
     text floor) while customer_view.html had already been corrected to teal.
     Both now resolve to the one verified --accent value. */
  --brand: var(--accent);
  --brand-2: var(--accent-hover);
  --brand-soft: var(--accent-soft);
  --brand-ring: var(--accent-soft);
  --ok: var(--status-done);
  --ok-soft: var(--status-done-bg);
  --warn: var(--status-warn);
  --warn-soft: var(--status-warn-bg);
  --danger: var(--status-fail);
  --danger-soft: var(--status-fail-bg);
  /* No canonical equivalent for "info" (dashboard.html has no neutral-blue
     informational role — --status-running is a pipeline-state color, a
     different meaning). Kept as its own literal rather than mis-borrowing
     that token's semantics.

     2026-08-12: #3b82f6 measured 3.68:1 as text on a white card — an AA
     failure, and the only one in this file that had nothing to do with the
     canvas work. It is live: 3 uses in client_dashboard.css and 3 in
     customer_view.css. Being outside the two theme blocks, it also had no
     dark value at all, so the same blue was painting onto dark cards at
     4.50:1 / 4.09:1. Darkened for light, and given a real dark override
     below. Hue held to the degree in both directions. */
  --info: #1a6df4;         /* 4.63:1 on a white card; white-on-it also 4.63:1 */
  --info-soft: #e8f0ff;    /* tint fill only — never carries text */
}

/* --info is declared in the alias block above, which sits outside both theme
   blocks, so it needs its own dark rule rather than a line in :root[dark]. */
:root[data-theme="dark"] {
  --info: #4d8df7;         /* 4.63:1 worst-case across bg / surface / surface-2 */
  --info-soft: color-mix(in srgb, var(--info) 16%, var(--surface));
}

/* ── Configurable dashboard widgets (shared/ea-dashboard.js) ─────────────────
   Every value below resolves through the tokens above, so a widget rendered on
   the agency surface and the same widget on the customer portal inherit their
   theme from whichever page mounted them rather than carrying a second
   palette. Nothing here declares a literal colour — and, since 2026-08-18,
   nothing declares a literal size either.

   It used to. This block shipped with seven off-ramp sizes — 12, 28, 13, 12,
   12.5, 13, 11.5 — in the same file that DEFINES --fs-100..--fs-500 and never
   once used them. DESIGN.md records that the five-step ramp exists to collapse
   26 ad-hoc sizes; a new block reintroducing seven is how that count climbs
   back. Every value below had a step already named for its exact role:
   micro labels and deltas are --fs-100, unit/body/list text is --fs-200, and
   the KPI numeral is --fs-500, which the ramp literally calls "KPI numerals".
   Sizes moved by 1-4px as a result. That is the ramp being applied, not a
   redesign. */
.ea-widget{background:var(--surface);border:1px solid var(--border);
  border-radius:var(--r-lg,12px);padding:var(--sp-4);display:flex;
  flex-direction:column;gap:var(--sp-2);min-width:0}
.ea-widget-head{display:flex;align-items:baseline;justify-content:space-between;gap:var(--sp-2)}
.ea-widget-title{margin:0;font-size:var(--fs-100);font-weight:600;color:var(--text-3);
  letter-spacing:.04em}
.ea-kpi-value{font-size:var(--fs-500);font-weight:800;letter-spacing:-.02em;line-height:1.1;
  color:var(--text);font-variant-numeric:tabular-nums}
.ea-kpi-unit{font-size:var(--fs-200);font-weight:600;color:var(--text-3);margin-inline-start:6px}
.ea-kpi-delta{font-size:var(--fs-100);color:var(--text-3);font-variant-numeric:tabular-nums}
.ea-widget-note{margin:0;font-size:var(--fs-200);line-height:1.5;color:var(--text-3)}
.ea-widget-note.is-error{color:var(--status-fail)}
.ea-widget-note.is-stale{color:var(--status-warn)}
.ea-widget-note.is-unavailable{color:var(--text-3)}
.ea-widget.is-unavailable{opacity:.62;border-style:dashed}
.ea-widget-list{list-style:none;margin:0;padding:0;display:flex;flex-direction:column;gap:6px}
.ea-widget-list li{display:flex;flex-direction:column}
.ea-widget-list b{font-size:var(--fs-200)}
.ea-widget-list small{font-size:var(--fs-100);color:var(--text-3)}

/* Visually hidden, still announced — same definition dashboard.html already
   carries inline; hoisted here so customer_view.html can share it instead of
   maintaining a second copy. Not display:none, which removes it from the
   accessibility tree too. */
.sr-only{
  position:absolute;width:1px;height:1px;padding:0;margin:-1px;
  overflow:hidden;clip:rect(0 0 0 0);clip-path:inset(50%);white-space:nowrap;border:0;
}
@media(max-width:760px){.ea-grid > .ea-widget{grid-column:1/-1!important}}
