/* =============================================================================
   qs-tokens.css — QService AdminPanel design token layer
   =============================================================================
   Added as part of the 2026-08 UI/UX audit remediation (finding L2 — "no shared
   design definitions"; also underlies L1/G1/G2/L5).

   LOAD ORDER: this file must be the FIRST stylesheet in <head> (see _Layout.cshtml
   and _LayoutAccount.cshtml). It declares custom properties only — no selector
   here paints anything — so loading it first is provably safe: nothing can
   regress by adding tokens nobody consumes yet.

   WHY TOKENS SURVIVE THE !important WAR (site.css/qs-theme-dark.css have ~1191
   !important declarations combined): custom properties are NOT subject to
   !important the way regular property values are. `background: var(--qs-primary)
   !important` still resolves --qs-primary from whichever :root / [data-theme]
   block declared it last in the cascade. So this file can sit underneath every
   !important in the app with zero conflict — the !important war is about
   *properties*, never about *values*.

   THREE LAYERS:
     1. Primitives   (--qs-c-*)   raw color ramps. Never referenced by components.
     2. Semantic     (--qs-*)     same NAMES in light and dark — this is the only
                                   layer components should ever use.
     3. Dimensions   (--qs-space-*, --qs-radius-*, --qs-fs-*, --qs-z-*, ...)
                                   theme-agnostic.

   Seeded from the four competing "primary blues" already live in the codebase
   (#173fa6 x38, #2150b8 x22, #1976d2 x42, #2196f3 x37) so nothing is invented
   ahead of QService's approved designs — when those arrive, swap layer 2's
   values and every component that used var() moves with zero code changes.
   ========================================================================== */

/* ---------- Layer 1: primitives (never used directly by components) ---------- */
:root {
    --qs-c-white: #ffffff;
    --qs-c-black: #000000;

    --qs-c-blue-900: #0f2e82;
    --qs-c-blue-700: #173fa6; /* incumbent --qs-primary, kept as the seed */
    --qs-c-blue-600: #2150b8; /* incumbent --qs-primary-600 */
    --qs-c-blue-500: #1976d2;
    --qs-c-blue-400: #2196f3;
    --qs-c-blue-100: #e7edfb;

    --qs-c-slate-900: #101a30;
    --qs-c-slate-700: #2c3a4b;
    --qs-c-slate-600: #5b6478;
    --qs-c-slate-400: #8891a3;
    --qs-c-slate-200: #dce3f0;
    --qs-c-slate-100: #ecf1fa;
    --qs-c-slate-50:  #f4f7fc;

    --qs-c-green-700: #1b7f3b;
    --qs-c-green-500: #34d399;
    --qs-c-green-100: #e4f5ef;

    --qs-c-amber-900: #241800;
    --qs-c-amber-500: #ff9800;
    --qs-c-amber-100: #fbf0dc;

    --qs-c-red-700: #c62828;
    --qs-c-red-500: #f87171;
    --qs-c-red-100: #fbe9e9;

    --qs-c-cyan-700: #00707e;
    --qs-c-cyan-500: #38bdf8;
    --qs-c-cyan-100: #e1f1f3;

    /* ------------------------------------------------------------------
       The -200 step: the BORDER on a subtle status chip, one shade stronger than
       the -100 fill it outlines. qs-ops-module.css has referenced these five names
       since it was written, but they were never actually declared here - the rules
       only rendered because each carried a hex fallback:

           border-color: var(--qs-c-green-200, #bfe8cd);

       A `var()` naming an undefined property with no fallback is INVALID AT
       COMPUTED-VALUE TIME: the declaration does not fall back to the previous rule,
       it resolves to the property's inherited/initial value, so border-color
       silently becomes currentColor. Declaring them properly is what makes the
       fallbacks safe to remove. Values are the exact fallbacks they replace, so
       rendering is unchanged.
       ------------------------------------------------------------------ */
    --qs-c-green-200: #bfe8cd;
    --qs-c-red-200:   #f6cfcb;
    --qs-c-amber-200: #ffe0b3;
    --qs-c-cyan-200:  #c7dbff;
    --qs-c-blue-200:  #c7d4f5;
}

/* ---------- Layer 2: semantic aliases — LIGHT (default) ---------- */
/* 2026-08 GPG-approved redesign ("Qatar Clean"): values below are ported from the
   client's approved reference package (DesignReference/UI-Redesign) onto the SAME
   --qs-* names, so every existing var(--qs-*) call site in the app repaints with
   zero code changes. See DesignReference/UI-Redesign/README.md for the source of
   these values; the reference itself uses unprefixed names (--bg, --accent, ...)
   that do not exist anywhere else in this codebase, so they are mapped here rather
   than dropped in verbatim. */
