/* ==========================================================================
   slab-tokens.css — the shared token layer light mode switches.
   GENERATED, do not hand-edit: the values are copied verbatim from
   public/design-system.css (the 52 semantic ROLES) and from DSPAL in
   public/design-system.js (the 14 palette families x 12 stops).
   Regenerate rather than edit; design-system.* is the source of truth.

   TWO LAYERS, IDENTICAL TOKEN NAMES
     :root                      -> the dark values (Slab's default today)
     html[data-theme="light"]   -> the light values
   Every name in one layer exists in the other, so a theme flip is a pure
   token swap and nothing falls back to an inherited dark value.

   NAMING
     Roles  --<role>            e.g. --surface-card, --text-muted, --accent-red
            Semantic. Prefer these in page CSS: they carry the design intent
            and they are what the design system audits.
     Ramps  --<family>-<stop>   e.g. --green-700, --blue-400, --n-950
            One token per palette family + stop. Family = the DSPAL family
            name lowercased with spaces removed; "Neutral Cool" is shortened
            to n because it is the workhorse and appears constantly.
            Prefixes: n green blue amber red teal cyan indigo violet magenta
                      pink orange yellow lime
            Stops: 25 50 100 200 300 400 500 600 700 800 900 950 (12 each).
            Reach for a ramp token only when no role fits.

   INK AND FILL — the two rules that make light mode work
     --ink-<family>-<stop>    a pale ramp stop used as TEXT.  Dark = the stop,
                              byte for byte; light = a deep ink of that hue.
     --fill-<family>-<stop>   a near-black tinted stop used as a CHIP SURFACE.
                              Dark = the stop; light = the pale wash of that hue.
     The BARE ramp token is for the thing that must NOT invert — a photo scrim,
     a filled action, a printed paper element.

   TINTS — the ~750 rgba() washes carry their alpha at the call site and take
     their colour from a token:
         color-mix(in srgb, var(--accent-amber) 14%, transparent)
     which is exactly rgba(219,148,44,.14) in dark and re-tints itself in light.
     --raise-ink / --shadow-ink / --scrim-ink carry the washes with no accent
     of their own (a white lift, a black shadow, the veil behind a modal).

   COUNTS  93 roles + 168 ramps = 261 tokens in each layer.

   USAGE   <link rel="stylesheet" href="/slab-tokens.css">
           <script src="/slab-theme.js"></script>   (stamps data-theme pre-paint)
   ========================================================================== */

