/* The hub's design tokens: the ONE place every visual value is defined: colours (light, dark, high contrast), radii, the text scale, weights,
   line heights, font stacks, spacing, sizes, border widths, focus rings, strokes, shadows, durations, easings, layers, opacity and the
   breakpoints. Every other stylesheet we write says var(--token) and nothing else; change a token here and it changes everywhere.
   Loaded by the dashboard (index.html), the sign-in page (login.html) and the sign-out page (logoutPage), BEFORE their own
   stylesheet. Served to a signed-out visitor too (authAssets in server_adminserver.go).
   Only custom properties (and color-scheme) in :root rules belong in this file: no selector that styles an element, nothing that loads
   another file. It is the only stylesheet allowed to write a literal; web/test/css-literals.test.mjs fails the build on one anywhere
   else (third-party CSS under static/vendor/ is exempt). How to add a token, the naming, the guard and the Figma export:
   docs/design-tokens.md. */
:root {
    /* Lets the browser theme native chrome we don't otherwise control —
       checkbox/radio rendering, scrollbars, <select> dropdown popups —
       to match. Without this, a dark page can still get a jarring
       white native <select> popup or light scrollbar track. */
    color-scheme: light;
    --primary: #2563eb;
    --primary-dark: #1e40af;
    --success: #10b981;
    --warning: #f59e0b;
    --danger: #ef4444;
    --sidebar-bg: #0f172a;
    --sidebar-hover: #1e293b;
    --sidebar-active-bg: rgba(96,165,250,.16); /* the current item: a tint over --sidebar-bg, white text 12:1+ (contrast-checked, css-contrast.test.mjs) */
    --sidebar-active-bar: #60a5fa;             /* its 3px accent bar: 6.9:1 on --sidebar-bg (UI needs 3:1) */
    --sidebar-text: #94a3b8;      /* 7.0:1 on --sidebar-bg */
    --sidebar-field-border: #64748b; /* the search trigger's outline: 3.8:1 on --sidebar-bg */
    --sidebar-label: #8c9bb0;     /* section labels (SERVICES, AGENTS, ADMIN): 6.3:1; was #64748b, 3.75:1 */
    --sidebar-text-active: #ffffff;
    --sidebar-width: 230px;
    --bg: #f1f5f9;
    --card-bg: #ffffff;
    --surface: #ffffff;
    --surface-muted: #f8fafc;
    --border: #e2e8f0;
    --text: #1e293b;
    --text-muted: #5a6b82;   /* 4.97:1 on --bg, 5.4:1 on white (was #64748b: 4.34:1 on --bg: the footer and panel descriptions) */
    --radius: var(--radius-md);   /* legacy name: same step as the scale below */
    --shadow: var(--shadow-sm);   /* legacy name: same step as the scale below */
    --topbar-height: 52px;
    --text-mobile-meta: .8125rem;
    --bp-short-height: 500px;

    /* Type scale. Font sizes had drifted to 19 near-duplicate ad hoc
       values (.72rem, .75rem, .78rem, 11px, 12px... all meaning
       roughly "secondary text" in different components written at
       different times) — consolidated to these 7 steps. Existing
       declarations below were mapped to their nearest step; several
       aliases are exact (12px = .75rem, 14px = .875rem at the 16px
       root) so those cost nothing. A few (.72rem→xs, .78rem→xs,
       .84rem→sm, 11px→xs, 13px→sm) shift by roughly half a pixel —
       intentional, that's the inconsistency being fixed. */
    --text-2xs: .7rem;    /* ~11px — micro badges, TRL/mesh pills, chip counts */
    --text-xs: .75rem;    /* ~12px — secondary/meta text, table cells, timestamps */
    --text-sm: .8rem;     /* ~13px — compact UI: filters, chips, form hints */
    --text-base: .875rem; /* ~14px — default body text, buttons, nav items */
    --text-lg: 1rem;      /*  16px — card headings, sub-titles */
    --text-xl: 1.2rem;    /* panel header on mobile */
    --text-2xl: 1.4rem;   /* panel title (the page's h1) on desktop */

    /* Semantic pastel pairs (badges, TRL pills, entity tags, alerts).
       Named by meaning, not color, so a dark override only has to
       redefine these ~20 variables instead of hunting down every
       component that happened to hardcode a light-mode-only hex pair. */
    --success-bg: #dcfce7; --success-text: #166534;
    --danger-bg: #fee2e2;  --danger-text: #991b1b;
    --warning-bg: #fef3c7; --warning-text: #92400e;
    --info-bg: #dbeafe;    --info-text: #1e40af;
    --purple-bg: #f3e8ff;  --purple-text: #6b21a8;
    --rose-bg: #ffe4e6;    --rose-text: #9f1239;
    --loading-bg: #e0f2fe; --loading-text: #075985;
    --alert-warning-bg: #fef9c3; --alert-warning-border: #fde047; --alert-warning-text: #713f12;
    --row-hover-bg: #eef2ff;
    --input-bg: #ffffff;

    /* Buttons: ONE system (see "Buttons" below). Variants primary / secondary / ghost / danger, sizes md (default) and
       sm. Every pair is measured: label text >= 4.5:1, secondary border >= 3:1 on white, focus ring >= 3:1 on --bg and
       on every surface. Redefined in the dark block; the same names serve prefers-contrast and forced-colors. */
    --btn-radius: var(--radius-md);
    --btn-h-md: var(--control-h-md);  --btn-px-md: 16px;
    --btn-h-sm: var(--control-h-sm);  --btn-px-sm: 10px;
    --btn-primary-bg: #2563eb; --btn-primary-hover: #1d4ed8; --btn-primary-active: #1e40af; --btn-primary-fg: #ffffff;
    --field-border: #7b8aa0;   /* the outline of buttons and form fields (inputs, selects, segmented controls): 3.5:1 on white, 3.2:1 on --bg */
    --btn-border: var(--field-border);
    --btn-hover-bg: #f1f5f9;   /* secondary hover/active fill */
    --btn-ghost-fg: var(--info-text); --btn-ghost-hover-bg: var(--row-hover-bg);
    --btn-danger-fg: #b91c1c;  --btn-danger-border: #b91c1c; --btn-danger-hover-bg: var(--danger-bg);
    --focus-ring: #1d4ed8;

    /* Identity chip (identity-chip.ts): 4 product colour slots, each a bg/fg pair with
       contrast >= 6:1 (WCAG AA needs 4.5), plus a muted text colour for the short id/host
       that stays >= 5.6:1 on --card-bg, --surface-muted and --bg. */
    --idchip-1-bg: #fef3c7; --idchip-1-fg: #92400e;
    --idchip-2-bg: #dcfce7; --idchip-2-fg: #166534;
    --idchip-3-bg: #dbeafe; --idchip-3-fg: #1e40af;
    --idchip-4-bg: #f3e8ff; --idchip-4-fg: #6b21a8;
    --idchip-muted: #556274;

    /* ── design tokens (hub cleanup batch 2): the ONE scale ────────────────────────────────────────────────────────────
       New CSS uses these; a literal radius, font size, spacing or shadow in a new rule is a review comment. The steps were
       taken from what the stylesheet already used most, so adopting one moves a corner by <= 2 px, not a layout.
       Radius   sm 4   kbd, code, small tags, inner details | md 8 buttons, fields, cards, menus, panels, bars
                lg 12  dialogs, the command palette, message bubbles, bottom sheets | pill 999 badges, chips, counts.
                50% stays a literal: it means "circle", not a size.
       Text     --text-2xs .. --text-2xl above: 11 / 12 / 13 / 14 / 16 / 19 / 22 px. Field labels --text-sm, body and
                controls --text-base, the page title --text-2xl. Nothing is smaller than --text-2xs.
       Space    4 / 8 / 12 / 16 / 24 on a 4 px grid: --space-2 inside a group, --space-4 between groups, --space-6 between
                the sections of a page.
       Shadow   sm = a card resting on the page; md = something floating above it (menus, popovers, dialogs).
       Colour   meaning, not hue: --success/--warning/--danger/--info(-bg/-text) for status, --primary and the --btn-* set
                for actions, --text/--text-muted, --surface/--surface-muted/--bg/--border for structure,
                --scrim for what dims the page behind a dialog, --on-accent for text on a filled accent. */
    --radius-sm: 4px;
    --radius-md: 8px;
    --radius-lg: 12px;
    --radius-pill: 999px;
    --space-1: 4px;
    --space-2: 8px;
    --space-3: 12px;
    --space-4: 16px;
    --space-6: 24px;
    --shadow-sm: 0 1px 2px rgba(15,23,42,.06);      /* a card at rest: the border does the work, the shadow only lifts it a hair (was two stacked layers, .1 + .06) */
    --shadow-md: 0 4px 16px rgba(15,23,42,.14);     /* floating: menus, popovers, the command palette (was 8px 24px at .18) */
    --card-pad: var(--space-4);                     /* the ONE padding of a card (was 12, 14, 16 and 20 px depending on the page) */
    --card-pad-compact: var(--space-3);             /* tiles and list items that sit in rows */
    --scrim: rgba(15,23,42,.55);
    --on-accent: #ffffff;
    --paper: #ffffff;                              /* the colour of a web page: the proxy preview frame stays white in the dark theme */
    --primary-halo: rgba(37,99,235,.12);           /* the soft ring around a focused field */
    --primary-wash: rgba(37,99,235,.04);           /* a drop target while a file is dragged over it */
    --shadow-drawer: 4px 0 24px rgba(0,0,0,.3);    /* the mobile sidebar when it is open (it casts to the right) */
    --sidebar-text-hover: #cbd5e1;                 /* sidebar text under the pointer */
    --sidebar-text-strong: #e2e8f0;                /* sidebar text that needs more than --sidebar-text (brand sub-line, the search trigger, group hover) */
    --sidebar-line: rgba(255,255,255,.07);         /* hairlines on the dark sidebar */
    --sidebar-line-strong: rgba(255,255,255,.1);   /* the outline of a key cap on the dark sidebar */
    --sidebar-veil: rgba(255,255,255,.05);         /* a resting field or key cap on the dark sidebar */
    --sidebar-veil-hover: rgba(255,255,255,.08);   /* the same, under the pointer */
    --idchip-letter-bg: rgba(255,255,255,.55);     /* the letter disc inside a product chip (dark: .12 below) */

    /* ── graph views (static/graph.css, graph-svg.ts, thread-timeline.ts, swimlane.ts) ────────────────────────────────
       Every text and mark colour is >= 4.5:1 (text) or >= 3:1 (graphics) on --card-bg, light and dark. */
    --graph-edge: #475569;
    --graph-node-stroke: #475569;
    --graph-focus: #1d4ed8;
    --graph-deny: #b91c1c;       /* refusals and findings */
    --graph-deny-ink: #ffffff;   /* the glyph inside a red shape */
    --graph-band-a: rgba(100, 116, 139, .12);
    --graph-band-b: rgba(100, 116, 139, .24);
    --graph-mark: #475569;
    --graph-mark-ink: #ffffff;
    --graph-grid: rgba(100, 116, 139, .22);
    --graph-halo: var(--card-bg);

    /* ── the sign-in and sign-out pages (login.css, logout.css) ────────────────────────────────────────────────────────
       The sign-in page is a dark page in both themes (login.html says data-theme="dark"), so these are single values, not
       light/dark pairs. Wherever the page's old colour is within 6/255 per channel of a token above it uses that token
       (--text, --text-muted, --danger, --on-accent, the radius and text scales, --space-*); what is left is named here, so a
       rebrand is one edit in this file. Contrast on --auth-card (the card; the placeholder sits on --auth-bg, the field fill, where it is
       higher): --auth-heading 15.4:1, --auth-muted 4.7:1, --auth-placeholder 4.7:1, --auth-faint 4.7:1 (WCAG AA 4.5:1 for text; they were
       3.5:1, 2.5:1 and 1.6:1: the subtitle, the placeholders and the footer line). Same hue as before, lighter only; under
       prefers-contrast: more all three are 7.1:1 (the block at the end of this file). web/test/css-contrast.test.mjs checks both. */
    --auth-bg: #0f1117;                            /* the page, and the inside of a field */
    --auth-card: #1a1d27;                          /* the card */
    --auth-heading: #f1f5f9;                       /* the product name and the "Welcome back" title */
    --auth-muted: #79889e;                         /* the host under the logo, the subtitle (was #64748b, 3.5:1) */
    --auth-placeholder: #7688a2;                   /* the hint in an empty field (was #475569, 2.5:1) */
    --auth-faint: #7289aa;                         /* the footer line (was #334155, 1.6:1) */
    --auth-version: #999999;                       /* the build, bottom right */
    --auth-line: var(--sidebar-line);              /* the card's outline (white at .07) */
    --auth-line-field: var(--field-border);        /* a field's outline: the dashboard's field outline, >= 3:1 on the field fill and the card (was white at .1: 1.15:1) */
    --auth-accent: #6366f1;                        /* indigo: the focused field, the button and the logo tile start */
    --auth-accent-strong: #4f46e5;                 /* the end of the button's gradient */
    --auth-teal: #10b981;                          /* the end of the logo tile's gradient */
    --auth-accent-halo: rgba(99,102,241,.18);      /* the ring round a focused field */
    --auth-glow-accent: rgba(99,102,241,.15);      /* the two soft glows behind the card */
    --auth-glow-teal: rgba(16,185,129,.10);
    --auth-error-bg: rgba(239,68,68,.12);          /* the "Incorrect username or password" line */
    --auth-error-border: rgba(239,68,68,.3);
    --auth-shadow: 0 24px 64px rgba(0,0,0,.5);     /* the card floating on the page */
    /* ── more sidebar colours: the focus ring and the chosen theme option on the dark sidebar, and prefers-contrast: more ─────
       (the same in both themes: the sidebar is dark in both) */
    --sidebar-focus-ring: #93c5fd;                 /* the focus ring of a button on the sidebar and the phone top bar: 9:1 on --sidebar-bg */
    --sidebar-chosen-bg: rgba(255,255,255,.18);    /* the chosen theme option; theme.test.mjs reads it here: white text >= 4.5:1 on it */
    --sidebar-chosen-bg-hover: rgba(255,255,255,.24);
    --sidebar-text-contrast: #f8fafc;              /* prefers-contrast: more: buttons on the sidebar (12:1+) */
    --sidebar-active-bg-contrast: rgba(147,197,253,.3); /* prefers-contrast: more: the current item */

    /* ── Messages: a sender's avatar is hsl(<hue per machine, set from script as --h> sat light), and my own bubble a tint of the accent */
    --avatar-hue: 210;                             /* the hue before a machine has one */
    --avatar-sat: 55%;
    --avatar-light: 42%;
    --bubble-mine-bg: color-mix(in srgb, var(--primary) 16%, var(--surface));

    /* ── Message graph: the colour of a machine (graph-layout.ts MACHINE_PALETTE, one slot per hash of the machine's name; the script
       sets them as var() through the CSSOM, so a rebrand is an edit here). fg = text and strokes, bg = the badge fill, border = its outline. Both themes. */
    --machine-1-fg: #38bdf8; --machine-1-bg: rgba(56,189,248,.18); --machine-1-border: rgba(56,189,248,.45);  /* sky */
    --machine-2-fg: #34d399; --machine-2-bg: rgba(52,211,153,.18); --machine-2-border: rgba(52,211,153,.45);  /* emerald */
    --machine-3-fg: #a78bfa; --machine-3-bg: rgba(167,139,250,.18); --machine-3-border: rgba(167,139,250,.45);  /* violet */
    --machine-4-fg: #fbbf24; --machine-4-bg: rgba(251,191,36,.18); --machine-4-border: rgba(251,191,36,.45);  /* amber */
    --machine-5-fg: #f472b6; --machine-5-bg: rgba(244,114,182,.18); --machine-5-border: rgba(244,114,182,.45);  /* rose */
    --machine-6-fg: #2dd4bf; --machine-6-bg: rgba(45,212,191,.18); --machine-6-border: rgba(45,212,191,.45);  /* teal */
    --machine-7-fg: #fb923c; --machine-7-bg: rgba(251,146,60,.18); --machine-7-border: rgba(251,146,60,.45);  /* orange */
    --machine-8-fg: #818cf8; --machine-8-bg: rgba(129,140,248,.18); --machine-8-border: rgba(129,140,248,.45);  /* indigo */
    --machine-other-fg: #94a3b8; --machine-other-bg: rgba(148,163,184,.12); --machine-other-border: rgba(148,163,184,.3);  /* machines the graph cannot name */

    /* ── History: who wrote it. A machine (hist-m-N, the slot graph-layout.ts machineSlot gives its name, the same slot as its colour in the
       message graph) and an agent (hist-a-<name>, a fixed slot per KNOWN_AGENTS entry). A machine is a tinted pill: ink = its text and its dot,
       wash = its fill; an agent is a solid badge: solid = the fill, its letters are --hist-on-solid. Ink on wash, on every surface and on --info-bg (a pressed chip and the marked event; the dark one is translucent, so it is laid over each surface), and the
       solid with its letters, are >= 4.5:1 (7:1 under prefers-contrast: more), and every solid is >= 3:1 on every surface
       (web/test/history-palette.test.mjs). Colour is never the only signal: a machine also has a glyph and its name, an agent two letters and its name. */
    --hist-m1-ink: #106d97; --hist-m1-wash: #d3f0fd; --hist-m2-ink: #0d7351; --hist-m2-wash: #d3fdef;
    --hist-m3-ink: #6830e8; --hist-m3-wash: #e0d3fd; --hist-m4-ink: #8a5d10; --hist-m4-wash: #fdeed3;
    --hist-m5-ink: #b81466; --hist-m5-wash: #fdd3e8; --hist-m6-ink: #0d7365; --hist-m6-wash: #d3fdf7;
    --hist-m7-ink: #a54d12; --hist-m7-wash: #fde4d3; --hist-m8-ink: #3547e9; --hist-m8-wash: #d3d8fd;
    --hist-a1-solid: #c84d19; --hist-a2-solid: #15843e; --hist-a3-solid: #1772e8; --hist-a4-solid: #5d7698;
    --hist-a5-solid: #af3ee0; --hist-a6-solid: #148185; --hist-a7-solid: #df2a54; --hist-a8-solid: #8b7109;
    --hist-on-solid: #ffffff;
    --hist-m0-ink: var(--idchip-muted); --hist-m0-wash: var(--surface-muted);   /* a machine with no name */
    --hist-a0-solid: var(--idchip-muted);                                      /* an agent that is not one of ours */

    /* ── syntax highlighting (static/styles.css .hl-*, highlight.ts; docs/highlighting.md) ──────────────────────────────
       Contrast of every --hl-* text colour on --bg, --surface-muted and --card-bg (and of --hl-add/--hl-del/--hl-mask/--hl-ctl on
       their own fill): >= 4.5:1; under prefers-contrast: more >= 7:1 (web/test/highlight-css.test.mjs checks both). */
    --hl-cmd: #1e3a8a; --hl-kw: #6b21a8; --hl-flag: #115e59; --hl-path: #9a3412; --hl-url: #1d4ed8; --hl-string: #166534;
    --hl-var: #86198f; --hl-num: #854d0e; --hl-op: #475569; --hl-comment: #556274; --hl-key: #0c4a6e; --hl-prompt: #475569;
    --hl-fence: #556274; --hl-hunk: #1e40af; --hl-meta: #556274; --hl-ref: #155e75;
    --hl-add: #166534; --hl-add-bg: rgba(22, 163, 74, .16); --hl-del: #991b1b; --hl-del-bg: rgba(220, 38, 38, .12);
    --hl-mask: #78350f; --hl-mask-bg: #fef3c7; --hl-ctl: #ffffff; --hl-ctl-bg: #b91c1c; --hl-uni-line: #b91c1c;

    /* ── Type: weights, line heights, tracking, font stacks ──────────────────────────────────────────────────────────────
       The size scale (--text-*) is above. Every other typographic value is a step here, named by role; a step that is a hair
       from its neighbour (1.35 / 1.4 / 1.45) is kept because a stylesheet used it, and is a candidate to merge (design call). */
    --fw-regular: 400;  --fw-medium: 500;  --fw-semibold: 600;  --fw-bold: 700;  --fw-heavy: 800;
    --lh-none: 1;        /* icons and one-line controls */
    --lh-tight: 1.2;     /* headings, buttons, big numbers */
    --lh-snug: 1.35;
    --lh-compact: 1.4;   /* secondary text, hints, chips */
    --lh-cosy: 1.45;     /* message bodies, code blocks, empty states */
    --lh-base: 1.5;      /* body text, notes, alerts */
    --lh-relaxed: 1.6;   /* long-form: textareas, remember notes, the about text */
    --lh-loose: 1.7;
    --lh-airy: 1.9;      /* the keyboard hints row */
    --lh-12px: 12px;     /* a line box as tall as a chart axis label or a badge: fixed so the box does not grow with the font */
    --lh-16px: 16px;
    --lh-18px: 18px;
    --tracking-tight: -.3px;                       /* the sign-in logo's name */
    --tracking-hair: .2px;
    --tracking-sm: .5px;
    --tracking-md: 1px;
    --tracking-caps-1: .04em;  --tracking-caps-2: .05em;  --tracking-caps-3: .06em;  --tracking-caps-4: .08em;  --tracking-caps-5: .1em;   /* UPPERCASE labels, small to wide */
    --font-sans: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif;   /* the app and the sign-in page */
    --font-system: system-ui, sans-serif;                                              /* the sign-out page */
    --font-mono: ui-monospace, SFMono-Regular, Menlo, monospace;                      /* ids, keys, code, highlighted text */
    --font-mono-menlo: ui-monospace, Menlo, monospace;                                 /* the same face as --font-mono on macOS; a stylesheet wrote it without SFMono-Regular */
    --font-mono-sf: ui-monospace, 'SF Mono', Menlo, monospace;
    --font-mono-win: ui-monospace, SFMono-Regular, Menlo, Consolas, monospace;         /* what highlighted text falls back to on Windows */
    --font-mono-code: 'SF Mono', 'Fira Code', 'Cascadia Code', monospace;              /* the result textarea */
    --font-monospace: monospace;                                                        /* the generic face: config values, coordinates, raw JSON */
    --text-glyph-sm: .7em;  --text-glyph-md: .8em;  --text-glyph-lg: .85em;            /* a glyph or caret sized relative to the text beside it */
    --lines-clamp: 4;                                                                   /* lines of a long message body before "Show more" */
    /* ── Motion: durations, easings, the turns of a chevron ──────────────────────────────────────────────────────────────
       Every transition and animation is written with these (prefers-reduced-motion switches them off in styles.css). Steps that are
       a hair apart (.1 / .12 / .15 / .18 / .2 s) are kept because a stylesheet used them: a candidate to merge (design call). */
    --dur-instant: .1s;  --dur-fast: .12s;  --dur-base: .15s;  --dur-sheet: .18s;  --dur-slow: .2s;
    --dur-spin: .6s;     --dur-spin-slow: .7s;   --dur-shimmer: 1.4s;
    --ease-standard: cubic-bezier(.25,.1,.25,1);   /* what CSS calls "ease" */
    --ease-out: cubic-bezier(0,0,.58,1);
    --ease-in-out: cubic-bezier(.42,0,.58,1);
    --ease-linear: cubic-bezier(0,0,1,1);          /* the same curve as "linear": spinners and a delayed visibility switch */
    --rot-spin: 360deg;           /* one turn of a spinner */
    --rot-chevron-open: 45deg;    /* the chevron of an open group / details (a corner of a box, turned) */
    --rot-chevron-closed: -45deg;
    --rot-caret-collapsed: -90deg; /* the caret of a collapsed category header */
    --angle-gradient-diag: 135deg;  /* the sign-in page's logo tile and button */
    --angle-gradient-row: 90deg;    /* the skeleton's shimmer runs along the row */
    --sheet-rise: 24px;             /* how far a bottom sheet slides up while it fades in */
    --press-scale: .98;             /* a pressed button */

    /* ── Layers: one scale, low to high ──────────────────────────────────────────────────────────────────────────────────
       Names say what sits on that layer; the order is what matters: a menu is above a sticky header, the phone drawer above its
       overlay, the skip link above the page chrome, the command palette above all of it. */
    --z-raised: 1;          /* sticky table headers, group heads, the sidebar brand */
    --z-pinned: 2;          /* the pinned Actions header, above the sticky header row */
    --z-pill: 5;            /* the "newest messages" pill */
    --z-bar: 15;            /* the sticky bulk-action bar */
    --z-menu: 20;           /* menus, toolbars, the graph tooltip */
    --z-backdrop: 24;       /* the dim behind a filter sheet on the phone */
    --z-panel: 25;          /* the filter panel */
    --z-sheet: 26;          /* the same panel as a bottom sheet */
    --z-drawer-overlay: 40;
    --z-drawer: 50;         /* the phone sidebar */
    --z-skip-link: 100;
    --z-dialog: 200;        /* the command palette */

    /* ── Opacity: how faded a thing is (0 and 1 are not steps: they mean hidden and shown) ──────────────────────────────── */
    --lines-1: 1;             /* how many lines a compact History snippet keeps (line-clamp takes a bare number) */
    --opacity-dim: .2;        /* what the graph fades while one node is in focus */
    --opacity-dimmed: .18;    /* graph nodes and edges that do not match the node filter */
    --opacity-faint: .35;     /* an idle sort arrow, a skeleton */
    --opacity-enter: .4;      /* the start of a sheet's fade-in */
    --opacity-disabled: .55;  /* a disabled button or pill, a stale graph */
    --opacity-muted: .6;
    --opacity-expired: .7;    /* an expired message */
    --opacity-edge: .72;      /* a graph edge at rest */
    --opacity-off: .75;       /* a severity filter that is off */
    --opacity-quiet: .8;
    --opacity-hover: .92;     /* the sign-in button under the pointer */
    /* ── Borders, focus rings and SVG strokes ─────────────────────────────────────────────────────────────────────────────
       Border and outline widths are five steps; a ring drawn with box-shadow uses the same ones. Focus outlines are --bw-medium at
       --focus-offset (a negative offset draws the ring inside a box that clips its overflow). Strokes are the widths of lines in the
       graph views and charts, in SVG user units (1 unit = 1 px at the size they are drawn). */
    --bw-thin: 1px;     /* the hairline of every card, field, table row and divider */
    --bw-medium: 2px;   /* focus outlines, the selected ring, a chevron's strokes, an underline's thickness */
    --bw-thick: 3px;    /* the halo round a focused field, the nav item's accent bar */
    --bw-accent: 4px;   /* the left bar of an alert or a status card */
    --bw-bar: 6px;      /* the left bar of a selected or refused activity row */
    --ring-w-pinned: 7px;   /* the halo round a pinned message */
    --focus-offset: 2px;  --focus-offset-tight: 1px;  --focus-offset-wide: 4px;
    --focus-offset-inset: -2px;  --focus-offset-inset-tight: -1px;  --focus-offset-inset-deep: -3px;
    --stroke-hair: 1px;  --stroke-thin: 1.4px;  --stroke-base: 1.5px;  --stroke-medium: 2px;  --stroke-semi: 2.5px;
    --stroke-thick: 3px;  --stroke-heavy: 3.5px;  --stroke-bold: 4px;  --stroke-band: 6px;  --stroke-hit: 14px;   /* hit = the invisible, wider line a pointer can find */
    --dash-group: 8 4;  --dash-read: 2 4;  --dash-unknown: 3 2;  --dash-other: 1 3;  --dash-withdrawn: 2 2;   /* stroke-dasharray of the graph's line kinds */
    /* ── Breakpoints ──────────────────────────────────────────────────────────────────────────────────────────────────────
       A media query cannot use var(), so the numbers are written in the @media rules of styles.css / graph.css, and ONLY these
       numbers: web/test/css-literals.test.mjs fails the build on any other width in an @media (and on a width in a script's
       matchMedia). These custom properties are the list: the one place the numbers are decided and documented, what a script reads
       (web/src/breakpoints.ts: getComputedStyle(...).getPropertyValue('--bp-...')), and what the Figma / DTCG export carries.
       Everything is max-width (the phone-first rules are the exceptions: under the width the layout stacks); the one min-width is
       the complement of --bp-split, written as the width + 1 px (801) so the two never both apply. */
    --bp-phone-sm: 480px;   /* a narrow phone: the shortcuts table and the Messages sessions grid go to one column, the sender's host hides */
    --bp-phone-md: 560px;   /* the footer's "shortcuts" link hides, a Filters sheet is full width */
    --bp-phone: 600px;      /* a phone: forms and panels stack, tables scroll, Filters become a bottom sheet (also read by script: web/src/breakpoints.ts) */
    --bp-graph: 700px;      /* the graph views stack their toolbar and detail card */
    --bp-tablet: 768px;     /* a portrait tablet: the Messages page's columns tighten */
    --bp-split: 800px;      /* the Messages / Activity two-pane layout becomes one pane (and the 801 px min-width rules are the wide side) */
    --bp-drawer: 900px;     /* the sidebar becomes a drawer behind the menu button (also read by script) */

    /* ── Sizes: icons, controls, avatars, widths and heights that are design constants ───────────────────────────────────
       A size is a role, not a number: icons by --size-icon-* (a glyph box), controls by --control-h-* and --touch-target (the 44 px
       a finger needs on a coarse pointer), people by --size-avatar-*, layout by --w-* (a width, min or max) and --h-* (a height).
       Gutters inside a calc() are spacing (--space-*). Steps a hair apart (the three 760 px widths, 280 / 320 px bases) have a name
       per role on purpose: they are free to drift apart. */
    --size-hair: 1px;                 /* the box of a visually hidden element (.sr-only): it has to exist, it must not show */
    --size-icon-2xs: 11px;  --size-icon-xs: 14px;  --size-icon-sm: 16px;  --size-icon-md: 18px;  --size-icon-lg: 20px;
    --size-icon-xl: 22px;   --size-icon-2xl: 24px; --size-icon-3xl: 32px; --size-icon-4xl: 40px;
    --size-icon-inline: 1em;          /* an icon that is as tall as the text beside it */
    --size-dot: 8px;                  /* a status dot */
    --size-chevron: 6px;              /* the box a chevron's two borders are drawn in */
    --size-check: 22px;               /* a checkbox in the Activity list */
    --size-chip-x: 20px;              /* the remove button of a filter chip */
    --size-count-min: 20px;           /* the smallest a count badge gets */
    --size-chip-letter: 1.4em;        /* the letter disc of a product chip: follows the text */
    --size-kbd-min: 1.6em;            /* the smallest a key cap gets */
    --size-meter-h: 5px;  --size-meter-w: 44px;   /* the credit bar */
    --size-axis-h: 16px;              /* the row of axis labels under a sparkline */
    --size-spark-h: 28px;  --size-spark-h-lg: 40px;
    /* the activity bars of a History name (history-controls.ts, class hist-bar-hN): nine heights, N = 0 (a quiet slice, drawn faint) .. 8 (the busiest);
       1 px, then every 2 px up to the 16 px the tallest bar and the box that holds them (--hist-bar-8) have */
    --hist-bar-0: 1px;  --hist-bar-1: 2px;  --hist-bar-2: 4px;  --hist-bar-3: 6px;  --hist-bar-4: 8px;
    --hist-bar-5: 10px; --hist-bar-6: 12px; --hist-bar-7: 14px; --hist-bar-8: 16px;
    --size-avatar-sm: 28px;           /* a message sender */
    --size-avatar-lg: 72px;           /* a LinkedIn profile picture */
    --size-logo: 36px;                /* the brand tile in the sidebar */
    --size-logo-lg: 44px;             /* the same on the sign-in page */
    --control-h-xs: 24px;  --control-h-sm: 28px;  --control-h-pill: 30px;  --control-h-snug: 32px;  --control-h-md: 36px;   /* the height of a control: --btn-h-* above are these */
    --touch-target: 44px;             /* the smallest a control is on a coarse pointer (WCAG 2.5.5) */
    --skel-line-h: 10px;  --skel-short-h: 8px;  --skel-title-h: 12px;  --skel-keys-line-h: 14px;  --skel-stat-h: 20px;  --skel-item-h: 58px;  --skel-card-h: 96px;
    --skel-title-w: 34%;  --skel-short-w: 22%;  --skel-line-short-w: 38%;  --skel-stat-w: 56px;      /* loading skeletons */
    --w-shrink: 1%;                   /* a table column that takes only what its content needs */
    --w-name-min: 3ch;  --w-meta-min: 10ch;  --w-share-meta: 40%;
    --w-tag-max: 22ch;  --w-chip-value-max: 28ch;  --w-title-max: 46ch;
    --w-measure: 80ch;                /* the longest a line of running text gets */
    --w-field-xs: 100px;  --w-field-sm: 130px;  --w-field-md: 160px;  --w-field-lg: 200px;  --w-field-xl: 220px;   /* form fields (.tp-w-*) */
    --w-fieldgroup-min: 190px;  --w-stat: 150px;  --w-seg-min: 60px;  --w-menu-min: 168px;
    --w-basis-lg: 280px;  --w-basis-xl: 320px;                       /* the flex basis of a search box, a toolbar field, a text column */
    --w-card-min-sm: 200px;  --w-card-min: 240px;  --w-session-min: 250px;   /* the narrowest a card or a column of a responsive grid gets */
    --w-swimlane-label: 340px;
    --w-graph-search: 140px;          /* the node filter in the message graph's toolbar */
    --h-fg-canvas: 480px;  --h-fg-canvas-phone: 340px;  --size-swatch: 12px;   /* the Fleet graph's picture (3D or flat) and the colour dot of its legend */
    --w-row-act: 4rem;                /* the room a History row keeps at its right edge for the Copy button that sits over it */
    --w-shortcut-key-min: 7rem;  --w-shortcut-key: 11rem;             /* the key column of the shortcuts dialog */
    --w-table-min: 760px;  --w-graph-table-min: 600px;                /* below these a table scrolls sideways instead of squeezing */
    --w-form: 760px;                  /* the issue-key form */
    --w-panel: 760px;                 /* a filter panel */
    --w-panel-max: 960px;             /* the default page column */
    --w-col-max: 880px;               /* the Messages column */
    --w-dialog: 640px;                /* the shortcuts dialog */
    --w-palette: 600px;               /* the command palette */
    --w-tip-max: 280px;               /* the graph tooltip */
    --w-error-pre: 480px;  --h-error-pre-max: 200px;
    --w-auth-card: 380px;             /* the sign-in card */
    --w-narrow-page: 24rem;           /* the sign-out page */
    --w-drawer: 280px;  --w-drawer-vw: 82vw;                          /* the phone sidebar: 82% of the screen, at most --w-drawer */
    --toast-gutter: 140px;            /* what a toast leaves free of the page's width */
    --h-map: 320px;  --h-map-short: 220px;
    --h-proxy: 480px;
    --h-thread-offset: 230px;         /* the chrome above the message thread: it is as tall as the viewport minus this */
    --h-thread-min: 240px;  --h-thread-min-lg: 320px;  --h-main-min: 260px;
    --h-result-min: 180px;  --h-result-min-lg: 260px;
    --h-raw-json-max: 300px;  --h-menu-max: 220px;
    --h-panel-max: 640px;  --h-panel-max-vh: 72vh;                    /* a filter panel: 72% of the screen, at most --h-panel-max */
    --h-sheet-max: 86vh;  --h-sheet-max-dyn: 86dvh;                   /* a bottom sheet (dvh where the browser knows it, vh before) */
    --h-dialog-max: 720px;  --h-dialog-max-vh: 82vh;
    --h-palette-max: 70vh;
    --content-max-tool: 800px;        /* a TOOL page (a form and its result): a narrow, readable column, 736 px of content with the 32 px side padding */
    --content-max-data: 1400px;       /* a DATA page (tables, grids, feeds): the width it is given, up to this */
    --auth-glow-bg: radial-gradient(ellipse 80% 60% at 20% 10%, var(--auth-glow-accent) 0%, transparent 60%), radial-gradient(ellipse 60% 50% at 80% 80%, var(--auth-glow-teal) 0%, transparent 60%);   /* the two soft glows behind the sign-in card */
    /* ── The spacing ramp ──────────────────────────────────────────────────────────────────────────────────────────────────
       --space-N is N x 4 px: 4 / 8 / 12 / 16 / 24 (--space-1 .. --space-6) are THE steps; padding, margin, gap and offsets use these
       and nothing else. The rest is what a page needs beyond them, each with its reason: 1 px (a hairline gap), 28 / 32 px (a
       roomy gutter), 40 / 44 px (an empty state, the sign-in card), 4rem (the sign-out page). A negative offset is
       calc(-1 * var(--space-N)). The in-between steps (2, 3, 5, 6, 7, 9, 10, 11, 14, 18, 20 px) were snapped onto the grid in the
       part that follows the pixel-exact ones: see CHANGELOG. */
    --space-px: 1px;
    --space-7: 28px;  --space-8: 32px;  --space-10: 40px;  --space-11: 44px;  --space-16: 4rem;
    --offscreen: -9999px;                  /* where a skip link waits until it takes focus */
    --pad-overlay-top: 12vh;  --pad-overlay-top-phone: 8vh;   /* how far down the command palette starts */
    --scroll-offset: 64px;  --scroll-offset-toolbar: 124px;  /* what a row scrolled into view keeps clear of: the sticky chrome above it */
    --align-glyph: .1em;                   /* a glyph sitting a hair below the baseline */
}

