/* ──────────────────────────────────────────────────────────────────────────
   tokens.css — the single, theme-agnostic foundation layer
   ----------------------------------------------------------------------------
   WHY THIS FILE EXISTS
   The app grew with NINE separate `:root` blocks (style.css, dashboard.css,
   field.css, …) and ~3,800 hardcoded `px` literals. There was no spacing scale
   and no type scale, so every panel re-invented its own rhythm and long strings
   / unusual viewports caused layout shift and clipping.

   This file is loaded *first* on every page, BEFORE the page stylesheet and any
   theme (classic / ui-next / theme-calm). It only DEFINES NEW token names
   (--sp-*, --text-*, --leading-*, --ease-*, --dur-*, --z-*) — it never collides
   with the existing colour/radius/shadow tokens, so it is purely additive and
   cannot regress the current look. Themes keep overriding colours/radii on top.

   The values are chosen to map 1:1 onto the px literals already in the codebase
   (4 / 6 / 8 / 10 / 12 / 14 / 16 / 20 / 24 …) so migration is a safe,
   like-for-like substitution rather than a redesign.
   ────────────────────────────────────────────────────────────────────────── */

:root {
  /* ── Spacing scale (4px base grid) ───────────────────────────────────────
     Use for padding / margin / gap. Picked to match the literals already in
     use so swaps are visually identical. Prefer these over raw px. */
  --sp-0:    0;
  --sp-px:   1px;
  --sp-0_5:  2px;
  --sp-1:    4px;
  --sp-1_5:  6px;
  --sp-2:    8px;
  --sp-2_5:  10px;
  --sp-3:    12px;
  --sp-3_5:  14px;   /* very common gap/padding in the current UI */
  --sp-4:    16px;
  --sp-5:    20px;
  --sp-6:    24px;
  --sp-7:    28px;
  --sp-8:    32px;
  --sp-10:   40px;
  --sp-12:   48px;
  --sp-16:   64px;

  /* ── Job-status colour family ────────────────────────────────────────────
     Shared semantic palette for dispatch job statuses (chips, badges, legend,
     records). Single source of truth; mirrors JOB_STATUS in
     js/scheduler/state.js — keep the two in sync. Chosen for mutual contrast:
     teal en-route no longer collides with the blue accent, and
     scheduled/cancelled are no longer near-identical greys. */
  --st-scheduled:   #64748b;   /* slate — booked */
  --st-en_route:    #14b8a6;   /* teal — moving */
  --st-in_progress: #f59e0b;   /* amber — on site */
  --st-complete:    #22c55e;   /* green — done */
  --st-no_show:     #ef4444;   /* red — missed */
  --st-cancelled:   #94a3b8;   /* muted — called off */
  --st-invoiced:    #8b5cf6;   /* violet — billed */

  /* ── Document-status colour family (quotes + invoices) ───────────────────
     Sibling of --st-* above, for the neuralbooks lifecycle. Single source of
     truth for quote/invoice badges (js/core/ui/badge.js + css/ui.css) —
     replaces the inline hex maps formerly duplicated in quotes-page.js and
     invoices-page.js. Hues are shared with --st-* on purpose so the same
     meaning carries one colour across every app:
       green  = money-good / done      amber = waiting / needs attention
       red    = act now                blue  = in flight (customer's court)
       slate  = not-yet-real (draft/void)   cyan = engagement (viewed)
     Chosen to stay legible on both the light panel and the dark (books
     default) panel when paired with a ~14% tint of themselves. */
  --doc-draft:     #94a3b8;   /* slate — not yet real */
  --doc-sent:      #60a5fa;   /* blue  — in the customer's court */
  --doc-viewed:    #22d3ee;   /* cyan  — customer opened it */
  --doc-accepted:  #22c55e;   /* green — won */
  --doc-open:      #60a5fa;   /* blue  — awaiting payment */
  --doc-partial:   #f59e0b;   /* amber — partially paid */
  --doc-paid:      #22c55e;   /* green — settled */
  --doc-overdue:   #ef4444;   /* red   — act now */
  --doc-declined:  #ef4444;   /* red   — lost */
  --doc-expired:   #f59e0b;   /* amber — lapsed */
  --doc-void:      #8b93a3;   /* muted — withdrawn */

  /* ── Type scale ──────────────────────────────────────────────────────────
     Small UI text stays FIXED on purpose — dense data tables should not reflow
     fluidly with the viewport (that hurts legibility and alignment). Only the
     display/heading sizes scale fluidly via clamp() so big headings stay
     proportional on phones and ultrawide monitors without media queries. */
  --text-2xs:  10px;
  --text-xs:   11px;
  --text-sm:   12px;
  --text-base: 13px;   /* app body default */
  --text-md:   14px;
  --text-lg:   15px;
  --text-xl:   16px;
  --text-2xl:  18px;
  /* Fluid display sizes: clamp(min, preferred-vw, max). */
  --text-h3:   clamp(18px, 1.2vw + 14px, 22px);
  --text-h2:   clamp(20px, 1.8vw + 14px, 28px);
  --text-h1:   clamp(24px, 3vw + 16px, 40px);
  --text-display: clamp(32px, 5vw + 16px, 64px);

  --leading-tight:  1.2;
  --leading-snug:   1.35;
  --leading-normal: 1.55;
  --leading-relaxed: 1.7;

  --weight-regular:  400;
  --weight-medium:   500;
  --weight-semibold: 600;
  --weight-bold:     700;

  /* ── Motion ──────────────────────────────────────────────────────────────
     Names the existing --spring/--transition tokens did not provide. The
     primitives (states.js, disclosure.js, stagger.js) read these so motion is
     consistent and tunable from one place. */
  --dur-1: 80ms;    /* taps / instant state flips */
  --dur-2: 150ms;   /* default — matches the legacy --transition */
  --dur-3: 240ms;   /* panels, popovers, list entry */
  --dur-4: 360ms;   /* large / modal-scale movement */
  --ease-standard: cubic-bezier(.2, .8, .2, 1);   /* matches legacy --spring */
  --ease-out:      cubic-bezier(0, 0, .2, 1);
  --ease-in-out:   cubic-bezier(.4, 0, .2, 1);
  --ease-emphasis: cubic-bezier(.2, .9, .1, 1);

  /* ── Z-index ladder ──────────────────────────────────────────────────────
     A named ladder prevents the magic-number z-index wars (the app already has
     z:30 topbars, ad-hoc modal overlays, toasts on body, …). */
  --z-base:     0;
  --z-sticky:   20;
  --z-topbar:   30;
  --z-dropdown: 100;
  --z-overlay:  900;
  --z-modal:    1000;
  --z-toast:    1100;

  /* Standard focus-ring offset, so every focusable shares one rhythm. */
  --focus-ring: 2px solid var(--accent, #3b82f6);
  --focus-offset: 2px;
}

/* ── Breakpoint ladder (reference only) ──────────────────────────────────────
   CSS custom properties cannot be read inside `@media` conditions, so these
   are not literal vars to consume — they are the canonical pixel values every
   stylesheet should match when writing a media query, instead of each file
   picking its own nearby number. Match the existing usage already converged
   on in dashboard.css / ui-next.css:
     --bp-laptop : 1200px  (dense desktop grids start losing readability)
     --bp-tablet :  768px  (primary mobile breakpoint — drawer nav, stacked grids)
     --bp-phone  :  480px  (phone — collapse remaining multi-col grids to 1)
   New responsive CSS should reuse 1200 / 768 / 480 rather than introducing a
   fourth nearby value (e.g. 800px, 720px, 760px). */

/* ── Global defensive defaults ───────────────────────────────────────────────
   Cheap, safe rules that stop the most common "extreme content" breakages
   (long unbroken strings, oversized media, runaway localized labels) WITHOUT
   touching component layout. We intentionally do NOT set a blanket
   `* { min-width: 0 }` (that would silently change flex behaviour app-wide);
   instead we provide the opt-in `.u-min0` utility below for flex children that
   need to be allowed to shrink. */
img, svg, video, canvas { max-width: 100%; }
/* Headings/paragraphs should wrap mid-word rather than overflow their box. */
h1, h2, h3, h4, h5, h6, p, li, dd, dt, figcaption { overflow-wrap: break-word; }

/* ── Accessible-motion guard ─────────────────────────────────────────────────
   Honour the OS "reduce motion" setting globally. Animations collapse to a
   single near-instant frame instead of being removed (so state still changes
   visibly), and smooth-scroll is disabled. Individual primitives ALSO check
   this in JS, but the CSS guard covers any animated element we miss. */
@media (prefers-reduced-motion: reduce) {
  *, *::before, *::after {
    animation-duration: 0.01ms !important;
    animation-iteration-count: 1 !important;
    transition-duration: 0.01ms !important;
    scroll-behavior: auto !important;
  }
  /* Functional loading spinners are exempt: a frozen single frame reads as
     "stuck/broken", whereas a steady rotation (no flashing) conveys ongoing
     work and is the accepted reduced-motion exception. Covers .spin/.ti-spin,
     the login .spinner, Tabler ti-loader icons, and inline animation:spin. */
  .spin, .ti-spin, .spinner,
  [class*="ti-loader"], [style*="spin"] {
    animation-duration: 0.8s !important;
    animation-iteration-count: infinite !important;
  }
}

/* ── Defensive layout utilities (opt-in) ─────────────────────────────────────
   Reach for these instead of re-writing the same truncation/wrap rules inline.
   They are the antidote to text clipping under long strings / localization. */
.u-min0      { min-width: 0; min-height: 0; }        /* let a flex/grid child shrink */
.u-truncate  { min-width: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.u-clamp-1,
.u-clamp-2,
.u-clamp-3   { display: -webkit-box; -webkit-box-orient: vertical; overflow: hidden; }
.u-clamp-1   { -webkit-line-clamp: 1; }
.u-clamp-2   { -webkit-line-clamp: 2; }
.u-clamp-3   { -webkit-line-clamp: 3; }
.u-break     { overflow-wrap: anywhere; word-break: break-word; }
.u-nowrap    { white-space: nowrap; }

/* A visually-hidden helper for screen-reader-only labels (used by the new
   a11y primitives when a control has only an icon). */
.sr-only {
  position: absolute !important; width: 1px; height: 1px;
  padding: 0; margin: -1px; overflow: hidden; clip: rect(0, 0, 0, 0);
  white-space: nowrap; border: 0;
}

/* Skip link: visually hidden until focused, then pinned to the top-left so
   keyboard users can jump straight past the chrome to the main content. */
.skip-link {
  position: absolute; left: -9999px; top: 0; z-index: var(--z-max, 9999);
  padding: var(--sp-2_5) var(--sp-4); background: var(--bg-elevated, #111827); color: #fff;
  border-radius: 0 0 8px 0; font-size: var(--text-md, 14px); font-weight: 600;
  text-decoration: none;
}
.skip-link:focus { left: 0; outline: 2px solid #fff; outline-offset: 2px; }

/* Baseline keyboard-focus indicator for every page (some pages — e.g. the
   dashboard — don't load style.css's global rule, so their buttons and the
   role="button" elements upgraded by core/clickable.js had no visible ring).
   Zero specificity via :where() so any existing component focus style wins.
   :focus-visible matches keyboard/AT focus only, never a mouse click. */
:where(a, button, [role="button"], [role="tab"], [role="link"],
       input, select, textarea, summary, [tabindex]):focus-visible {
  outline: var(--focus-ring);
  outline-offset: var(--focus-offset);
}