:root {
    /* surfaces */
    --qs-bg: #f5f5f7;
    --qs-surface: var(--qs-c-white);
    --qs-surface-2: #fafafa;
    --qs-surface-3: #f0f0f2;
    --qs-overlay: rgba(16, 26, 48, .55);
    /* lines */
    --qs-border: rgba(0, 0, 0, .08);
    --qs-border-strong: rgba(0, 0, 0, .16);
    --qs-divider: rgba(0, 0, 0, .08);
    /* text */
    --qs-text: #1d1d1f;
    --qs-text-muted: #6e6e73;
    --qs-text-faint: #aeaeb2;
    --qs-text-disabled: #aeaeb2;
    --qs-text-on-accent: var(--qs-c-white);
    /* selected text (::selection in qs-a11y.css). Both halves are declared per theme
       rather than left to the browser: the UA default paints a LIGHT highlight and does
       not touch the text colour, which is fine over dark-on-white body copy but turns
       any light-on-dark run (a dark-theme table row, the sidebar, the login brand panel)
       into near-white text on a near-white block. */
    --qs-selection-bg: #b9d4ff;
    --qs-selection-fg: #0b1220;
    /* brand — 2026-08 explicit request: replace the blue accent with a monochrome
       black (light) / white (dark) system app-wide, a deliberate departure from the
       DesignReference mockup's own accent (which is this same blue, #0071e3/#0a84ff —
       confirmed against its token file before making this change). */
    --qs-primary: var(--qs-c-blue-700);
    --qs-primary-hover: #000000;
    --qs-primary-active: #000000;
    --qs-primary-subtle: rgba(29, 29, 31, .08);
    /* Subtle-chip border. --qs-primary-subtle is the FILL; a chip that borrows the fill
       for its border too reads as a flat blob, so the outline gets its own step on the
       ramp. Used by the unified tab/pill hover state in site.css. */
    --qs-primary-subtle-border: rgba(29, 29, 31, .24);
    --qs-on-primary: var(--qs-c-white);
    /* deprecated bridge — retire once nothing references --qs-primary-600 directly */
    --qs-primary-600: #1d1d1f;
    /* links — follow --qs-primary rather than their own hardcoded blue, so the
       monochrome accent swap above covers links too. */
    --qs-link: var(--qs-primary);
    --qs-link-hover: var(--qs-primary-hover);
    /* status families — solid. Ported from the GPG reference palette (--success/
       --warning/--danger/--info); re-verify against WCAG if a component pairs one
       of these with white/dark text in a new way the previous audit didn't cover. */
    --qs-success-solid: #1d9d4e;
    --qs-on-success: var(--qs-c-white);
    --qs-success-subtle-bg: rgba(52, 199, 89, .13);
    --qs-success-subtle-fg: #1d9d4e;
    --qs-danger-solid: #d70015;
    --qs-on-danger: var(--qs-c-white);
    --qs-danger-subtle-bg: rgba(255, 59, 48, .11);
    --qs-danger-subtle-fg: #d70015;
    --qs-warning-solid: #b25000; /* dark enough on its own for white
                                                     text — unlike the old #ff9800,
                                                     which needed dark ink */
    --qs-on-warning: var(--qs-c-white);
    --qs-warning-subtle-bg: rgba(255, 149, 0, .14);
    --qs-warning-subtle-fg: #b25000;
    --qs-info-solid: #0071a4;
    --qs-on-info: var(--qs-c-white);
    --qs-info-subtle-bg: rgba(90, 200, 250, .16);
    --qs-info-subtle-fg: #0071a4;
    --qs-neutral-solid: #546e7a; /* 5.40:1 on white — no reference
                                                     equivalent, kept as-is */
    --qs-on-neutral: var(--qs-c-white);
    --qs-neutral-subtle-bg: #fafafa;
    --qs-neutral-subtle-fg: #6e6e73;
    /* ------------------------------------------------------------------
       GRADIENT END-STOPS (--qs-*-strong)

       A two-stop gradient cannot be tokenised with one token: substituting the
       same var() into both stops renders a flat block, silently deleting the
       effect. That is why every gradient in qs-layout.css / qs-menu.css /
       qs-signature.css kept BOTH of its raw hexes long after the flat colours
       around them had moved. Each -strong below is the darker end of its family,
       so `linear-gradient(135deg, var(--qs-x-solid), var(--qs-x-strong))` keeps
       the depth AND follows a brand change.
       ------------------------------------------------------------------ */
    --qs-primary-strong: #000000;
    --qs-danger-strong: #a3000f;
    --qs-success-strong: #157a3c;
    --qs-info-strong: #00566f;
    --qs-warning-strong: #7a3700;
    /* Purple is not a status — it marks "special / privileged" menu entries. Kept
       distinct from --qs-act-special, which is an action-BUTTON fill with its own
       measured white-glyph ratio and must not drift to match a badge. */
    --qs-special-solid: #8944ab;
    --qs-special-strong: #6b3486;
    --qs-special-soft: #a970c4;
    /* INVERSE surface — a panel that is dark in BOTH themes (the notification toast,
       tooltips). Not --qs-surface inverted: those flip with the theme, these must not,
       or the toast turns white-on-white the moment dark mode is on. */
    --qs-inverse-surface: #111c33;
    --qs-inverse-surface-2: #0d1526;
    --qs-inverse-fg: #f5f8fc;
    /* status channels — for glows/rings, same reason as --qs-primary-rgb above */
    --qs-success-rgb: 29, 157, 78; /* == --qs-success-solid #1d9d4e */
    --qs-danger-rgb: 215, 0, 21; /* == --qs-danger-solid  #d70015 */
    --qs-warning-rgb: 178, 80, 0; /* == --qs-warning-solid #b25000 */
    --qs-info-rgb: 0, 113, 164; /* == --qs-info-solid    #0071a4 */
    --qs-nav-fg-group-rgb: 110, 110, 115; /* == --qs-nav-fg-group var(--qs-text-muted) #6e6e73 */
    /* ------------------------------------------------------------------
       ROW-ACTION FILLS (L2 + G1 sweep, 2026-08-20)

       These eight are the palette of the circular icon buttons in every grid's
       Actions column. Until now each was a raw hex literal repeated inline in
       ~142 places across ViewScripts and Views - the single largest source of
       hardcoded colour in the app, and precisely what L2 means by "a brand change
       needs hundreds of edits instead of one".

       They are ALSO an unreported contrast defect. The icons are white, and the
       fills as shipped measured:

           #00BCD4 view       2.30:1      #F44336 deactivate  3.68:1
           #FF9800 edit       2.16:1      #E91E63 delete      4.35:1
           #4CAF50 activate   2.78:1      #607D8B manage      4.37:1

       Four of those six were already on the css-budget deny-list from the earlier
       G1/G2 work, which is the guard saying this migration was always the intent.
       Each token below keeps its button's HUE FAMILY and darkens the shade until a
       white glyph clears 4.5:1 - the same move, and the same reference pattern, the
       G1 report cites for the Orders status badges.

       Delete and Deactivate stay two DIFFERENT colours on purpose (crimson-pink vs
       red). They are adjacent destructive actions that sit side by side in the same
       cell; collapsing them into one token would have been tidier and would have
       made two different operations look identical.
       ------------------------------------------------------------------ */
    --qs-act-view: #0e7490; /* was #00BCD4 2.30:1 -> 5.36:1 */
    --qs-act-edit: #8a4b00; /* was #FF9800 2.16:1 -> 6.80:1 */
    --qs-act-delete: #a8174e; /* was #E91E63 4.35:1 -> 7.25:1 */
    --qs-act-activate: #1b7f3b; /* was #4CAF50 2.78:1 -> 5.07:1 */
    --qs-act-deactivate: #b02a37; /* was #F44336 3.68:1 -> 6.50:1 */
    --qs-act-manage: #455a64; /* was #607D8B 4.37:1 -> 7.24:1 */
    --qs-act-special: #6a1b9a; /* was #9C27B0 6.30:1 -> 9.39:1 */
    --qs-act-primary: #2f3d94; /* was #3F51B5 6.87:1 -> 8.4:1  */
    --qs-act-fg: #ffffff; /* the glyph colour every ratio above assumes */
    /* NO DARK OVERRIDE FOR --qs-act-* , AND THAT IS THE DECISION, NOT AN OMISSION.
       The first attempt gave dark mode a pale ramp with a dark glyph, mirroring how
       --qs-success-solid etc. invert. Measured result: the glyph stayed white (a vendor
       rule on .btn-icon owns it at a higher specificity than a class can reach without
       escalating), so amber-400 with a white icon came out at 1.67:1 - far worse than
       what it replaced. Chasing that would have meant a specificity fight over the icon
       colour in every action variant.
       The fills above are saturated mid-darks. Measured on the dark surface with the
       same white glyph they carry in light mode, every one of them clears 5.36:1. One
       accessible palette for both themes is fewer rules, fewer failure modes, and one
       less thing to keep in sync. */
    /* money semantics (H1/H2/H3 — one governing rule for signed amounts) */
    --qs-money-pos: var(--qs-success-solid);
    --qs-money-neg: var(--qs-danger-solid);
    --qs-money-zero: var(--qs-text-muted);
    /* sidebar / nav — GPG redesign moves the sidebar from a solid brand-blue panel to
       a NEUTRAL surface (reference: `.sb{background:var(--surface)}`), with the brand
       colour reserved for the active-item tint/label instead of the panel itself. This
       is why these are no longer derived from the blue primitive, and why the ramp
       below IS re-declared per theme (unlike the old scheme, where one dark-blue
       background served both themes identically). */
    --qs-nav-bg: var(--qs-surface);
    --qs-nav-fg: var(--qs-text);
    --qs-nav-fg-muted: var(--qs-text-muted);
    --qs-nav-active-bg: var(--qs-primary-subtle);
    --qs-nav-active-bar: var(--qs-primary);
    /* Reference hardcodes .sb-group-h{color:#696969} rather than deriving it from a
       token — --qs-text-faint (#aeaeb2) is a full step lighter and read as washed-out/
       barely-legible section headers ("LIVE UPDATES", "OPERATIONS") next to the reference's
       more visible medium gray. Matched to the same literal value; dark mode already gets
       its own #9a9a9a via qs-theme-dark.css's .sb-group-h rule, so only light needs this. */
    --qs-nav-section-fg: #696969;
    --qs-nav-divider: rgba(0, 0, 0, .05);
    --qs-nav-fg-leaf: var(--qs-text); /* leaf menu label */
    --qs-nav-fg-group: var(--qs-text-muted); /* group header — quieter than the leaves */
    /* focus (see qs-a11y.css for the ring implementation) — follows --qs-primary too. */
    --qs-focus-ring: var(--qs-primary);
    --qs-focus-ring-contrast: #ffffff;
    /* elevation — FULL box-shadow shorthands. Only ever usable as the whole value of
       `box-shadow:`, never inside one. For `box-shadow: 0 4px 12px <colour>` use the
       --qs-shadow-color* tokens below instead; substituting a shorthand into a colour
       slot makes the declaration invalid at computed-value time and the shadow silently
       disappears. (That is exactly the bug the --qs-d-shadow bridge shipped with.) */
    --qs-shadow-1: 0 1px 2px rgba(0, 0, 0, .08);
    --qs-shadow-2: 0 4px 24px rgba(0, 0, 0, .06);
    --qs-shadow-3: 0 12px 48px rgba(0, 0, 0, .14);
    /* elevation, COLOUR ONLY — for hand-rolled `box-shadow: <offsets> var(...)` rules */
    --qs-shadow-color: rgba(0, 0, 0, .06);
    --qs-shadow-color-lg: rgba(0, 0, 0, .14);
    /* misc accents (sidebar glow / signature flourishes) */
    --qs-accent-cyan: #22d3ee;
    --qs-accent-indigo: #818cf8;
    --qs-accent-amber: #fbbf24;
    --qs-accent-cyan-rgb: 34, 211, 238; /* == --qs-accent-cyan, for the signature glows */
    /* The logo's yellow. Deliberately NOT folded into --qs-warning-*: it is a mark colour
       (loader orbit dot, signature flourish), not a status, and it must not shift when the
       warning ramp is retuned for contrast. */
    --qs-brand-yellow: #f8c719;
    /* Magenta. Same reasoning as --qs-brand-yellow: a distinguishing accent (the commission
       stat tile), not a status, so it must not be folded into the danger ramp. */
    --qs-accent-pink: #b02a6b;
    /* ------------------------------------------------------------------
       CHANNEL TOKENS (--qs-*-rgb) — the missing half of the token system.

       A token can only be swapped for a brand change if EVERY use site can
       reference it. `rgba(23, 63, 166, .3)` cannot: rgba() needs three bare
       numbers, and `var(--qs-primary)` expands to `#173fa6`, which is not a
       legal rgba() argument list. So every translucent brand glow, wash and
       shadow in the app was forced to re-hardcode the brand hex — 40+ times in
       site.css alone — and stayed hardcoded no matter how good the semantic
       layer above got. That is why adoption stalled around 20% in the legacy
       sheets: the tokens physically could not express what those rules do.

       These declare the same colours as bare `R, G, B` channel lists, so the
       translucent form becomes tokenised too:

           box-shadow: 0 0 14px rgba(var(--qs-primary-rgb), .6);

       Keep each *-rgb in lockstep with its hex sibling above/below. They are
       two encodings of ONE colour; a brand swap edits both lines or neither.
       ------------------------------------------------------------------ */
    --qs-white-rgb: 255, 255, 255; /* primitive — never themed */
    --qs-black-rgb: 0, 0, 0; /* primitive — never themed */

    --qs-primary-rgb: 29, 29, 31; /* == --qs-primary  #1d1d1f */
    --qs-surface-rgb: 255, 255, 255; /* == --qs-surface  #ffffff */
    --qs-text-rgb: 29, 29, 31; /* == --qs-text     #1d1d1f */
    --qs-nav-fg-rgb: 29, 29, 31; /* == --qs-nav-fg   var(--qs-text) #1d1d1f */
    --qs-shadow-rgb: 0, 0, 0; /* the ink every elevation tint is mixed from */
}

