/* ============================================================================
   TOKENS — the portal design system (#519 Phase 1)

   Subject: a data-dense B2B portal where a shop owner configures an AI sales
   assistant and reads what it did. Opened daily, not admired once. Every choice
   below is tuned for repeat use: tight reading sizes, quiet surfaces, and one
   loud element (see --voice-*) reserved for the assistant's own voice.

   WHAT IS LOCKED AND WHY
   - The colour palette and the two typefaces are BRAND, captured from
     sailesman.ai in #282. See docs/superpowers/plans/282-palette-mapping.md.
     Their VALUES are fixed; their USE is this file's business.
   - Everything else here — type scale, spacing, radius, elevation, motion,
     z-index — did not exist before #519. The legacy flat sheet tokenised colour
     only, so every size in its 2184 lines was a raw literal. That hole is what
     this file fills.

   THEMING RULE — load-bearing.
   Every themed token must be declared in BOTH :root and [data-theme="dark"].
   A token declared only in :root silently keeps its LIGHT value in dark mode:
   there is no error, the page just quietly looks wrong. When adding a themed
   token, add it twice or not at all.
   ========================================================================= */

/* Brand fonts, self-hosted (SIL OFL 1.1) — copied from sailesman.ai to keep the
   portal self-contained (no marketing-CDN hotlink). DM Sans = body/UI,
   Outfit = display. This is the only copy since #519 Phase 5 put onboarding on
   this sheet and deleted the legacy flat one — every layout in the unit links
   tokens.css first, so the brand faces load on every surface. */
@font-face {
    font-family: "DM Sans";
    font-style: normal;
    font-weight: 400 700;
    font-display: swap;
    src: url("/fonts/dm-sans-latin-ext.woff2") format("woff2");
    unicode-range: U+0100-02BA, U+02BD-02C5, U+02C7-02CC, U+02CE-02D7, U+02DD-02FF, U+0304, U+0308, U+0329, U+1D00-1DBF, U+1E00-1E9F, U+1EF2-1EFF, U+2020, U+20A0-20AB, U+20AD-20C0, U+2113, U+2C60-2C7F, U+A720-A7FF;
}
@font-face {
    font-family: "DM Sans";
    font-style: normal;
    font-weight: 400 700;
    font-display: swap;
    src: url("/fonts/dm-sans-latin.woff2") format("woff2");
    unicode-range: U+0000-00FF, U+0131, U+0152-0153, U+02BB-02BC, U+02C6, U+02DA, U+02DC, U+0304, U+0308, U+0329, U+2000-206F, U+20AC, U+2122, U+2191, U+2193, U+2212, U+2215, U+FEFF, U+FFFD;
}
@font-face {
    font-family: "Outfit";
    font-style: normal;
    font-weight: 500 800;
    font-display: swap;
    src: url("/fonts/outfit-latin-ext.woff2") format("woff2");
    unicode-range: U+0100-02BA, U+02BD-02C5, U+02C7-02CC, U+02CE-02D7, U+02DD-02FF, U+0304, U+0308, U+0329, U+1D00-1DBF, U+1E00-1E9F, U+1EF2-1EFF, U+2020, U+20A0-20AB, U+20AD-20C0, U+2113, U+2C60-2C7F, U+A720-A7FF;
}
@font-face {
    font-family: "Outfit";
    font-style: normal;
    font-weight: 500 800;
    font-display: swap;
    src: url("/fonts/outfit-latin.woff2") format("woff2");
    unicode-range: U+0000-00FF, U+0131, U+0152-0153, U+02BB-02BC, U+02C6, U+02DA, U+02DC, U+0304, U+0308, U+0329, U+2000-206F, U+20AC, U+2122, U+2191, U+2193, U+2212, U+2215, U+FEFF, U+FFFD;
}