:root{
  /* ---- semantic roles (dark) ---- */
  --app-bg:#101520;
  --surface-card:#141D2E;
  --surface-head:#141D2E;
  --surface-sunken:#131628;
  --surface-menu:#131628;
  --surface-rail:#0C0D16;
  --surface-topbar:#0C0D16;
  --surface-hover:#1E2A3F;
  --surface-toast:#152624;
  --border:#1E2A3F;
  --border-strong:#2A3A52;
  /* ── THE CHART PALETTE ──────────────────────────────────────────────────────
     Added 2026-08-29. The vendor chart painted from the SEMANTIC accents (VEND_TOKENS in
     index.html), which is why its light values could not simply be brightened:
     --accent-amber is also a chip background carrying white text, and --action-primary is
     the primary button fill carrying white text in 69 places. Bright enough to read as a
     chart makes the TEXT on them fail AA. One token, two jobs, two contrast floors — the
     same shape as --text-faint, which had four.

     So charts get their own roles. DARK values are byte-identical to the semantic tokens
     they replace, so dark mode does not move. LIGHT values are built to the GRAPHICS floor
     (~3:1, WCAG 1.4.11) instead of the text floor the old ones were wrongly built to — which
     is what made them dark and muddy at 4.6-8.7:1 on white.

     NOTHING HERE CARRIES TEXT. If that ever changes, re-measure before reusing one. */
  --chart-1:#DB563D;
  --chart-2:#2E5E4E;
  --chart-3:#DB942C;
  --chart-4:#2585B6;
  --chart-5:#52A882;
  --chart-6:#7BACD9;
  /* --border-hover and --fill-faint were split OUT of --text-faint on 2026-08-28.
     One token was doing four jobs: 99 text colours, 36 hover borders, 7 graphic fills
     and a toggle background. Text needs 4.5:1 and the other three do not, so the single
     token could not satisfy text without dragging every border and chart bar up with it.
     These two hold --text-faint's ORIGINAL value, so borders and fills did not move. */
  --border-hover:#435471;
  --fill-faint:#435471;
  --border-faint:#141D2E;
  --border-rail:#141D2E;
  --border-menu:#26344A;
  --text:#E0E6F2;
  --text-strong:#F0F4FF;
  --text-bright:#ECF2FE;
  --text-2:#C0CAE2;
  --text-3:#9DACCB;
  --text-muted:#7A8AAA;
  /* Raised from #5A6880 on 2026-08-29, for TWO reasons at once.
     1. It was 2.99:1 on --surface-card and failed WCAG AA for text, across 311 uses.
     2. Raising --text-faint to #7188AD (4.68:1) earlier the same day made faint BRIGHTER
        than dim, which sits ABOVE it in the ramp — the ramp ran backwards at that step.
     #7A89A2 is 4.76:1: above the floor, and it lands between --text-muted (4.85) and
     --text-faint (4.68) so the order is real again. The gap is deliberately tiny because
     the 4.5 floor leaves almost no room below --text-3 — three legible quiet tiers cannot
     be far apart on a dark card. Light was already 5.64:1 and is untouched. */
  --text-dim:#7A89A2;
  --text-faint:#7188AD;   /* was #435471 = 2.21:1 on --surface-card, FAILED WCAG AA.
                             #7188AD is 4.68:1 there and stays dimmer than --text-muted
                             (4.85:1), so the ramp keeps its order. */
  --text-ghost:#283044;
  --on-action:#FFFFFF;
  --action-primary:#2E5E4E;
  --action-primary-hover:#30755D;
  --accent-green:#52A882;
  --accent-green-bright:#6ED4A0;
  --accent-green-max:#B2F0D1;
  --accent-blue:#7BACD9;
  --accent-blue-deep:#5B9EC9;
  --accent-amber:#DB942C;
  --accent-amber-light:#E0A94A;
  --accent-red:#E06B54;
  --accent-red-deep:#DD5E46;   /* 4.63:1 on the card. Was #DB563D = 4.35 — a real AA miss on
     .gc-today-lbl, the gantt's TODAY marker, which is 14px/700 and therefore NOT WCAG
     'large text' (that needs 18.66px at bold), so 4.5 applies and 4.35 did not clear it.
     Found 2026-08-29. The chart series keeps the ORIGINAL #DB563D — it is --chart-1 now,
     a separate role with a graphics floor, which is exactly why the split was worth it. */
  --accent-red-bright:#FF9B82;
  --accent-red-mute:#C4705C;
  --accent-neutral:#9DACCB;
  --accent-violet:#A68ED9;
  --brand-gold:#D6B07B;
  --tint-green:rgba(46,94,78,.28);
  --tint-amber:rgba(219,148,44,.16);
  --tint-red:rgba(224,107,84,.18);
  --tint-blue:rgba(37,133,182,.18);
  --tint-neutral:rgba(122,138,170,.16);
  --tint-lost:rgba(219,86,61,.10);
  --raise-1:rgba(255,255,255,.03);
  --raise-2:rgba(255,255,255,.05);
  --raise-3:rgba(255,255,255,.09);
  --ring-primary:rgba(46,94,78,.5);
  --shadow-md:0 10px 30px rgba(0,0,0,.4);
  --shadow-lg:0 14px 34px rgba(0,0,0,.5);

  /* ---- 168 palette ramp stops (dark) ---- */
  /* Neutral Cool — --n-* */
  --n-25:#F0F4FF;
  --n-50:#ECF2FE;
  --n-100:#E0E6F2;
  --n-200:#C0CAE2;
  --n-300:#9DACCB;
  --n-400:#7A8AAA;
  --n-500:#6B7D9E;
  --n-600:#5A6880;
  --n-700:#435471;
  --n-800:#2A3A52;
  --n-900:#1E2A3F;
  --n-950:#141D2E;

  /* Green — --green-* */
  --green-25:#D3FDE7;
  --green-50:#B2F0D1;
  --green-100:#6ED4A0;
  --green-200:#52C591;
  --green-300:#52A882;
  --green-400:#3F9B76;
  --green-500:#2A7B5D;
  --green-600:#30755D;
  --green-700:#2E5E4E;
  --green-800:#005334;
  --green-900:#004125;
  --green-950:#002D17;

  /* Blue — --blue-* */
  --blue-25:#DAF7FF;
  --blue-50:#BCE8FF;
  --blue-100:#A0D4FB;
  --blue-200:#83C0EA;
  --blue-300:#7BACD9;
  --blue-400:#5B9EC9;
  --blue-500:#2585B6;
  --blue-600:#0072A1;
  --blue-700:#00608A;
  --blue-800:#004E73;
  --blue-900:#003B5C;
  --blue-950:#002944;

  /* Amber — --amber-* */
  --amber-25:#FFEBB8;
  --amber-50:#FFD78E;
  --amber-100:#FFC16A;
  --amber-200:#E0A94A;
  --amber-300:#DB942C;
  --amber-400:#CA8100;
  --amber-500:#B56E00;
  --amber-600:#9D5C00;
  --amber-700:#864A00;
  --amber-800:#6F3900;
  --amber-900:#5A2A00;
  --amber-950:#441A00;

  /* Red — --red-* */
  --red-25:#FFE3D3;
  --red-50:#FFCCB6;
  --red-100:#FFB39B;
  --red-200:#FF9B82;
  --red-300:#FE8066;
  --red-400:#E06B54;
  --red-500:#DB563D;
  --red-600:#BE3F2C;
  --red-700:#A32E1E;
  --red-800:#891F13;
  --red-900:#6E1108;
  --red-950:#520300;

  /* Teal — --teal-* */
  --teal-25:#F7FAFA;
  --teal-50:#EDF5F3;
  --teal-100:#DCECEA;
  --teal-200:#BCE0DB;
  --teal-300:#97D3CA;
  --teal-400:#6BC0B4;
  --teal-500:#4BA599;
  --teal-600:#3B8177;
  --teal-700:#2C6059;
  --teal-800:#20403C;
  --teal-900:#152624;
  --teal-950:#0C1514;

  /* Cyan — --cyan-* */
  --cyan-25:#F7FAFA;
  --cyan-50:#EDF3F5;
  --cyan-100:#DBEAED;
  --cyan-200:#B8DAE3;
  --cyan-300:#90C9D8;
  --cyan-400:#5FB2C7;
  --cyan-500:#3C95AC;
  --cyan-600:#2F7586;
  --cyan-700:#235764;
  --cyan-800:#1B3B43;
  --cyan-900:#132328;
  --cyan-950:#0C1416;

  /* Indigo — --indigo-* */
  --indigo-25:#F7F8FA;
  --indigo-50:#EDEEF5;
  --indigo-100:#DBDEED;
  --indigo-200:#B8BFE3;
  --indigo-300:#909CD8;
  --indigo-400:#5F70C7;
  --indigo-500:#3C4FAC;
  --indigo-600:#2F3E86;
  --indigo-700:#232E64;
  --indigo-800:#1B2243;
  --indigo-900:#131628;
  --indigo-950:#0C0D16;

  /* Violet — --violet-* */
  --violet-25:#F8F7FA;
  --violet-50:#EFEDF5;
  --violet-100:#E1DBEE;
  --violet-200:#C5B7E3;
  --violet-300:#A68ED9;
  --violet-400:#7F5CC9;
  --violet-500:#5F3AAE;
  --violet-600:#4A2D88;
  --violet-700:#372265;
  --violet-800:#281B44;
  --violet-900:#191328;
  --violet-950:#0F0B16;

  /* Magenta — --magenta-* */
  --magenta-25:#FAF7FA;
  --magenta-50:#F5EDF5;
  --magenta-100:#ECDCEC;
  --magenta-200:#E0BBE0;
  --magenta-300:#D394D3;
  --magenta-400:#C065C0;
  --magenta-500:#A543A5;
  --magenta-600:#813581;
  --magenta-700:#602760;
  --magenta-800:#401E40;
  --magenta-900:#261426;
  --magenta-950:#150C15;

  /* Pink — --pink-* */
  --pink-25:#FAF7F9;
  --pink-50:#F5EDF1;
  --pink-100:#EEDAE4;
  --pink-200:#E4B6CC;
  --pink-300:#DB8DB1;
  --pink-400:#CB5A8F;
  --pink-500:#B03870;
  --pink-600:#8A2B57;
  --pink-700:#672041;
  --pink-800:#451A2E;
  --pink-900:#28121D;
  --pink-950:#160B10;

  /* Orange — --orange-* */
  --orange-25:#FBF8F6;
  --orange-50:#F7F0EB;
  --orange-100:#F1E2D8;
  --orange-200:#EAC7B0;
  --orange-300:#E5AA82;
  --orange-400:#DA844B;
  --orange-500:#C16527;
  --orange-600:#964F1F;
  --orange-700:#703B17;
  --orange-800:#4A2A14;
  --orange-900:#2B1A0F;
  --orange-950:#170F0A;

  /* Yellow — --yellow-* */
  --yellow-25:#FFF2DD;
  --yellow-50:#FFE4C8;
  --yellow-100:#FFD6A9;
  --yellow-200:#FFC786;
  --yellow-300:#FEB95D;
  --yellow-400:#F6AD4B;
  --yellow-500:#DB942C;
  --yellow-600:#BB7800;
  --yellow-700:#965F00;
  --yellow-800:#754700;
  --yellow-900:#553000;
  --yellow-950:#3A1800;

  /* Lime — --lime-* */
  --lime-25:#F9FAF7;
  --lime-50:#F1F5ED;
  --lime-100:#E5EDDB;
  --lime-200:#CFE2B9;
  --lime-300:#B6D691;
  --lime-400:#96C461;
  --lime-500:#78A93F;
  --lime-600:#5D8431;
  --lime-700:#466324;
  --lime-800:#30421C;
  --lime-900:#1E2713;
  --lime-950:#11150C;
  --text-caption:#6B7D9E;
  --text-on-menu:#DBDEED;
  --surface-tooltip:#1B2243;
  --text-on-tooltip:#DBDEED;

  /* ---- INK ROLES: a pale ramp stop used as TEXT ----------------------------
     A ramp stop is a PALETTE value, and the palette does not invert: --blue-100
     is pale in both layers. That is right for a chip fill and wrong for a word.
     Every one of these was measured at 1.1-2.5:1 on a light page.
     RULE: pale stop as a background -> keep the ramp token.
           pale stop as text/icon    -> use the --ink-* with the same name.
     The dark value is the ramp stop, byte for byte, so dark mode does not move.
     The light value is a deep ink of the same hue (>=5:1 on white). */
  --ink-blue-100:#A0D4FB;
  --ink-blue-200:#83C0EA;
  --ink-red-25:#FFE3D3;
  --ink-red-50:#FFCCB6;
  --ink-red-100:#FFB39B;
  --ink-amber-25:#FFEBB8;
  --ink-amber-50:#FFD78E;
  --ink-amber-100:#FFC16A;
  --ink-yellow-50:#FFE4C8;
  --ink-yellow-100:#FFD6A9;
  --ink-yellow-200:#FFC786;
  --ink-yellow-400:#F6AD4B;
  --ink-yellow-600:#BB7800;
  --ink-orange-100:#F1E2D8;
  --ink-orange-200:#EAC7B0;
  --ink-orange-300:#E5AA82;
  --ink-teal-200:#BCE0DB;
  --ink-teal-300:#97D3CA;
  --ink-teal-400:#6BC0B4;
  --ink-teal-500:#4BA599;
  --ink-lime-50:#F1F5ED;
  --ink-lime-100:#E5EDDB;
  --ink-violet-25:#F8F7FA;
  --ink-pink-300:#DB8DB1;

  /* Text on a filled AMBER control — the mirror of --on-action. Dark ink on the
     dark-mode amber, white on the light-mode amber, which is much darker. */
  --on-amber:#132328;


  /* ---- TINT BASES: the 470 rgba() washes ----------------------------------
     Slab carries hundreds of accent washes at alphas no role names
     (rgba(46,94,78,·) alone at 12 different alphas). Rather than a role per
     alpha, the alpha stays at the call site and the COLOUR comes from a token:
         color-mix(in srgb, var(--action-primary) 14%, transparent)
     which is exactly rgba(46,94,78,.14) in dark and re-tints itself in light.
     These three carry the washes that have no accent of their own. */
  --raise-ink:#FFFFFF;                 /* a white lift; a dark one on a light page */
  --shadow-ink:#000000;                /* light-mode shadows carry their own damping */
  --scrim-ink:#0C0D16;                 /* the wash behind a modal */

  /* ---- FILL ROLES: a ramp stop used as a CHIP SURFACE ----------------------
     The mirror of --ink-*. A near-black tinted chip (#2B1A0F, #170F0A, …) is an
     accent wash on a dark page; on a light page it has to become the pale wash of
     the same hue or it stays a black rectangle. Dark = the stop, byte for byte.
     RULE: ramp stop as text -> --ink-*; as a surface -> --fill-*; the bare ramp
     token only where the thing must NOT invert (a photo scrim, a filled action). */
  --fill-teal-950:#0C1514;
  --fill-lime-950:#11150C;
  --fill-orange-900:#2B1A0F;
  --fill-orange-950:#170F0A;
  --fill-pink-900:#28121D;
  --fill-pink-950:#160B10;
  /* A finished bar on the schedule strip, and its washed-out label. */
  --fill-bar-done:#1E2A3F;
  --on-bar-dim:rgba(255,255,255,.5);
  /* the label on a DISABLED filled action — white-at-52% on the dark fill,
     a muted ink on the light one, where a white wash would vanish. */
  --on-action-dim:rgba(255,255,255,.52);
  /* The sign-out avatar fill. A ROLE, not a ramp stop: it must stay dark enough for the
     white `logout` glyph in BOTH themes. --fill-pink-900's light stop is #FBE2DC, a pale
     pink that would hide white ink. Light is a touch richer than dark on purpose — a
     near-black square reads oddly on a white menu. */
  --surface-signout:#28121D;
}