/* ---------- Layer 2: semantic aliases — DARK (OS preference, no explicit toggle) ---------- */
@media (prefers-color-scheme: dark) {
    :root:not([data-theme="light"]) {
        --qs-bg: #000000;
        --qs-surface: #1c1c1e;
        --qs-surface-2: #161618;
        --qs-surface-3: #2c2c2e;
        --qs-overlay: rgba(4, 8, 16, .65);

        --qs-border: rgba(255, 255, 255, .12);
        --qs-border-strong: rgba(255, 255, 255, .20);
        --qs-divider: rgba(255, 255, 255, .12);

        --qs-text: #f5f5f7;
        --qs-text-muted: #98989d;
        --qs-text-faint: #636366;
        --qs-text-disabled: #636366;
        --qs-text-on-accent: #ffffff;
        /* The dark theme's own authored secondary/tertiary text. Brighter than
           --qs-text-muted/-faint above because these land on --qs-surface (#16202e):
           #6b7688 there measures 3.56:1 and FAILS WCAG AA, while #c4cfdd is 10.4:1. */
        --qs-text-muted-strong: #e6ecf4;
        --qs-text-faint-strong: #c4cfdd;

        /* Selected text: a solid mid-blue with white ink (6.5:1), so a selected row keeps
           the same legibility as an unselected one. See the light block for why the UA
           default cannot be left in place here. */
        --qs-selection-bg: #2f5da8;
        --qs-selection-fg: #ffffff;

        /* Monochrome dark-mode counterpart to the light-mode black accent above — near-
           white rather than black, since black-on-black would be invisible. */
        --qs-primary: #f5f5f7;
        --qs-primary-hover: #ffffff;
        --qs-primary-active: #ffffff;
        --qs-primary-subtle: rgba(245, 245, 247, .16);
        --qs-primary-subtle-border: rgba(245, 245, 247, .32);
        --qs-on-primary: #000000;
        --qs-primary-600: #f5f5f7;

        --qs-link: var(--qs-primary);
        --qs-link-hover: var(--qs-primary-hover);

        --qs-success-solid: #30d158;
        --qs-on-success: #0b1220;
        --qs-success-subtle-bg: rgba(48, 209, 88, .16);
        --qs-success-subtle-fg: #6ee7a8;

        --qs-danger-solid: #ff453a;
        --qs-on-danger: #ffffff;
        --qs-danger-subtle-bg: rgba(255, 69, 58, .16);
        --qs-danger-subtle-fg: #fca5a5;

        --qs-warning-solid: #ff9f0a;
        --qs-on-warning: #241800;
        --qs-warning-subtle-bg: rgba(255, 159, 10, .16);
        --qs-warning-subtle-fg: #fcd34d;

        --qs-info-solid: #64d2ff;
        --qs-on-info: #0b1220;
        --qs-info-subtle-bg: rgba(100, 210, 255, .14);
        --qs-info-subtle-fg: #7dd3fc;

        --qs-neutral-solid: #94a3b8;
        --qs-on-neutral: #0b1220;
        --qs-neutral-subtle-bg: #1d2937;
        --qs-neutral-subtle-fg: #cbd5e1;

        /* gradient end-stops, dark. See the light block for why these exist.
           --qs-inverse-* and --qs-special-* are NOT re-declared: the inverse surface is
           dark in both themes by definition, and the purple reads correctly on both. */
        --qs-primary-strong: #ffffff;
        --qs-danger-strong: #ff6961;
        --qs-success-strong: #63e6a3;
        --qs-info-strong: #85dcff;
        --qs-warning-strong: #ffb340;

        --qs-success-rgb: 48, 209, 88;   /* == --qs-success-solid #30d158 */
        --qs-danger-rgb: 255, 69, 58;    /* == --qs-danger-solid  #ff453a */
        --qs-warning-rgb: 255, 159, 10;  /* == --qs-warning-solid #ff9f0a */
        --qs-info-rgb: 100, 210, 255;    /* == --qs-info-solid    #64d2ff */


        --qs-money-pos: #6ee7a8; /* 10.7:1 on --qs-surface */
        --qs-money-neg: #fca5a5;
        --qs-money-zero: var(--qs-text-muted);

        --qs-nav-bg: var(--qs-surface);
        --qs-nav-fg: var(--qs-text);
        --qs-nav-fg-muted: var(--qs-text-muted);
        --qs-nav-active-bg: var(--qs-primary-subtle);
        --qs-nav-active-bar: var(--qs-primary);
        --qs-nav-section-fg: var(--qs-text-faint);
        --qs-nav-divider: rgba(255, 255, 255, .07);

        --qs-focus-ring: var(--qs-primary);
        --qs-focus-ring-contrast: #ffffff;

        --qs-shadow-1: 0 1px 2px rgba(0, 0, 0, .40);
        --qs-shadow-2: 0 4px 24px rgba(0, 0, 0, .35);
        --qs-shadow-3: 0 12px 48px rgba(0, 0, 0, .55);
        --qs-shadow-color: rgba(0, 0, 0, .35);
        --qs-shadow-color-lg: rgba(0, 0, 0, .55);

        /* channel tokens — dark. Kept in lockstep with the hex siblings above; see the
           long note in the light :root block for why these exist at all.
           --qs-white-rgb / --qs-black-rgb are primitives and are NOT re-declared here. */
        --qs-primary-rgb: 245, 245, 247;   /* == --qs-primary  #f5f5f7 */
        --qs-surface-rgb: 28, 28, 30;     /* == --qs-surface  #1c1c1e */
        --qs-text-rgb: 245, 245, 247;     /* == --qs-text     #f5f5f7 */
        --qs-nav-fg-rgb: 245, 245, 247;   /* == --qs-nav-fg   var(--qs-text) #f5f5f7 */
        --qs-shadow-rgb: 0, 0, 0;         /* dark elevation is mixed from pure black */
    }
}