:root {
    /* ---------------------------------------------------------------- COLOUR
       Brand palette, light theme (#282). Values locked — do not edit; a
       contrast failure is fixed by changing WHICH token is applied where, or
       by adding a text-specific variant, never by changing a brand colour. */
    --bg: #fafaf9;
    --surface: #ffffff;
    --surface-2: #f2f1ea;
    --border: #e8e7d8;
    --text: #042940;
    /* MEASURED, not chosen — and a deliberate ~4% deviation from the #282 brand
       muted ink (#71717a). That value clears AA on --surface (4.83:1) and --bg
       (4.63:1) but reaches only 4.27:1 on --surface-2, so it FAILED AA the moment
       secondary text sat inside an inset — a hover row, a code block, the
       readiness strip. #282 only ever checked muted ink against --surface, so the
       pairing was a latent hole in the palette rather than a misuse of it.
       #68686f clears every surface in this theme: 5.53:1 on --surface, 5.29:1 on
       --bg, 4.88:1 on --surface-2. Darkening can only RAISE contrast, so no
       existing consumer can regress. Keep these numbers with the value: it is a
       measured floor, and lightening it back toward the brand value silently
       restores the failure. See #525. */
    --text-muted: #68686f;
    --accent: #005c53;
    --accent-hover: #00473f;
    --accent-tint: rgba(0, 92, 83, 0.08);

    /* Accent used as TEXT/icon colour, as opposed to a fill. Identical to
       --accent in light; the dark override is lightened because solid --accent
       as text measures ~3.8:1 on dark surfaces, short of AA. Keep the split. */
    --accent-text: #005c53;

    /* Accent as text ON AN --accent-tint FILL — the active nav item, a status
       badge, the preview's snapshot badge, the promoted-first toggle. A THIRD
       variant rather than a reuse of --accent-text, for the same reason
       --accent-text exists at all: the tint raises the background toward the
       ink, so the pair that clears AA on a plain surface does not clear it on
       a tinted one. Measured in light, where --accent-text is already legal on
       every tinted surface (6.97:1 over --surface, 6.66:1 over --bg, 6.18:1
       over --surface-2) — so this is an alias here and a real value in dark.
       #519 Phase 4. */
    --accent-on-tint: #005c53;

    /* Accent as a FILL under white label text, on hover. Split from
       --accent-hover because that token is doing two opposite jobs: as TEXT on
       a page surface it must move AWAY from the background (lighter in dark),
       and as a FILL under white it must move TOWARD it (darker in dark). Light
       wants the same darkening for both, so here they are the same value and
       the split costs nothing; dark is where they diverge. #519 Phase 4. */
    --accent-fill-hover: #00473f;

    --danger: #dc2626;
    --danger-tint: rgba(220, 38, 38, 0.12);
    --danger-bg: #fff1f2;
    --danger-border: #fecdd3;
    --danger-text: #9f1239;
    --success-bg: #ecfdf5;
    --success-border: #a7f3d0;
    --success-text: #065f46;

    /* THE VOICE — the one deliberately loud thing in the system.
       Brand ships a CTA lime (#dbf227) that #282 parked: the portal hardcodes
       white button labels, and white-on-lime is ~1.1:1. #519 brings it back
       under the only condition it is ever legible — as a FILL carrying navy
       ink (11.99:1), never as a foreground. That constraint is the point:
       lime measures 1.25:1 against white, so it can carry no meaning of its
       own in light mode and is used for nothing but drawing the eye.
       It is deliberately IDENTICAL in both themes. Because the chip supplies
       its own background it needs no dark variant, which makes it the single
       element on the page that does not shift between light and dark — the
       constant that says "Sailesman is speaking". See .says in components.css.
       Reserved: nothing else in the portal may use these two tokens. */
    --voice: #dbf227;
    --voice-ink: #042940;

    /* Chart marks. Two SEPARATE single-series charts, so these never sit
       adjacent and need no CVD separation from each other — see the chart
       block in components.css for why the combined chart was split. Both clear
       the 3:1 mark floor against their surface.
       There are deliberately no --chart-axis/--chart-grid tokens: line.js
       strokes the axes with currentColor, so the axis weight comes from
       .chart__plot's own colour. Tokens for them existed unused until #519
       Phase 3 and were removed rather than given a contrived consumer. */
    --chart-volume: #005c53;   /* 7.91:1 on --surface */
    --chart-error: #dc2626;    /* 4.83:1 on --surface */

    /* Focus. --accent on light surfaces measures 6.98:1 even over the darkest
       inset, comfortably past the 3:1 non-text floor. */
    --focus-ring: var(--accent);

    /* ------------------------------------------------------------ ELEVATION
       Opinion: on a warm sand palette, drop shadows read as grime. Structure
       is carried by BORDER + a surface step, and shadow is reserved for things
       that genuinely float above the page. A panel therefore has no shadow at
       rest — only the drawer and any popover do. */
    --shadow-raised: 0 1px 2px rgba(4, 41, 64, 0.06), 0 2px 8px rgba(4, 41, 64, 0.05);
    --shadow-overlay: 0 12px 32px rgba(4, 41, 64, 0.16);

    color-scheme: light;
}