/* THE FLIP. One attribute on <html>. Same 220 names, light values. */
html[data-theme="light"]{
  /* ---- semantic roles (light) ---- */
  --app-bg:#F3F5F8;
  --surface-card:#FFFFFF;
  --surface-head:#EDF0F5;
  --surface-sunken:#E8ECF3;
  --surface-menu:#FFFFFF;
  --surface-rail:#EDF0F5;
  --surface-topbar:#FFFFFF;
  --surface-hover:#EEF3F9;
  --surface-toast:#DDEEE6;
  --border:#D9DFE9;
  --border-strong:#C3CBD9;
  /* chart palette — see the note in the dark layer. Built to the ~3:1 GRAPHICS floor. */
  --chart-1:#D3391A;
  --chart-2:#32A177;
  --chart-3:#CB7E02;
  --chart-4:#4F96C5;
  --chart-5:#2FA276;
  --chart-6:#3795E1;
  --border-hover:#5C6878;
  --fill-faint:#5C6878;
  --border-faint:#E8ECF2;
  --border-rail:#DDE3EC;
  --border-menu:#C3CBD9;
  --text:#141C28;
  --text-strong:#080D16;
  --text-bright:#0A1220;
  --text-2:#2E3A4C;
  --text-3:#3E4A5C;
  --text-muted:#56637A;
  --text-dim:#5B6576;   /* 5.89:1. Was #5D6879 (5.64) which sat 0.03 BELOW --text-faint
                           (5.67) — one unit per channel apart, so light mode had dim and
                           faint as the same colour with the ramp technically inverted.
                           Pre-existing; found 2026-08-29 while fixing the dark ramp. */
  --text-faint:#5C6878;
  --text-ghost:#626D82;
  --on-action:#FFFFFF;
  --action-primary:#24614A;
  --action-primary-hover:#1B5039;
  --accent-green:#1E5540;
  --accent-green-bright:#2E8162;   /* 4.74:1 on white. Was #1E5540 — BYTE-IDENTICAL to
     --accent-green, so in light mode "bright" was not bright and any design using the two
     together collapsed to one colour (found 2026-08-29 when the award confetti, which is
     deliberately two greens, rendered as one in light). Used as `color` 164 times, so it
     stays above the 4.5 text floor rather than going as bright as the dark value. */
  --accent-green-max:#17452F;
  --accent-blue:#1F5C8E;
  --accent-blue-deep:#1B5480;
  --accent-amber:#7E520A;
  --accent-amber-light:#7E520A;
  --accent-red:#B23A22;
  --accent-red-deep:#93301C;
  --accent-red-bright:#93301C;
  --accent-red-mute:#8A5F55;
  --accent-neutral:#4E5A6E;
  --accent-violet:#5B3FA0;
  --brand-gold:#7A5C22;
  --tint-green:#DDEEE6;
  --tint-amber:#FBEFD6;
  --tint-red:#FBE2DC;
  --tint-blue:#DEEAF6;
  --tint-neutral:#E9ECF2;
  --tint-lost:#F1E9E7;
  --raise-1:rgba(16,28,44,.035);
  --raise-2:rgba(16,28,44,.05);
  --raise-3:rgba(16,28,44,.09);
  --ring-primary:rgba(36,97,74,.45);
  --shadow-md:0 10px 30px rgba(16,28,44,.12);
  --shadow-lg:0 14px 34px rgba(16,28,44,.18);

  /* ---- 168 palette ramp stops (light) ---- */
  /* Neutral Cool — --n-* */
  --n-25:#F0F7FF;
  --n-50:#E7ECF3;
  --n-100:#D4DCE8;
  --n-200:#B8C4D6;
  --n-300:#A4B1C6;
  --n-400:#8C97A8;
  --n-500:#7B899F;
  --n-600:#687791;
  --n-700:#54617A;
  --n-800:#42516B;
  --n-900:#2E3A4C;
  --n-950:#141C28;

  /* Green — --green-* */
  --green-25:#E7FBF1;
  --green-50:#DEE5E3;
  --green-100:#C3D6CE;
  --green-200:#A6C2B5;
  --green-300:#8AAE9D;
  --green-400:#6C9B85;
  --green-500:#468066;
  --green-600:#377057;
  --green-700:#2E5E4E;
  --green-800:#12523A;
  --green-900:#013D28;
  --green-950:#002717;

  /* Blue — --blue-* */
  --blue-25:#E5EFF6;
  --blue-50:#CEE0EE;
  --blue-100:#B4CCE0;
  --blue-200:#9AB9D3;
  --blue-300:#81A6C4;
  --blue-400:#6594B7;
  --blue-500:#3F7BA2;
  --blue-600:#306B91;
  --blue-700:#12658F;
  --blue-800:#004D73;
  --blue-900:#003858;
  --blue-950:#00253F;

  /* Amber — --amber-* */
  --amber-25:#FAF1E2;
  --amber-50:#F3DFC4;
  --amber-100:#E4C9A5;
  --amber-200:#D5B486;
  --amber-300:#C69F68;
  --amber-400:#B58B4A;
  --amber-500:#986D21;
  --amber-600:#8B6118;
  --amber-700:#825600;
  --amber-800:#664100;
  --amber-900:#4D2D00;
  --amber-950:#381A00;

  /* Red — --red-* */
  --red-25:#FFF2EB;
  --red-50:#FAE7E4;
  --red-100:#FCCDC3;
  --red-200:#F6B09F;
  --red-300:#ED917C;
  --red-400:#E1745A;
  --red-500:#C84B31;
  --red-600:#B53921;
  --red-700:#B52711;
  --red-800:#930C01;
  --red-900:#730000;
  --red-950:#4D0000;

  /* Teal — --teal-* */
  --teal-25:#D3FFF6;
  --teal-50:#B7EDE2;
  --teal-100:#9CDBCE;
  --teal-200:#82C9BB;
  --teal-300:#66B5A6;
  --teal-400:#4CA292;
  --teal-500:#2E8E7F;
  --teal-600:#1B7B6C;
  --teal-700:#16685B;
  --teal-800:#0E4E44;
  --teal-900:#073830;
  --teal-950:#03241F;

  /* Cyan — --cyan-* */
  --cyan-25:#CDFFFF;
  --cyan-50:#AFEDF8;
  --cyan-100:#91DAE8;
  --cyan-200:#73C8D7;
  --cyan-300:#51B5C5;
  --cyan-400:#29A1B2;
  --cyan-500:#008EA0;
  --cyan-600:#007988;
  --cyan-700:#006674;
  --cyan-800:#004C57;
  --cyan-900:#003740;
  --cyan-950:#00232A;

  /* Indigo — --indigo-* */
  --indigo-25:#EEF5FF;
  --indigo-50:#D8E1FF;
  --indigo-100:#C2CDFF;
  --indigo-200:#ACB9F1;
  --indigo-300:#95A5E1;
  --indigo-400:#7F91D0;
  --indigo-500:#697EBE;
  --indigo-600:#546BAA;
  --indigo-700:#415994;
  --indigo-800:#2B4378;
  --indigo-900:#172F5C;
  --indigo-950:#041D41;

  /* Violet — --violet-* */
  --violet-25:#FFF0FF;
  --violet-50:#F0DAFF;
  --violet-100:#DEC5F7;
  --violet-200:#CCB0E8;
  --violet-300:#B89BD7;
  --violet-400:#A586C6;
  --violet-500:#9173B4;
  --violet-600:#7E60A0;
  --violet-700:#6A4E8B;
  --violet-800:#52396F;
  --violet-900:#3B2655;
  --violet-950:#26153B;

  /* Magenta — --magenta-* */
  --magenta-25:#FFF0FE;
  --magenta-50:#FFD3F4;
  --magenta-100:#FABDE2;
  --magenta-200:#EBA7D1;
  --magenta-300:#D990BE;
  --magenta-400:#C87BAC;
  --magenta-500:#B56699;
  --magenta-600:#A15286;
  --magenta-700:#8B4072;
  --magenta-800:#6F2B59;
  --magenta-900:#541A42;
  --magenta-950:#3A092C;

  /* Pink — --pink-* */
  --pink-25:#FFF1F7;
  --pink-50:#FFD5E3;
  --pink-100:#FFBCD0;
  --pink-200:#F3A6BC;
  --pink-300:#E28FA8;
  --pink-400:#D07994;
  --pink-500:#BD6481;
  --pink-600:#A9506E;
  --pink-700:#923E5C;
  --pink-800:#752946;
  --pink-900:#591731;
  --pink-950:#3E071E;

  /* Orange — --orange-* */
  --orange-25:#FFF2E9;
  --orange-50:#FFDACA;
  --orange-100:#FFC0A3;
  --orange-200:#FAA883;
  --orange-300:#E9916A;
  --orange-400:#D67C53;
  --orange-500:#C2673D;
  --orange-600:#AC542B;
  --orange-700:#95421C;
  --orange-800:#762E0B;
  --orange-900:#5A1C00;
  --orange-950:#400B00;

  /* Yellow — --yellow-* */
  --yellow-25:#FFF2DD;
  --yellow-50:#FFE4C8;
  --yellow-100:#FFD6A9;
  --yellow-200:#FFC786;
  --yellow-300:#FEB95D;
  --yellow-400:#F6AD4B;
  --yellow-500:#DB942C;
  --yellow-600:#BB7800;
  --yellow-700:#965F00;
  --yellow-800:#754700;
  --yellow-900:#553000;
  --yellow-950:#3A1800;

  /* Lime — --lime-* */
  --lime-25:#F0FCD1;
  --lime-50:#DAE9B6;
  --lime-100:#C5D69C;
  --lime-200:#B0C383;
  --lime-300:#9AAF6A;
  --lime-400:#849C52;
  --lime-500:#70893C;
  --lime-600:#5C7629;
  --lime-700:#4A6319;
  --lime-800:#344B07;
  --lime-900:#233600;
  --lime-950:#172200;
  --text-caption:#616E88;
  --text-on-menu:#2E3A4C;
  --surface-tooltip:#2E3A4C;
  --text-on-tooltip:#E7ECF3;

  /* ---- INK ROLES (light): a deep ink of the stop's own hue ---- */
  --ink-blue-100:#1F5C8E;
  --ink-blue-200:#1B5480;
  --ink-red-25:#93301C;
  --ink-red-50:#A32E1E;
  --ink-red-100:#A5341E;
  --ink-amber-25:#664100;
  --ink-amber-50:#825600;
  --ink-amber-100:#8A5A0B;
  --ink-yellow-50:#754700;
  --ink-yellow-100:#825600;
  --ink-yellow-200:#8A5A0B;
  --ink-yellow-400:#965F00;
  --ink-yellow-600:#825600;
  --ink-orange-100:#762E0B;
  --ink-orange-200:#95421C;
  --ink-orange-300:#AC542B;
  --ink-teal-200:#16685B;
  --ink-teal-300:#16685B;
  --ink-teal-400:#16685B;
  --ink-teal-500:#16685B;
  --ink-lime-50:#4A6319;
  --ink-lime-100:#466324;
  --ink-violet-25:#5B3FA0;
  --ink-pink-300:#923E5C;
  --on-amber:#FFFFFF;
  --raise-ink:#101C2C;
  --shadow-ink:rgba(16,28,44,.35);
  --scrim-ink:rgba(20,28,40,.64);

  /* ---- FILL ROLES (light): the pale wash of the same hue ---- */
  --fill-teal-950:#E3F2EF;
  --fill-lime-950:#EDF3E3;
  --fill-orange-900:#FBEFD6;
  --fill-orange-950:#FBE2DC;
  --fill-pink-900:#FBE2DC;
  --fill-pink-950:#FBE2E8;
  --fill-bar-done:#54617A;
  --on-bar-dim:rgba(255,255,255,.85);
  --on-action-dim:#454F62;
  --surface-signout:#752946;
}