/* ---------- Layer 2: semantic aliases — DARK (explicit toggle wins both ways) ---------- */
:root[data-theme="dark"] {
    --qs-bg: #000000;
    --qs-surface: #1c1c1e;
    --qs-surface-2: #161618;
    --qs-surface-3: #2c2c2e;
    --qs-overlay: rgba(4, 8, 16, .65);

    --qs-border: rgba(255, 255, 255, .12);
    --qs-border-strong: rgba(255, 255, 255, .20);
    --qs-divider: rgba(255, 255, 255, .12);

    --qs-text: #f5f5f7;
    --qs-text-muted: #98989d;
    --qs-text-faint: #636366;
    --qs-text-disabled: #636366;
    --qs-text-on-accent: #ffffff;
    /* The dark theme's own authored secondary/tertiary text. Brighter than
       --qs-text-muted/-faint above because these land on --qs-surface (#16202e):
       #6b7688 there measures 3.56:1 and FAILS WCAG AA, while #c4cfdd is 10.4:1. */
    --qs-text-muted-strong: #e6ecf4;
    --qs-text-faint-strong: #c4cfdd;

    /* Selected text: a solid mid-blue with white ink (6.5:1), so a selected row keeps
       the same legibility as an unselected one. See the light block for why the UA
       default cannot be left in place here. */
    --qs-selection-bg: #2f5da8;
    --qs-selection-fg: #ffffff;

    --qs-primary: #f5f5f7;
    --qs-primary-hover: #ffffff;
    --qs-primary-active: #ffffff;
    --qs-primary-subtle: rgba(245, 245, 247, .16);
    --qs-primary-subtle-border: rgba(245, 245, 247, .32);
    --qs-on-primary: #000000;
    --qs-primary-600: #f5f5f7;

    --qs-link: var(--qs-primary);
    --qs-link-hover: var(--qs-primary-hover);

    --qs-success-solid: #30d158;
    --qs-on-success: #0b1220;
    --qs-success-subtle-bg: rgba(48, 209, 88, .16);
    --qs-success-subtle-fg: #6ee7a8;

    --qs-danger-solid: #ff453a;
    --qs-on-danger: #ffffff;
    --qs-danger-subtle-bg: rgba(255, 69, 58, .16);
    --qs-danger-subtle-fg: #fca5a5;

    --qs-warning-solid: #ff9f0a;
    --qs-on-warning: #241800;
    --qs-warning-subtle-bg: rgba(255, 159, 10, .16);
    --qs-warning-subtle-fg: #fcd34d;

    --qs-info-solid: #64d2ff;
    --qs-on-info: #0b1220;
    --qs-info-subtle-bg: rgba(100, 210, 255, .14);
    --qs-info-subtle-fg: #7dd3fc;

    --qs-neutral-solid: #94a3b8;
    --qs-on-neutral: #0b1220;
    --qs-neutral-subtle-bg: #1d2937;
    --qs-neutral-subtle-fg: #cbd5e1;

    /* gradient end-stops, dark. See the light block for why these exist.
       --qs-inverse-* and --qs-special-* are NOT re-declared: the inverse surface is
       dark in both themes by definition, and the purple reads correctly on both. */
    --qs-primary-strong: #ffffff;
    --qs-danger-strong: #ff6961;
    --qs-success-strong: #63e6a3;
    --qs-info-strong: #85dcff;
    --qs-warning-strong: #ffb340;

    --qs-success-rgb: 48, 209, 88;   /* == --qs-success-solid #30d158 */
    --qs-danger-rgb: 255, 69, 58;    /* == --qs-danger-solid  #ff453a */
    --qs-warning-rgb: 255, 159, 10;  /* == --qs-warning-solid #ff9f0a */
    --qs-info-rgb: 100, 210, 255;    /* == --qs-info-solid    #64d2ff */

    --qs-money-pos: #6ee7a8;
    --qs-money-neg: #fca5a5;
    --qs-money-zero: var(--qs-text-muted);

    --qs-nav-bg: var(--qs-surface);
    --qs-nav-fg: var(--qs-text);
    --qs-nav-fg-muted: var(--qs-text-muted);
    --qs-nav-active-bg: var(--qs-primary-subtle);
    --qs-nav-active-bar: var(--qs-primary);
    --qs-nav-section-fg: var(--qs-text-faint);
    --qs-nav-divider: rgba(255, 255, 255, .07);

    --qs-focus-ring: var(--qs-primary);
    --qs-focus-ring-contrast: #ffffff;

    --qs-shadow-1: 0 1px 2px rgba(0, 0, 0, .40);
    --qs-shadow-2: 0 4px 24px rgba(0, 0, 0, .35);
    --qs-shadow-3: 0 12px 48px rgba(0, 0, 0, .55);
    --qs-shadow-color: rgba(0, 0, 0, .35);
    --qs-shadow-color-lg: rgba(0, 0, 0, .55);

    /* channel tokens — dark. Kept in lockstep with the hex siblings above; see the
       long note in the light :root block for why these exist at all.
       --qs-white-rgb / --qs-black-rgb are primitives and are NOT re-declared here. */
    --qs-primary-rgb: 245, 245, 247;   /* == --qs-primary  #f5f5f7 */
    --qs-surface-rgb: 28, 28, 30;     /* == --qs-surface  #1c1c1e */
    --qs-text-rgb: 245, 245, 247;     /* == --qs-text     #f5f5f7 */
    --qs-nav-fg-rgb: 245, 245, 247;   /* == --qs-nav-fg   var(--qs-text) #f5f5f7 */
    --qs-shadow-rgb: 0, 0, 0;         /* dark elevation is mixed from pure black */
}