[data-theme="dark"] {
    /* Teal-navy dark, derived from sailesman.ai's own dark theme (#282). */
    --bg: #021824;
    --surface: #042940;
    --surface-2: #08314b;
    --border: #0a3550;
    --text: #e8f0f3;
    /* Deliberately NOT darkened alongside the light theme's #525 fix: dark never
       had that problem. Clears AA on every surface in this block — 7.37:1 on
       --bg, 6.12:1 on --surface, 5.51:1 on --surface-2. */
    --text-muted: #7aadbd;
    --accent: #14857a;
    --accent-hover: #22a899;
    --accent-tint: rgba(74, 173, 160, 0.15);
    --accent-text: #4aada0;   /* lightened: 5.57:1 on --surface */

    /* MEASURED, like --text-muted's floor. The tint composites to #0e3d4e over
       --surface, #124458 over --surface-2 and #0d2e37 over --bg, and
       --accent-text on those measures 4.33 / 3.90 / 5.31 — so the active nav
       item, the status badge, the snapshot badge and the promoted-first toggle
       were all BELOW AA in dark while passing comfortably in light. (Only the
       three rendered in the scanned states were caught automatically; the other
       two are the same pairing and are fixed with them rather than left to be
       found later.)
       This value is --accent-text's own hue and saturation (172.1deg, 0.401)
       lightened until it clears 4.5:1 on the WORST of the three — 5.00:1 over
       --surface-2, 5.55:1 over --surface, 6.81:1 over --bg — which puts dark on
       the same footing as light's 6.18-6.97 band. Derived from the palette
       rather than picked, and lightening can only raise contrast against these
       backgrounds, so no existing consumer regresses. See #519 Phase 4. */
    --accent-on-tint: #6ec1b6;

    /* White on --accent-hover (#22a899) measures 2.95:1 — a clear AA failure on
       the portal's primary call to action, and invisible to an automated scan
       because no scanner walks hover states. The cause is the double duty
       described in :root: dark's --accent-hover is LIGHTER than --accent, which
       is right for hover text and wrong under a white label. This darkens
       instead, the way light's hover already does. White measures 6.02:1 on it
       (vs 4.50:1 on --accent itself, which only just clears), and it reads 1.34:1
       against --accent, so the hover step is still plainly visible. */
    --accent-fill-hover: #116f66;

    --danger: #ff6b6b;
    --danger-tint: rgba(248, 113, 113, 0.18);
    --danger-bg: rgba(248, 113, 113, 0.1);
    --danger-border: rgba(248, 113, 113, 0.4);
    --danger-text: #fca5a5;
    --success-bg: rgba(16, 185, 129, 0.12);
    --success-border: rgba(16, 185, 129, 0.4);
    --success-text: #6ee7b7;

    /* --voice / --voice-ink are deliberately NOT overridden. See :root. */

    --chart-volume: #14857a;   /* 3.34:1 on --surface — clears the 3:1 mark floor */
    --chart-error: #ff6b6b;    /* 5.42:1 on --surface */

    /* --accent is only 4.03:1 on the dark page; --accent-text is brighter and
       makes the ring unmistakable against both --surface and --surface-2. */
    --focus-ring: var(--accent-text);

    --shadow-raised: 0 1px 2px rgba(0, 0, 0, 0.3), 0 2px 8px rgba(0, 0, 0, 0.28);
    --shadow-overlay: 0 12px 32px rgba(0, 0, 0, 0.45);

    color-scheme: dark;
}