/* ── THE WORDMARK IN LIGHT MODE ─────────────────────────────────────────────
   `.sb-brand{color:#D6B07B}` and `.sb-sq{background:#2E5E4E}` stay LITERAL in every
   page: takeoff-export.test.js §13 and slab-doc-pdf.test.js parse those exact rules
   with a `#[0-9A-Fa-f]{6}` regex to prove the PDF module's hard-coded RGB still
   matches the app. Tokenising them breaks the export guard.

   So light mode overrides them here instead of rewriting them there. The tan reads
   1.5:1 on a light rail — invisible; --brand-gold's own light value is 5.4:1. */
html[data-theme="light"] .sb-brand,
html[data-theme="light"] .brand,
html[data-theme="light"] .top-name,
html[data-theme="light"] .sg-wm,
html[data-theme="light"] .ll-brand{color:var(--brand-gold);}


/* ══ THE KEYBOARD FOCUS RING ════════════════════════════════════════════════
   ONE rule, one file, for the whole app.

   WHY HERE. It has to be a render-blocking stylesheet that every page loads, and
   slab-tokens.css is the only file that qualifies: slab-header.css is absent from
   field/schedule/privacy/terms, slab-critical.css is absent from punchlist/field/
   schedule, and nav.js is a SCRIPT — a focus ring that disappears when the script
   fails is exactly the kind of thing this ring exists to catch. The audit's own
   instruction ("one rule, in the shared stylesheet") is only satisfiable here.

   MEASURED, 28 Aug 2026. --accent-green-bright is #6ED4A0 in dark and #1E5540 in
   light. Against every ground it can land on:
     dark   app-bg 10.08  card 9.31  rail 10.68  menu 9.88  hover/border 7.95
     light  app-bg  7.92  card 8.65  head/rail 7.57
   Worst case 7.57:1, against a 3:1 floor (WCAG 1.4.11). outline-offset:2px means
   the ring is separated from the control by 2px OF THE GROUND on both sides, so it
   is never judged against a filled button's own fill (which in light mode is
   #24614A — 1.19:1 against the ring, and would fail if the ring sat on it).

   :focus-visible, NOT :focus — a mouse click leaves nothing behind, which is the
   whole reason 87 `outline:none` rules were ever written.

   The [tabindex="-1"] exclusion keeps the ring off things that are focused
   programmatically rather than reached — chiefly the .csel enhancer's hidden
   native <select>, which is opacity:0 and would draw a ring around nothing.

   SPECIFICITY IS DELIBERATELY LOW (0,2,0). Every one of the app's twelve existing
   custom rings — the red delete rings, the three modal closes, the doc card, the
   review card, the three checkboxes — matches at (0,2,0) or higher in a stylesheet
   that loads after this one, so each keeps its own colour and offset. This rule is
   the floor under them, not a replacement for them. */