/* ---------- Layer 3: dimensions (theme-agnostic) ---------- */
:root {
    /* spacing — 4px base */
    --qs-space-0: 0;
    --qs-space-1: 4px;
    --qs-space-2: 8px;
    --qs-space-3: 12px;
    --qs-space-4: 16px;
    --qs-space-5: 24px;
    --qs-space-6: 32px;
    --qs-space-7: 48px;
    --qs-space-8: 64px;

    /* radius — --qs-radius-md bumped 8px -> 10px (reference --r-ctl, "controls");
       --qs-radius-xl already matched reference --r-card (16px) exactly. --qs-radius-2xl
       is new (additive, reference --r-lg) for page-level surfaces the old scale had no
       step for — modals, the sidebar's outer edge. */
    --qs-radius-xs: 4px;
    --qs-radius-sm: 6px;
    --qs-radius-md: 10px;
    --qs-radius-lg: 12px;
    --qs-radius-xl: 16px;
    --qs-radius-2xl: 20px;
    --qs-radius-pill: 999px;
    --qs-radius-circle: 50%;

    /* type — Latin stack updated to the reference's ('Inter', -apple-system, ...).
       --qs-font-sans-ar is intentionally NOT touched: Inter has no Arabic glyphs and
       the reference package is English-only demo content (see its README — "use the
       resx localizer in the app"), so Dubai stays the Arabic face. */
    --qs-font-sans: 'Inter', -apple-system, BlinkMacSystemFont, 'Segoe UI', Tahoma, Arial, sans-serif;
    --qs-font-sans-ar: 'Dubai', 'Segoe UI', Tahoma, Arial, sans-serif;
    --qs-fs-2xs: .6875rem;
    --qs-fs-xs: .75rem;
    --qs-fs-sm: .8125rem;
    --qs-fs-base: .875rem;
    --qs-fs-md: 1rem;
    --qs-fs-lg: 1.125rem;
    --qs-fs-xl: 1.25rem;
    --qs-fs-2xl: 1.5rem;
    --qs-fs-3xl: 1.875rem;
    --qs-fw-regular: 400;
    --qs-fw-medium: 500;
    --qs-fw-semibold: 600;
    --qs-fw-bold: 700;
    --qs-lh-tight: 1.25;
    --qs-lh-normal: 1.5;
    --qs-lh-relaxed: 1.7;

    /* z-index — seeded from the Bootstrap 4 stack already in use so adopting
       these tokens doesn't reflow any existing stacking context */
    --qs-z-base: 0;
    --qs-z-sticky: 100;
    --qs-z-sidebar: 1000;
    --qs-z-navbar: 1030;
    --qs-z-dropdown: 1050;
    --qs-z-modal-backdrop: 1055;
    --qs-z-modal: 1060;
    --qs-z-popover: 1070;
    --qs-z-tooltip: 1080;
    --qs-z-toast: 1090;
    --qs-z-loader: 1100;

    /* control sizing — G4's whole fix starts here: --qs-tap-min is a TOKEN so the
       44px minimum is a one-line audit answer, not 1064 individual style="" edits */
    --qs-tap-min: 44px;
    --qs-control-h: 40px;
    --qs-control-h-sm: 32px;
    --qs-checkbox-size: 20px;
    --qs-icon-btn: 40px;

    /* motion — durations and the out-ease curve updated to match the reference
       (--dur-fast/-mid/-slow, --ease-out). --qs-ease-spring is new (additive,
       reference --ease-spring) for the bouncier entrance the redesign uses on
       dropdowns/popovers; nothing existing referenced an overshoot curve before. */
    --qs-dur-fast: 150ms;
    --qs-dur: 240ms;
    --qs-dur-slow: 340ms;
    --qs-ease-out: cubic-bezier(.215, .61, .355, 1);
    --qs-ease-spring: cubic-bezier(.34, 1.45, .64, 1);
}