/* Dark theme — toggled by JS setting data-theme on <html>, defaulting
   to the OS preference (prefers-color-scheme) on first visit, then
   persisted to localStorage. Overriding this one variable block is
   sufficient for the whole app because every component below
   references these custom properties rather than a hardcoded hex —
   see the sweep that replaced ~50 inline/CSS hex values with var()
   lookups in the same change that added this block. */
:root[data-theme="dark"] {
    color-scheme: dark;
    --primary: #3b82f6;
    --primary-dark: #60a5fa;
    --success: #34d399;
    --warning: #fbbf24;
    --danger: #f87171;
    --sidebar-bg: #0a0f1c;
    --sidebar-hover: #1a2438;
    --bg: #0b1220;
    --card-bg: #131b2c;
    --surface: #131b2c;
    --surface-muted: #1a2338;
    --border: #263349;
    --text: #e5e9f0;
    --text-muted: #94a3b8;
    --input-bg: #0f1729;

    --success-bg: rgba(16,185,129,.16);  --success-text: #6ee7b7;
    --danger-bg: rgba(239,68,68,.16);    --danger-text: #fca5a5;
    --warning-bg: rgba(245,158,11,.16);  --warning-text: #fcd34d;
    --info-bg: rgba(59,130,246,.18);     --info-text: #93c5fd;
    --purple-bg: rgba(168,85,247,.16);   --purple-text: #d8b4fe;
    --rose-bg: rgba(244,63,94,.16);      --rose-text: #fda4af;
    --loading-bg: rgba(14,165,233,.16);  --loading-text: #7dd3fc;
    --alert-warning-bg: rgba(234,179,8,.14); --alert-warning-border: rgba(234,179,8,.4); --alert-warning-text: #fde68a;
    --row-hover-bg: rgba(59,130,246,.1);

    /* Buttons (dark). The primary stays #2563eb: white on the lighter dark-theme --primary (#3b82f6) is only 3.7:1. */
    --btn-primary-bg: #2563eb; --btn-primary-hover: #1d4ed8; --btn-primary-active: #1e40af;
    --field-border: #64748b;   /* 3.6:1 on --card-bg, 3.9:1 on --bg */
    --btn-hover-bg: #202c45;
    --btn-danger-fg: #f87171;  --btn-danger-border: #f87171;
    --focus-ring: #60a5fa;

    /* Opaque so the contrast (>= 7.7:1) does not depend on what the chip sits on. */
    --idchip-1-bg: #3b2a0b; --idchip-1-fg: #fcd34d;
    --idchip-2-bg: #0e3a28; --idchip-2-fg: #6ee7b7;
    --idchip-3-bg: #16295a; --idchip-3-fg: #93c5fd;
    --idchip-4-bg: #33204f; --idchip-4-fg: #d8b4fe;
    --idchip-muted: #94a3b8;
    --idchip-letter-bg: rgba(255,255,255,.12);

    --hl-cmd: #93c5fd; --hl-kw: #d8b4fe; --hl-flag: #5eead4; --hl-path: #fdba74; --hl-url: #7dd3fc; --hl-string: #86efac;
    --hl-var: #f0abfc; --hl-num: #fde047; --hl-op: #94a3b8; --hl-comment: #94a3b8; --hl-key: #a5b4fc; --hl-prompt: #94a3b8;
    --hl-fence: #94a3b8; --hl-hunk: #93c5fd; --hl-meta: #94a3b8; --hl-ref: #67e8f9;
    --hl-add: #86efac; --hl-add-bg: rgba(34, 197, 94, .18); --hl-del: #fca5a5; --hl-del-bg: rgba(239, 68, 68, .22);
    --hl-mask: #fde68a; --hl-mask-bg: #422006; --hl-ctl: #450a0a; --hl-ctl-bg: #fca5a5; --hl-uni-line: #fca5a5;

    --graph-edge: #94a3b8;
    --graph-node-stroke: #94a3b8;
    --graph-focus: #93c5fd;
    --graph-deny: #fca5a5;
    --graph-deny-ink: #3b0a0a;
    --graph-band-a: rgba(148, 163, 184, .10);
    --graph-band-b: rgba(148, 163, 184, .22);
    --graph-mark: #94a3b8;
    --graph-mark-ink: #0b1220;
    --graph-grid: rgba(148, 163, 184, .20);

    /* History: the same names in the dark theme (web/test/history-palette.test.mjs). */
    --hist-m1-ink: #36baf7; --hist-m1-wash: #254656; --hist-m2-ink: #23f6af; --hist-m2-wash: #255645;
    --hist-m3-ink: #ae8efb; --hist-m3-wash: #332556; --hist-m4-ink: #f6a823; --hist-m4-wash: #564425;
    --hist-m5-ink: #f971b5; --hist-m5-wash: #56253d; --hist-m6-ink: #23f6da; --hist-m6-wash: #25564f;
    --hist-m7-ink: #f8924f; --hist-m7-wash: #563825; --hist-m8-ink: #8d98fa; --hist-m8-wash: #252a56;
    --hist-a1-solid: #da541b; --hist-a2-solid: #1daf52; --hist-a3-solid: #2a7dea; --hist-a4-solid: #6780a2;
    --hist-a5-solid: #b650e2; --hist-a6-solid: #1bacb1; --hist-a7-solid: #e24066; --hist-a8-solid: #c09c0c;
    --hist-on-solid: #0b1220;
}

