/* ═══════════════════════════════════════════════════════════════════════════
   YARDWYRE DESIGN TOKENS
   ───────────────────────────────────────────────────────────────────────────
   The single source of truth for every colour, size, and depth in the app.

   These are not aspirational values — every one was lifted from what
   frontend.html was already doing, so adopting a token never changes a pixel.
   What it changes is the blast radius: the app's "dim grey" was written as
   #9ca3af in 177 separate places, so nobody could adjust it. Now it is
   --color-text-dim, in one place.

   RULE: no raw hex, px font-size, or z-index number anywhere outside this
   file. If you need a value that isn't here, add it here first — that is the
   conversation about whether the app really needs another grey.

   See DESIGN_SYSTEM.md for the how and why.
   ═══════════════════════════════════════════════════════════════════════════ */

:root {

  /* ── BRAND ─────────────────────────────────────────────────────────────
     The marketplace palette the product is built around. It shows up as the
     login gradient, the header underline, and the per-letter logo colours.
     Blue is the only one that acts as a UI colour; the other four are
     identity, used in gradients and accents, not on controls. */
  --brand-red:            #e53238;
  --brand-blue:           #0064d2;
  --brand-blue-bright:    #3665f3;
  --brand-gold:           #f5af02;
  --brand-green:          #86b817;
  /* The same two hues darkened until they clear WCAG AA as TEXT on white.
     --brand-gold/-green above are bright enough to fail that and are for
     decoration only (the gradient rules, the login card's top border). The
     marketing pages have used this darker pair inline since the contrast audit;
     naming them here is what lets the wordmark be one definition instead of two
     that drift. Do not "simplify" by pointing .yw-logo at the bright pair. */
  --brand-gold-ink:       #ba8502;
  --brand-green-ink:      #729c14;
  --brand-gradient:       linear-gradient(90deg, #e53238, #0064d2 33%, #f5af02 66%, #86b817);

  /* Somebody else's colours, not ours: the login screen's two install buttons,
     each FILLED in its platform's colour. They are here so no raw hex lands in
     a fragment, and they are deliberately not part of the ramp above — neither
     is ours to restyle, and neither should be swept up by a palette change.

     Both carry WHITE labels, which is a deliberate choice and the one thing
     here that is not simply Android's and Apple's published value.

     Apple's #000 takes white at 21:1 and needed nothing. Android's published
     green is #3ddc84, a light mint that takes white at only 1.8:1 — far under
     the 4.5:1 a button label needs. Its darkest usable value for white ink is
     around L* 0.183; the green below is #3ddc84 walked down its OWN hue (147°,
     preserved to 149°) until white clears that with margin, landing at 5.3:1.
     It is recognisably the same green and it is not the one Android ships, so
     do not "correct" it back to #3ddc84 without also changing the ink.

     `-hover` is each colour one step darker again, so a press reads as the same
     button rather than a different one; the green's hover clears 7:1. */
  --logo-android:         #0e7c43;
  --logo-android-hover:   #0b6637;
  --logo-apple:           #000;
  --logo-apple-hover:     #1c1c1e;

  /* ── NEUTRALS ──────────────────────────────────────────────────────────
     A Tailwind-shaped grey ramp. Prefer the semantic aliases below over
     reaching for a step directly — "muted text" survives a palette change,
     "gray-500" does not. */
  --gray-900:             #111827;
  --gray-800:             #1a1a2e;
  --gray-700:             #374151;
  --gray-600:             #4b5563;
  --gray-500:             #6b7280;
  --gray-400:             #9ca3af;
  --gray-300:             #d1d5db;
  --gray-200:             #e5e7eb;
  --gray-100:             #f3f4f6;
  --gray-50:              #f9fafb;
  --white:                #fff;

  /* A second, cooler neutral (slate, not the grey ramp above) that showed up
     hardcoded on exactly one button pattern — the "Items" link on
     invoices/work-orders/delivery-tickets rows — three separate times with
     the identical pair of values. Named for what it dresses rather than
     generalized into a ramp, since nothing else uses it. */
  --color-items-btn-bg:   #f1f5f9;
  --color-items-btn-text: #334155;

  /* ── SEMANTIC SURFACES & TEXT ──────────────────────────────────────────
     Reach for these first. They say what the colour is *for*, which is the
     only thing that stays true when the palette moves. */
  /* Was #f0f2f5 until 2026-08-20. Eleven components read --color-text-muted
     (gray-500) on this ground and landed at 4.31:1 — clearing AA on white,
     4% short here. The ground was the cheaper thing to move: every grey below
     gray-600 fails on #f0f2f5, so fixing it in the type ramp would have meant
     promoting muted to gray-600, which is now what --color-text-dim is, and
     the two would have collapsed into one colour. #f6f8fa puts muted at
     4.54:1 with margin, and the page/card step it costs (1.10:1 -> 1.06:1)
     was never carrying the boundary — .table-card and .modal draw a real
     1px border, which is what WCAG 1.4.11 actually asks for. */
  --color-bg:             #f6f8fa;   /* the page behind every card          */
  --color-surface:        var(--white);
  --color-surface-sunken: #f8fafc;   /* table heads, tfoot, read-only field */
  --color-surface-hover:  var(--gray-100);

  --color-text:           var(--gray-800);   /* body default                */
  --color-text-strong:    var(--gray-900);   /* headings, figures           */
  --color-text-body:      var(--gray-700);   /* labels, table cells         */
  --color-text-muted:     var(--gray-500);   /* secondary / helper copy     */
  /* gray-400 until 2026-08-20, where it was 2.54:1 on --color-surface and
     2.26:1 on --color-bg — failing WCAG AA 1.4.3 on BOTH grounds, at every
     text size, with no context in which it was defensible. This file already
     said so in the prefers-contrast block below, and already named gray-600 as
     the promotion; it just applied it only to readers who had asked. It now
     applies to everyone: 7.56:1 on white, 6.74:1 on the page grey.
     Measured, not estimated. Every step below gray-600 fails on --color-bg,
     so "dim" cannot be lighter than this and still be readable. */
  --color-text-dim:       var(--gray-600);   /* placeholder, empty states   */
  --color-text-invert:    var(--white);

  --color-border-subtle:  var(--gray-100);   /* row separators inside a card*/
  --color-border:         var(--gray-200);   /* card + table hairlines      */
  --color-border-strong:  var(--gray-300);   /* input outlines              */
  --color-border-hover:   var(--gray-400);   /* an outlined control, hovered*/

  /* ── INTENT ────────────────────────────────────────────────────────────
     Four meanings, each with a foreground, a hover/darker step, a tinted
     background, and a border. Anything that communicates state uses one of
     these four — never a one-off hex. */

  /* Primary — the action that moves the task forward. */
  --color-primary:        var(--brand-blue);
  --color-primary-hover:  var(--brand-blue-bright);
  /* #eff6ff until 2026-08-20: --color-text-muted read 4.44:1 on it (.notif-card-body),
     a 1% shortfall. Lightened until muted clears: now 4.52:1. */
  --color-primary-bg:     #f2f8ff;   /* subtlest tint (nav active, hover)   */
  --color-primary-bg-bold:#dbeafe;   /* a toggled-on control                */
  --color-primary-border: #bfdbfe;
  --color-primary-border-bold: #93c5fd;
  --color-primary-text:   #1e40af;   /* primary-coloured text on a tint     */
  --color-primary-text-bold: #1d4ed8;

  /* Danger — destructive, failed, or blocking. */
  /* Was #dc2626, which read 3.95:1 as label text on its own --color-danger-bg
     tint (.btn-tint-red, .alert-error) and 4.41:1 on the softer wash — both
     under WCAG AA 1.4.3. #c81e1e clears every pairing it appears in: 4.70:1
     on the tint, 5.24:1 on the wash, 5.74:1 on white. It is also the fill of
     .btn-red, so white button text improves from 4.83:1 to 5.74:1 with it. */
  --color-danger:         #c81e1e;
  --color-danger-hover:   #b91c1c;
  --color-danger-text:    #991b1b;   /* on a danger tint                    */
  /* #fef2f2 until 2026-08-20: --color-text-muted read 4.42:1 on it (the
     danger-zone explainer). Now 4.54:1. */
  --color-danger-bg:      #fef6f6;   /* subtlest wash (danger-zone, alert)  */
  --color-danger-bg-bold: #fee2e2;   /* badge / toast fill                  */
  --color-danger-border:  #fecaca;
  /* A deliberately pale destructive border. .btn-outline-red uses the full
     --color-danger on its border; the delete-store button wants the softer
     edge, which is a choice rather than drift, so it gets a name. */
  --color-danger-border-strong: #fca5a5;

  /* Success — completed, paid, listed, in stock. */
  /* Was #16a34a, which failed everywhere it was used as text, not merely on
     tints: 3.30:1 on white, 3.03:1 on the blue notification tint, 2.94:1 on
     the page grey. #137a37 clears all three at 5.43 / 4.99 / 4.84:1. */
  --color-success:        #137a37;
  /* Was #059669: a bold 13px price at 3.77:1 on white. #047857 is 5.48:1 on
     white, 4.89:1 on the page grey. storefront.py's _STORE_TOKENS carries a
     copy, held equal by scripts/test_price_contrast.py. */
  --color-success-deep:   #047857;   /* toast rule, price figures           */
  --color-success-text:   #065f46;   /* on a success tint                   */
  --color-success-bg:     #dcfce7;
  --color-success-bg-bold:#d1fae5;
  --color-success-text-bold: #166534;   /* on --color-success-bg (the Resume-style utility button) */

  /* Warning — needs attention before you continue. */
  --color-warn:           #d97706;
  --color-warn-text:      #92400e;   /* on a warn tint                      */
  --color-warn-bg:        #fffbeb;
  --color-warn-bg-bold:   #fef3c7;
  --color-warn-border:    #fde68a;

  /* Stat-tile figures. These four read as a scale (low → high) on the Price
     Check and eBay Demand summaries, so they are semantic, not decorative --
     but low/high intentionally borrow the success/danger hues (a low price
     reads as good news, a high one as a caution), so they reference those
     tokens rather than duplicating the literal value. Mid and average have
     no prior token -- #2563eb and #7c3aed appear nowhere else in this file
     -- so they stay as their own values. */
  --color-stat-low:       var(--color-success);
  --color-stat-mid:       #2563eb;
  --color-stat-avg:       #7c3aed;
  --color-stat-high:      var(--color-danger);

  /* A violet no semantic role owns. The lead-source badges needed a third
     colour and the --color-access-* set below is spoken for: it is the
     six-area web-user palette, and borrowing one would tie two unrelated
     things together. These are the exact values the Contact badge hard-coded
     in js/058-leads-platform-admin.js before it became a class. */
  --color-violet-bg:   #f3e8ff;
  --color-violet-text: #6d28d9;

  /* Access-area chips on the Users screen. These are CATEGORY colours, not
     intent colours: "Inventory" is not success and "Admin" is not a warning,
     they just need to be told apart. They are named for the area they dress,
     the same way --color-items-btn-* is, rather than folded into the ramp.

     Three of the six pairs happen to equal an existing intent token, so they
     reference it instead of repeating the literal -- same reasoning as
     --color-stat-low above. ai-drafts, ops and sales introduce new values.

     They were written out by hand in TWO places that had to agree: the legend
     in pages/020-web-users.html and PERM_LABELS in js/091-web-users.js. They
     had already drifted -- the JS renders a Sales chip the legend never had. */
  --color-access-inventory-bg:   var(--color-success-bg-bold);
  --color-access-inventory-text: var(--color-success-text);
  --color-access-ai-drafts-bg:   #ede9fe;
  --color-access-ai-drafts-text: #5b21b6;
  --color-access-ebay-bg:        var(--color-primary-bg-bold);
  --color-access-ebay-text:      var(--color-primary-text);
  --color-access-ops-bg:         #ffedd5;
  --color-access-ops-text:       #9a3412;
  /* Sales borrowed --color-success-bg, one step off inventory's
     --color-success-bg-bold: #dcfce7 beside #d1fae5. Two mint chips a shade
     apart, in a palette whose only job is telling six areas apart, and the
     one that reveals revenue was the one hiding. Rose is the sector nothing
     else here occupies — green, violet, blue, orange and amber are spoken
     for — and it is nowhere near --color-danger, which is a saturated red. */
  --color-access-sales-bg:       #fce7f3;
  --color-access-sales-text:     #9d174d;
  --color-access-admin-bg:       var(--color-warn-bg-bold);
  --color-access-admin-text:     var(--color-warn-text);

  /* ── TYPOGRAPHY ────────────────────────────────────────────────────────
     One UI face. Two mono stacks, deliberately: --font-mono is the tabular
     face for part numbers and stock codes in tables; --font-mono-ui is the
     system stack used where the text is code rather than data (the HTML
     source view, VIN decoder diagnostics). */
  --font-sans:            'Segoe UI', system-ui, sans-serif;
  --font-mono:            'Cascadia Code', 'Courier New', monospace;
  --font-mono-ui:         ui-monospace, Menlo, Consolas, monospace;

  /* 12px, the same as --text-xs: no token sets text below the 12px floor
     docs/CONTRIBUTING.md holds body text to. Kept as its own name so the ~150 micro-label
     and table-head sites can still be told apart. */
  --text-2xs:             12px;   /* uppercase micro-labels, table heads    */
  --text-xs:              12px;   /* helper text, badges, dense cells       */
  --text-sm:              13px;   /* the app's workhorse size               */
  --text-base:            14px;   /* nav, search, primary inputs            */
  --text-md:              15px;   /* login submit, VIN field                */
  --text-lg:              16px;   /* modal titles, header logo              */
  --text-xl:              18px;   /* section figures                        */
  --text-2xl:             20px;   /* page titles                            */
  --text-3xl:             22px;   /* login wordmark                         */
  --text-stat:            26px;   /* the one big number on a stat tile      */
  --text-hero:            48px;   /* the login screen's emoji mark          */

  --weight-normal:        400;
  --weight-medium:        500;
  --weight-semibold:      600;
  --weight-bold:          700;
  --weight-black:         800;

  /* Leading tracks size INVERSELY: tight as text grows, looser for body copy.
     The two values below are body values; a 20-26px heading set at 1.45 reads
     as loose, disconnected lines. */
  --leading-tight:        1.15;   /* page titles, the stat number           */
  --leading-snug:         1.3;    /* modal titles and other short headings  */
  --leading-normal:       1.45;
  --leading-relaxed:      1.5;

  /* Tracking is SIZE-SPECIFIC, and the sign flips with size: small text wants
     a little air, large text reads too far apart and wants pulling in. Every
     value here used to be positive, so headings had no tracking at all while
     11px labels had 1px of it.
     In em, not px, so it scales with the text — .page-title h2 drops to 18px
     on a narrow screen (responsive.css) and a px value would not follow it. */
  --tracking-tight:       -.01em; /* 20px+ headings                         */
  --tracking-wide:        .4px;   /* micro-labels                           */
  --tracking-wider:       .5px;   /* table heads, section dividers          */
  --tracking-widest:      1px;    /* sidebar section headings               */

  /* ── SPACE ─────────────────────────────────────────────────────────────
     Used for padding, gap, and margin. The app predates this scale, so some
     legacy rules still carry off-scale values (9px, 11px, 18px). Those are
     fine to leave; just don't add new ones. */
  --space-1:              4px;
  --space-2:              6px;
  --space-3:              8px;
  --space-4:              10px;
  --space-5:              12px;
  --space-6:              14px;
  --space-7:              16px;
  --space-8:              20px;
  --space-9:              24px;
  --space-10:             32px;
  --space-12:             48px;

  /* The smallest box a pointer target may have (WCAG 2.5.8, Target Size). A
     size, not a gap — which is why it is not --space-9, though it matches. */
  --target-min:           24px;

  /* ── RADII ─────────────────────────────────────────────────────────────
     Roughly: the bigger the surface, the rounder the corner. */
  --radius-xs:            4px;    /* badges, tight chips                    */
  --radius-sm:            5px;    /* toolbar buttons inside the RTE         */
  --radius-md:            6px;    /* small inputs, page buttons             */
  --radius-lg:            7px;    /* form fields                            */
  --radius-xl:            8px;    /* buttons, search, alerts                */
  --radius-2xl:           10px;   /* cards, tables, stat tiles              */
  --radius-3xl:           12px;   /* modals, login card                     */
  --radius-sheet:         14px;   /* the phone bottom-sheet's top corners   */
  --radius-pill:          20px;   /* status badges, filter pills            */
  --radius-round:         50%;    /* avatars, thumbnail delete buttons      */

  /* ── ELEVATION ─────────────────────────────────────────────────────────
     Named for the thing that casts them, because that is how you pick one. */
  --shadow-header:        0 2px 8px rgba(0,0,0,.2);
  /* Reserved, not speculative: the six typeahead panels still carry this
     shadow inline, because they are shown/hidden by element.style.display and
     cannot take a class until those toggles move to classList. Same for
     --z-dropdown below, which they inline as z-index:50 (and 999 in two
     places — which is exactly the drift the scale exists to stop). */
  --shadow-dropdown:      0 6px 16px rgba(0,0,0,.12);
  --shadow-toast:         0 4px 20px rgba(0,0,0,.15);
  --shadow-drawer:        2px 0 18px rgba(0,0,0,.18);
  --shadow-modal:         0 25px 80px rgba(0,0,0,.25);
  --shadow-login:         0 20px 60px rgba(0,0,0,.3);
  --shadow-sticky-col:    -2px 0 4px rgba(0,0,0,.06);

  /* Every focusable control gets the same ring. Do not invent another one —
     an inconsistent focus style is an accessibility bug, not a style choice. */
  --focus-ring:           0 0 0 3px rgba(45,106,159,.12);
  --focus-outline:        2px solid var(--color-primary);

  /* ── MOTION ────────────────────────────────────────────────────────────
     Three speeds. Anything slower than --transition-slow feels broken on a
     table with 500 rows in it. */
  --transition-fast:      .15s;
  --transition-base:      .2s;
  --transition-slow:      .22s;

  /* ── LAYOUT ────────────────────────────────────────────────────────────
     The app shell's fixed dimensions. The sidebar drawer, the backdrop, and
     the responsive rules all key off --header-h; changing it in one place
     keeps them aligned. */
  --header-h:             56px;
  --sidebar-w:            220px;
  --sidebar-w-drawer:     262px;

  /* ── Z-INDEX ───────────────────────────────────────────────────────────
     A real scale, because "z-index: 9999" is how two people working in the
     same file end up with a modal behind a backdrop. Pick the layer you mean;
     never write a bare number.

     NOTE: CSS cannot use var() inside a @media query, so the breakpoints are
     documented rather than tokenised — 900px (tablet) and 640px (phone) are
     the only two, and they live in responsive.css. */
  --z-sticky-cell:        2;      /* a frozen first/last table column       */
  --z-sticky-head:        3;      /* ...and its header cell, above the body */
  --z-dropdown:           50;     /* typeahead panels inside a card         */
  --z-backdrop:           900;    /* dims the app behind the nav drawer     */
  --z-drawer:             950;    /* the mobile sidebar itself              */
  /* The billing lock's wall. Deliberately BETWEEN the drawer and the modal:
     it has to cover the whole app including the sidebar, but the card-entry
     modal is opened FROM it and must still come out on top — a wall that
     outranked --z-modal would hide the one thing it exists to send you to. */
  --z-billing-lock:       980;
  --z-modal:              1000;   /* modal overlay + dialog                 */
  --z-lightbox:           1050;   /* photo viewer — must outrank a modal, since
                                      photos are opened FROM modals             */
  --z-toast:              9999;   /* always the top layer, by definition    */

  /* ── RELEASE ───────────────────────────────────────────────────────────
     Identifies the release this stylesheet shipped in. Not a design value:
     nothing reads it, and nothing should. */
  --yw-rel: r9dd1f7;
}

/* ── HIGH CONTRAST ────────────────────────────────────────────────────────
   `prefers-contrast: more` is a real accessibility setting (Windows "Contrast
   theme", macOS "Increase contrast"), and nothing here answered it.

   Overridden at the TOKEN level rather than per component: every hairline,
   label and placeholder in the app already reads its colour from these, so
   one block moves all of them and no rule can be missed. Only colour changes
   — no size, spacing or layout — so a high-contrast user sees the same
   interface, just legibly.

   The values are promotions along the existing grey ramp, not new colours.
   Ratios are measured against BOTH grounds, because a token that clears 4.5:1
   on white can still fail on the page grey behind every card:

                             on #fff   on --color-bg #f6f8fa
     gray-400  #9ca3af          2.54            2.38   fails both
     gray-500  #6b7280          4.83            4.54   passes both
     gray-600  #4b5563          7.56            7.09   passes both
     gray-700  #374151         10.31            9.67   passes both

   --color-text-dim was gray-400 and is now gray-600 for EVERY reader, not just
   this block — see its definition above. What remains here is the further
   promotion for readers who asked for more contrast. --color-text-muted stays
   gray-500 by default: it clears AA on white, where it is overwhelmingly used,
   and 4.31:1 on the grey ground is a known 4% shortfall recorded rather than
   silently rounded away.

   Borders answer WCAG 1.4.11, which asks 3:1 of a non-text UI boundary. The
   whole existing border ramp fails that even at its strongest (gray-300 is
   1.47:1, gray-200 1.24:1 — a row separator that is less a line than a
   rumour), so each one is promoted to a grey that actually clears 3:1 while
   keeping the four steps visually distinct from each other. */
@media (prefers-contrast: more) {
  :root {
    /* Text: dim is already gray-600 for everyone, so here it goes one further;
       muted rises to clear 4.5:1 on the grey ground as well as on white. */
    --color-text-muted:     var(--gray-700);
    --color-text-dim:       var(--gray-700);

    /* Borders: a defined edge is what replaces a subtle tint at high contrast,
       so every surface that was distinguished by shade now has a real line. */
    --color-border-subtle:  var(--gray-500);   /*  4.83:1 */
    --color-border:         var(--gray-600);   /*  7.56:1 */
    --color-border-strong:  var(--gray-700);   /* 10.31:1 */
    --color-border-hover:   var(--gray-900);   /* 17.74:1 */
  }

  /* Surfaces separated only by a near-white fill get an explicit edge — the
     shade difference (#f8fafc vs #fff, 1.03:1) does not survive this setting. */
  .table-card, .modal, .stat-tile { border: 1px solid var(--color-border); }
  thead th, tfoot td { border-bottom: 2px solid var(--color-border-strong); }

  /* A focus ring is the one indicator a keyboard user cannot do without. */
  :focus-visible { outline: 3px solid var(--color-text-strong); outline-offset: 2px; }
}