:focus-visible:not([tabindex="-1"]){
  outline:2px solid var(--accent-green-bright);
  outline-offset:2px;
  box-shadow:0 0 0 4px var(--ring-primary);
}

/* THE TWO PLACES A 2px OUTSET RING IS CLIPPED, so it goes inside instead. The rail
   scrolls (overflow-y:auto) and its items are full-bleed, so an outset ring loses its
   left and right edges; a table row's outset ring is painted over by the row under it.
   Inside, it is still 2px of #6ED4A0 / #1E5540 on the same grounds measured above —
   verified live on the rail, where "Budget" draws a complete, unclipped box.

   The .csel MENU is not in this list on purpose. Its rows are driven by
   aria-activedescendant, so focus never leaves the button and an option never matches
   :focus-visible; the highlight there is .csel-opt.active, which nav.js now sets. */
.ni:focus-visible,
.slab-table tbody tr:focus-visible{ outline-offset:-2px; }

/* ══ HIT TARGETS ════════════════════════════════════════════════════════════
   WCAG 2.5.8 (AA) asks for 24 x 24 CSS px. Slab's baseline is 31-37px; the
   handful of controls below ship smaller, and every one of them is small for a
   layout reason — a 21px thumbnail action, a 13px number step inside a 37px
   field. So the GLYPH does not move: a transparent ::before is stretched to the
   floor around it, which is a real pointer target because the pseudo-element
   belongs to the button and inherits its click.

   MEASURED 28 Aug 2026, both from the declared boxes and live in the browser. Eight
   kinds of control are under the floor. They are named HERE, not in eight page files,
   for the same reason the focus ring is: the .btn base is already copied byte-identical
   into nine files and that is how its disabled opacity became ten different values. One
   list is one thing to audit and one thing to test.

     .num-step         22 x 13   bids — the smallest interactive target in Slab
     .ttog             ~50 x 21  index — the timeframe segmented control
     .pcard-del        28 x 22   punchlist — small AND destructive, the worst pair
     .thumb .pact      21 x 21   takeoff — the sheet actions
     .thumb .pgrip     21 x 21   takeoff — the sheet drag handle
     .chk              ~18 tall  bids, projects, settings, v2-budget
     .se-chk/.te-chk   17 / 15   takeoff
     .vis-chk          19-20     v2-estimate, v2-proposal, v2-invoices, v2-change-orders
     .cp-active        ~18 tall  directory

   THE CHECKBOXES ARE EXPANDED AT THE LABEL, not at the box. A 14px tick box inside a
   label that wraps the words is already a big target horizontally; what fails is the
   label's HEIGHT (font 11-13px on a row with no min-height). Growing the box would
   leave the target the same size. The ::before belongs to the label, so a press
   anywhere in it toggles the input exactly as a press on the words does.

   .sdc-del is the same defect in the shared document card and is fixed in
   slab-doc-card.js, next to the rest of that component's CSS.

   NOT under the floor, though the audit listed it: the row deletes at 26-28px
   (.te-del, .crew-edit, .crew-shift, .cp-edit, .cp-shift, .ph-x, .dp-x, .rep-x,
   .pla-del, .pla-color). 26 >= 24. Left alone rather than padded for a number. */
.slab-hit,
.ttog,.num-step,.pcard-del,.thumb .pact,.thumb .pgrip,
.chk,.se-chk,.te-chk,.vis-chk,.cp-active{position:relative;}

.slab-hit::before,
.ttog::before,.pcard-del::before,.thumb .pact::before,.thumb .pgrip::before,
.chk::before,.se-chk::before,.te-chk::before,.vis-chk::before,.cp-active::before{
  content:'';position:absolute;left:50%;top:50%;
  transform:translate(-50%,-50%);width:100%;height:100%;
  min-width:24px;min-height:24px;}

/* THE ONE THAT CANNOT BE CENTRED. Two 13px steps stacked 2px apart inside a 37px
   field: two 24px boxes centred on them would overlap by 20px and the top one would
   eat the bottom one's presses. So each grows AWAY from the other — the raise button
   upward, the lower button downward — which leaves 2px between the two hit areas and
   satisfies 2.5.8 without either one moving on screen. */
.num-steps .num-step::before{content:'';position:absolute;left:50%;
  transform:translateX(-50%);width:100%;min-width:24px;height:24px;}
.num-steps .num-step:first-child::before{bottom:0;}
.num-steps .num-step:last-child::before{top:0;}