/* The same names under prefers-contrast: more (muted text and field outlines at full strength). Last, so it wins over both blocks above. */
@media (prefers-contrast: more) {
    :root:not([data-theme="dark"]) { --text-muted: #3f4e63; }  /* 7.9:1 on --bg */
    :root, :root[data-theme="dark"] { --field-border: var(--text); }                      /* fields and buttons: full text colour, 12:1+ */
    :root, :root[data-theme="dark"] { --auth-muted: #9eaaba; --auth-placeholder: #9eaaba; --auth-faint: #9eaaba; }  /* the sign-in page's quiet text: 7.1:1 on the card */
}

/* Syntax highlighting under prefers-contrast: more (>= 7:1). Last, so it wins over both blocks above, as it did in styles.css. */
@media (prefers-contrast: more) {
    :root {
        --hl-cmd: #172554; --hl-kw: #4c1d95; --hl-flag: #134e4a; --hl-path: #7c2d12; --hl-url: #1e3a8a; --hl-string: #14532d;
        --hl-var: #701a75; --hl-num: #713f12; --hl-op: #334155; --hl-comment: #334155; --hl-key: #082f49; --hl-prompt: #334155;
        --hl-fence: #334155; --hl-hunk: #1e3a8a; --hl-meta: #334155; --hl-ref: #164e63;
        --hl-add: #052e16; --hl-add-bg: rgba(22, 163, 74, .14); --hl-del: #450a0a; --hl-del-bg: rgba(220, 38, 38, .12);
        --hl-mask: #451a03; --hl-mask-bg: #fde68a; --hl-ctl: #ffffff; --hl-ctl-bg: #7f1d1d; --hl-uni-line: #7f1d1d;
        --hist-m1-ink: #0c4f6e; --hist-m1-wash: #d3f0fd;
        --hist-m2-ink: #09553c; --hist-m2-wash: #d3fdef;
        --hist-m3-ink: #4715bc; --hist-m3-wash: #e0d3fd;
        --hist-m4-ink: #65430b; --hist-m4-wash: #fdeed3;
        --hist-m5-ink: #850f4a; --hist-m5-wash: #fdd3e8;
        --hist-m6-ink: #09544a; --hist-m6-wash: #d3fdf7;
        --hist-m7-ink: #77380d; --hist-m7-wash: #fde4d3;
        --hist-m8-ink: #1527c1; --hist-m8-wash: #d3d8fd;
        --hist-a1-solid: #963a13;
        --hist-a2-solid: #10652f;
        --hist-a3-solid: #1154ac;
        --hist-a4-solid: #465872;
        --hist-a5-solid: #8a1eb8;
        --hist-a6-solid: #0f5f62;
        --hist-a7-solid: #a9193b;
        --hist-a8-solid: #695607;
        --hist-m0-ink: var(--text); --hist-a0-solid: var(--text);
    }
    :root[data-theme="dark"] {
        --hl-cmd: #bfdbfe; --hl-kw: #e9d5ff; --hl-flag: #99f6e4; --hl-path: #fed7aa; --hl-url: #bae6fd; --hl-string: #bbf7d0;
        --hl-var: #f5d0fe; --hl-num: #fef08a; --hl-op: #cbd5e1; --hl-comment: #cbd5e1; --hl-key: #c7d2fe; --hl-prompt: #cbd5e1;
        --hl-fence: #cbd5e1; --hl-hunk: #bfdbfe; --hl-meta: #cbd5e1; --hl-ref: #a5f3fc;
        --hl-add: #bbf7d0; --hl-add-bg: rgba(34, 197, 94, .18); --hl-del: #fecaca; --hl-del-bg: rgba(239, 68, 68, .22);
        --hl-mask: #fef3c7; --hl-mask-bg: #422006; --hl-ctl: #450a0a; --hl-ctl-bg: #fecaca; --hl-uni-line: #fecaca;
        --hist-m1-ink: #a7e1fb; --hist-m1-wash: #254656;
        --hist-m2-ink: #acfce1; --hist-m2-wash: #255645;
        --hist-m3-ink: #cfbcfc; --hist-m3-wash: #332556;
        --hist-m4-ink: #fbdca7; --hist-m4-wash: #564425;
        --hist-m5-ink: #fcb1d6; --hist-m5-wash: #56253d;
        --hist-m6-ink: #acfcf1; --hist-m6-wash: #25564f;
        --hist-m7-ink: #fcccac; --hist-m7-wash: #563825;
        --hist-m8-ink: #bcc2fc; --hist-m8-wash: #252a56;
        --hist-a1-solid: #eb875c;
        --hist-a2-solid: #1eb857;
        --hist-a3-solid: #66a2f0;
        --hist-a4-solid: #90a2bb;
        --hist-a5-solid: #cc84eb;
        --hist-a6-solid: #1bb1b6;
        --hist-a7-solid: #ec7e98;
        --hist-a8-solid: #c09c0c;
        --hist-m0-ink: var(--text); --hist-a0-solid: var(--text);
    }
}