@media (prefers-reduced-motion: reduce) {
    :root {
        --qs-dur-fast: 0ms;
        --qs-dur: 0ms;
        --qs-dur-slow: 0ms;
    }
}

/* ---------- Compatibility bridges — retire once the last consumer moves off ---------- */
/* qs-signature.css's own :root (lines 11-15) declares these; once that block is
   deleted these aliases keep its 31 var(--qs-sig-*) call sites resolving. */
:root {
    --qs-sig-accent: var(--qs-accent-cyan);
    --qs-sig-accent-2: var(--qs-accent-indigo);
    --qs-sig-accent-3: var(--qs-accent-amber);
    --qs-sig-ease: var(--qs-ease-out);
}

/* qs-theme-dark.css's own --qs-d-* block (lines 27-44, 672 var() call sites in
   that file alone, 263 more in qs-theme-dark-pages.css) is scoped to
   html[data-theme="dark"] and duplicates the light/dark split this file already
   does. Re-declaring the 18 names here as aliases lets every existing
   var(--qs-d-*) call site keep resolving correctly WITHOUT waiting for someone
   to hand-edit 935 call sites first — the two source declarations in
   qs-theme-dark*.css can be deleted at leisure once this bridge is confirmed live. */
:root[data-theme="dark"] {
    --qs-d-bg: var(--qs-bg);
    --qs-d-surface: var(--qs-surface);
    --qs-d-surface-2: var(--qs-surface-2);
    --qs-d-surface-3: var(--qs-surface-3);
    --qs-d-border: var(--qs-border);
    --qs-d-border-strong: var(--qs-border-strong);
    --qs-d-text: var(--qs-text);
    --qs-d-text-muted: var(--qs-text-muted-strong);
    --qs-d-text-faint: var(--qs-text-faint-strong);
    --qs-d-primary: var(--qs-primary);
    --qs-d-primary-600: var(--qs-primary-600);
    --qs-d-link: var(--qs-link);
    --qs-d-success: var(--qs-success-solid);
    --qs-d-warning: var(--qs-warning-solid);
    --qs-d-danger: var(--qs-danger-solid);
    --qs-d-info: var(--qs-info-solid);
    --qs-d-shadow: var(--qs-shadow-color);
    --qs-d-shadow-lg: var(--qs-shadow-color-lg);
}

@media (prefers-color-scheme: dark) {
    :root:not([data-theme="light"]) {
        --qs-d-bg: var(--qs-bg);
        --qs-d-surface: var(--qs-surface);
        --qs-d-surface-2: var(--qs-surface-2);
        --qs-d-surface-3: var(--qs-surface-3);
        --qs-d-border: var(--qs-border);
        --qs-d-border-strong: var(--qs-border-strong);
        --qs-d-text: var(--qs-text);
        --qs-d-text-muted: var(--qs-text-muted);
        --qs-d-text-faint: var(--qs-text-faint);
        --qs-d-primary: var(--qs-primary);
        --qs-d-primary-600: var(--qs-primary-600);
        --qs-d-link: var(--qs-link);
        --qs-d-success: var(--qs-success-solid);
        --qs-d-warning: var(--qs-warning-solid);
        --qs-d-danger: var(--qs-danger-solid);
        --qs-d-info: var(--qs-info-solid);
        --qs-d-shadow: var(--qs-shadow-2);
        --qs-d-shadow-lg: var(--qs-shadow-3);
    }
}