:root {
    /* ------------------------------------------------------------ TYPOGRAPHY
       Two brand faces, divided by JOB rather than by the usual heading/body
       split:

         Outfit (display, 500–800) — things the SYSTEM ASSERTS: page and panel
           titles, and display-size measurements (KPI values). Its geometric
           figures make a number read as data.
         DM Sans (body, 400–700) — things you READ or SCAN: prose, labels,
           form fields, and every table cell.

       The division matters at one specific place: KPI values are Outfit,
       table numbers are NOT. A table needs tabular figures at 14px for column
       alignment and scanning; Outfit's display character costs legibility
       there. See .kpi-tile__value vs .data-table in components.css. */
    --font-body: "DM Sans", "Segoe UI", -apple-system, BlinkMacSystemFont, Roboto, sans-serif;
    --font-display: "Outfit", "DM Sans", "Segoe UI", sans-serif;

    /* Scale. Deliberately NOT one uniform modular ratio. A dense working tool
       needs tight steps where text is READ (11 → 12.5 → 14 → 15, ratios of
       roughly 1.09–1.14, so hints and sub-headings sit close to body without
       fragmenting the page) and a real jump where text RANKS (15 → 18 → 26 →
       32, ratios of 1.2–1.44, so a page title is unmistakably a page title).
       One ratio across the whole range would make either the small end noisy
       or the large end timid. */
    --text-micro: 0.6875rem;   /* 11px   — eyebrow / overline, uppercase */
    --text-xs: 0.78125rem;     /* 12.5px — field hints, captions, table meta */
    --text-sm: 0.875rem;       /* 14px   — BODY. the portal's reading size */
    --text-md: 0.9375rem;      /* 15px   — sub-section heading (h3), lead prose */
    --text-lg: 1.125rem;       /* 18px   — panel title (h2) */
    --text-xl: 1.625rem;       /* 26px   — page title (h1) */
    --text-2xl: 2rem;          /* 32px   — KPI value */

    /* h3 at 15px is barely above the 14px body on purpose: at sub-section
       level the FACE CHANGE (Outfit 600) does the ranking, not the size. A
       settings page with a dozen sub-headings should not shout twelve times. */

    --leading-tight: 1.2;      /* display numbers, page title */
    --leading-snug: 1.3;       /* headings */
    --leading-normal: 1.5;     /* body, table cells */
    --leading-relaxed: 1.6;    /* prose blocks: help text, install steps */

    --tracking-tight: -0.015em; /* Outfit needs it back at >=26px */
    --tracking-wide: 0.08em;    /* the 11px uppercase eyebrow needs it opened */

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

    /* --------------------------------------------------------------- SPACE
       4px grid. The number IS the multiple of 4, so --space-6 is 24px and the
       scale needs no lookup table. Steps thin out above 24px because layout
       gaps that large are structural and only a few sizes are ever right. */
    --space-05: 2px;
    --space-1: 4px;
    --space-2: 8px;
    --space-3: 12px;
    --space-4: 16px;
    --space-5: 20px;
    --space-6: 24px;
    --space-8: 32px;
    --space-10: 40px;
    --space-12: 48px;
    --space-16: 64px;

    /* -------------------------------------------------------------- RADIUS
       A scale with an opinion: the container is always SOFTER than what sits
       inside it. A 10px panel holding 3px inputs reads as considered; the flat
       4px-everywhere of the old sheet read as no decision at all. */
    --radius-sm: 3px;      /* inputs, checkboxes, chips, swatches */
    --radius-md: 6px;      /* buttons, table wrappers, insets */
    --radius-lg: 10px;     /* panels and cards */
    --radius-pill: 999px;  /* badges only */

    /* -------------------------------------------------------------- MOTION
       Short and snappy. This is a tool people use repeatedly, so on the SHELL
       animation is feedback, never spectacle — nothing there animates layout or
       entrance. The one exception is onboarding, seen once, where the motion is
       the content: scene.css composes entrances and an ambient pulse from these
       easings and its own durations, which is why neither step below is an
       entrance duration. base.css disables all of it under prefers-reduced-motion. */
    --motion-fast: 120ms;  /* hover / focus feedback */
    --motion-base: 180ms;  /* state changes: drawer, disclosure */
    --ease-out: cubic-bezier(0.2, 0.6, 0.35, 1);

    /* ------------------------------------------------------------- Z-INDEX
       The unit had no z-scale; the values below are the ones already in force
       across the legacy flat sheet and its page JS, now named. Gaps of 10 leave room to
       insert a layer without renumbering. */
    --z-sticky: 20;    /* in-page sticky layers, e.g. the configuration nav */
    --z-topbar: 30;    /* the shell topbar — must out-stack --z-sticky */
    --z-scrim: 40;     /* mobile drawer scrim */
    --z-sidebar: 50;   /* mobile drawer itself */
    --z-overlay: 60;   /* popovers, the guided-tour popover */

    /* -------------------------------------------------------------- LAYOUT */
    /* Load-bearing well beyond the bar itself: the sidebar brand row matches
       it so both header rows line up, and anything sticking below it offsets
       by it. Size against the token, never a fresh literal. */
    --topbar-height: 56px;
    --sidebar-width: 244px;

    /* Prose measure. Help text, notices and install steps are capped at this;
       tables, charts and toolbars are not. The old sheet ran every paragraph
       to the container edge, which is why its copy read like documentation. */
    --measure: 68ch;
}
