/* ===================================================================
   VamiAppSec — shared design system
   -------------------------------------------------------------------
   ONE source of truth for the tokens every authenticated surface reads:
   the dashboard (frontend/dashboard/css/*.css), the Mobile AppSec
   workspace (mobile/platform-ui/workspace.css) and the auth screens
   (frontend/auth/css/auth.css).

   Load this FIRST, before any page stylesheet:
     <link rel="stylesheet" href="/css/design-system.css" />
     <link rel="stylesheet" href="css/dashboard.css" />

   Rules of the road:
   - Never hard-code a hex/rgba in a component. Add a token here instead.
   - Severity, run-state and coverage tokens carry MEANING. `unknown`,
     `partial` and `untested` are first-class and must stay visually
     distinct from `pass` and `fail` — never collapse them into a
     success or failure colour.
   - Every interactive element gets :focus-visible from --focus-*.
   - Motion uses opacity/transform only, --dur-* timings, and is
     disabled wholesale under prefers-reduced-motion (bottom of file).
=================================================================== */

/* ===== Brand palette / theme ====================================== */
:root {
  color-scheme: light;

  /* Surfaces */
  --bg: #F6FCFA;
  --bg-2: #EAF7F3;
  --surface: #FFFFFF;
  --surface-soft: #EEF8F5;
  --card: #FFFFFF;

  /* Text */
  --text: #0A3032;
  --text-muted: #57716D;
  --text-dim: #3D5D59;
  --text-faint: #66807B;

  /* Brand */
  --primary: #2DD4BF;
  --primary-light: #5EEAD4;
  --primary-dark: #0F766E;
  --on-primary: #042422;
  --teal-core: #0F766E;
  --teal-mid: #14B8A6;
  /* Brand colour when used as TEXT. --primary-dark is only legible on light
     surfaces, so ~30 rules had to pair it with a hand-written dark override
     and about half of them were missing one. This token flips automatically. */
  --primary-text: #0F766E;

  /* Borders */
  --border: rgba(15, 118, 110, 0.16);
  --border-2: rgba(15, 118, 110, 0.24);
  --border-3: rgba(15, 118, 110, 0.34);

  /* Legacy severity aliases — kept so existing rules keep resolving.
     New work should use the --sev-* set below. */
  --critical: #F87171;
  --high: #F59E0B;
  --medium: #FBBF24;
  --low: #2DD4BF;
  --danger: #F87171;

  --grad-teal: linear-gradient(135deg, #5EEAD4 0%, #2DD4BF 50%, #0F766E 100%);

  /* Type families */
  --sans: "Geist", -apple-system, BlinkMacSystemFont, "Segoe UI", sans-serif;
  --serif: "Fraunces", Georgia, serif;
  --mono: "JetBrains Mono", ui-monospace, SFMono-Regular, monospace;

  /* Radii */
  --r-sm: 6px;
  --r: 8px;
  --r-md: 12px;
  --r-lg: 16px;
  --r-pill: 999px;

  /* ---- Spacing scale (4px base) -------------------------------- */
  --sp-1: 4px;
  --sp-2: 8px;
  --sp-3: 12px;
  --sp-4: 16px;
  --sp-5: 20px;
  --sp-6: 24px;
  --sp-7: 32px;
  --sp-8: 40px;
  --sp-9: 48px;

  /* ---- Type scale ---------------------------------------------- */
  /* Whole-pixel steps chosen to match the values already dominant in the
     codebase, so adopting them is a de-fragmentation rather than a redesign.
     31 distinct sizes had accumulated (10 vs 10.5, 11 vs 11.5, 12 vs 12.5,
     13 vs 13.5, plus 8.5/9/9.5 micro-text below the legibility floor). */
  --fs-micro: 10px;   /* uppercase tracked mono labels — the floor */
  --fs-2xs: 11px;     /* dense meta, table micro-labels */
  --fs-xs: 12px;      /* captions, help text, secondary meta */
  --fs-sm: 13px;      /* table cells, secondary body */
  --fs-md: 14px;      /* default body */
  --fs-lg: 15px;      /* card / panel titles */
  --fs-xl: 17px;      /* section titles */
  --fs-2xl: 20px;     /* page title */
  --fs-3xl: 24px;     /* large numerics */
  --fs-4xl: 30px;     /* KPI values */

  --lh-tight: 1.25;
  --lh-snug: 1.4;
  --lh-base: 1.55;

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

  --tracking-label: 0.08em;   /* uppercase mono labels */

  /* ---- Control sizing ------------------------------------------ */
  --control-h: 38px;
  --control-h-sm: 32px;
  --control-h-lg: 44px;
  --tap-min: 44px;            /* minimum pointer target on coarse input */

  /* ---- Elevation ----------------------------------------------- */
  --elev-1: 0 1px 2px rgba(3, 54, 58, 0.06);
  --elev-2: 0 6px 20px -14px rgba(3, 54, 58, 0.34);
  --elev-3: 0 18px 48px -28px rgba(3, 54, 58, 0.38);
  --shadow: 0 24px 70px -42px rgba(3, 54, 58, 0.35);

  /* ---- Motion --------------------------------------------------- */
  --dur-1: 150ms;
  --dur-2: 200ms;
  --dur-3: 250ms;
  /* Progress/meter fills only: a longer ramp smooths the jumps between
     polling ticks. Never use this for hover or press feedback. */
  --dur-meter: 400ms;
  --ease: cubic-bezier(0.2, 0, 0.2, 1);
  --ease-out: cubic-bezier(0.16, 1, 0.3, 1);

  /* ---- Focus ---------------------------------------------------- */
  --focus-color: var(--primary-dark);
  --focus-ring: 0 0 0 3px rgba(15, 118, 110, 0.28);
  --focus-width: 2px;
  --focus-offset: 2px;

  /* ---- Severity (meaning-bearing) -------------------------------- */
  --sev-critical: #B91C1C;
  --sev-critical-bg: rgba(220, 38, 38, 0.10);
  --sev-critical-border: rgba(220, 38, 38, 0.28);
  --sev-high: #B45309;
  --sev-high-bg: rgba(245, 158, 11, 0.12);
  --sev-high-border: rgba(245, 158, 11, 0.30);
  --sev-medium: #92400E;
  --sev-medium-bg: rgba(251, 191, 36, 0.14);
  --sev-medium-border: rgba(251, 191, 36, 0.32);
  --sev-low: #0F766E;
  --sev-low-bg: rgba(45, 212, 191, 0.12);
  --sev-low-border: rgba(45, 212, 191, 0.28);
  --sev-info: #1D4ED8;
  --sev-info-bg: rgba(59, 130, 246, 0.10);
  --sev-info-border: rgba(59, 130, 246, 0.24);
  /* Absence of a verdict — deliberately neutral, never green. */
  --sev-unknown: #475569;
  --sev-unknown-bg: rgba(100, 116, 139, 0.10);
  --sev-unknown-border: rgba(100, 116, 139, 0.22);

  /* ---- Run / connection state ----------------------------------- */
  --state-ok: #0F766E;
  --state-ok-bg: rgba(45, 212, 191, 0.12);
  --state-ok-border: rgba(15, 118, 110, 0.26);
  --state-warn: #92400E;
  --state-warn-bg: rgba(245, 158, 11, 0.13);
  --state-warn-border: rgba(245, 158, 11, 0.30);
  --state-err: #B91C1C;
  --state-err-bg: rgba(220, 38, 38, 0.10);
  --state-err-border: rgba(220, 38, 38, 0.28);
  --state-info: #1D4ED8;
  --state-info-bg: rgba(59, 130, 246, 0.10);
  --state-info-border: rgba(59, 130, 246, 0.24);
  --state-running: #B45309;
  --state-running-bg: rgba(245, 158, 11, 0.12);
  --state-running-border: rgba(245, 158, 11, 0.28);
  /* Not attempted / not applicable / no data — must read as "no verdict". */
  --state-neutral: #475569;
  --state-neutral-bg: rgba(100, 116, 139, 0.10);
  --state-neutral-border: rgba(100, 116, 139, 0.22);

  /* ---- Finding categories --------------------------------------- */
  --cat-code: #4F46E5;
  --cat-secrets: #B91C1C;
  --cat-iac: #9A3412;
  --cat-deps: #115E59;
  --cat-license: #7E22CE;
  --cat-ai: #4F46E5;
  --cat-container: #0369A1;
  --cat-config: #475569;
  --cat-threat: #0E7490;
  /* External attack surface needs its own hue: it previously shared teal with
     dependencies on the results page and red with secrets on the scan page. */
  --cat-exposure: #A21CAF;
  --cat-other: #64748B;

  /* ---- Console surface ------------------------------------------ */
  /* Scan logs and command snippets stay dark in both themes: a terminal
     is the affordance being borrowed, so it is intentional, not a bug. */
  --console-bg: #0B1F1C;
  --console-text: #CDEEE6;

  /* ---- Risk-graph node fills ------------------------------------ */
  /* Solid fills painted into the SVG canvas. Kept separate from the
     --sev-* text tokens because they sit on the graph surface, not on a
     card. appsec.js references these by var() so the graph re-colours
     with the theme instead of baking in light-mode hexes. */
  --graph-critical: #B91C1C;
  --graph-high: #DC2626;
  --graph-medium: #F59E0B;
  --graph-low: #EAB308;
  --graph-info: #3B82F6;
  --graph-unknown: #9CA3AF;
  --graph-asset: #9CA3AF;
  --graph-scanner-ok: #0F766E;
  --graph-scanner-err: #DC2626;
  --graph-scanner-skip: #9CA3AF;
  /* Ink for text placed ON a solid severity fill (e.g. the mobile .sev-dot
     squares). It has to pair with the fill, not with the page: white reads on
     the dark light-theme reds, but needs to flip to near-black on amber. */
  --on-sev-critical: #FFFFFF;
  --on-sev-high: #FFFFFF;
  --on-sev-medium: #1A1206;
  --on-sev-info: #FFFFFF;

  /* ---- Aliases -------------------------------------------------- */
  /* Names already used by mobile/platform-ui/workspace.css and a few
     dashboard rules. They previously resolved to nothing, which made the
     whole declaration invalid at computed-value time — active subtabs lost
     their background, muted paragraphs inherited body colour, and the
     stream-error panel lost its border and fill. Defining them here fixes
     every one of those without touching the component rules. */
  --accent: var(--primary);
  --muted: var(--text-muted);
  --surface-2: var(--surface-soft);
  --surface-hover: rgba(15, 118, 110, 0.06);
  --border-strong: var(--border-3);
  --primary-soft: rgba(45, 212, 191, 0.12);
  --ok: var(--state-ok);
  --warning: var(--state-warn);
  --warning-soft: var(--state-warn-bg);
  --danger-border: var(--state-err-border);
  --danger-soft: var(--state-err-bg);

  /* Button system */
  --button-primary-bg: linear-gradient(135deg, #5EEAD4, #18B7A4);
  --button-primary-text: #002824;
  --button-primary-border: rgba(15, 118, 110, 0.45);
  --button-primary-hover-bg: linear-gradient(135deg, #73F5E3, #13A995);
  --button-secondary-bg: #FFFFFF;
  --button-secondary-text: #064E48;
  --button-secondary-border: rgba(15, 118, 110, 0.34);
  --button-secondary-hover-bg: #E8FAF6;
  --button-disabled-bg: rgba(93, 121, 117, 0.12);
  --button-disabled-text: #536D69;
  --button-disabled-border: rgba(72, 108, 103, 0.34);
}

html[data-theme="dark"] {
  color-scheme: dark;
  --bg: #05090B;
  --bg-2: #090F12;
  --surface: #0D1518;
  --surface-soft: #121D20;
  --card: #0D1518;
  --text: #EAF4F2;
  --text-muted: #91A7A2;
  --text-dim: #B7CAC6;
  --text-faint: #748B86;
  --border: rgba(94, 234, 212, 0.11);
  --border-2: rgba(94, 234, 212, 0.19);
  --border-3: rgba(94, 234, 212, 0.29);

  --elev-1: 0 1px 2px rgba(0, 0, 0, 0.4);
  --elev-2: 0 6px 20px -14px rgba(0, 0, 0, 0.7);
  --elev-3: 0 18px 48px -28px rgba(0, 0, 0, 0.75);
  --shadow: 0 24px 70px -34px rgba(0, 0, 0, 0.72);

  --primary-text: var(--primary-light);
  --focus-color: var(--primary-light);
  --focus-ring: 0 0 0 3px rgba(90, 242, 223, 0.32);

  /* Severity — lightened for contrast on dark surfaces (all ≥ 4.5:1
     against --surface #082F2B). */
  --sev-critical: #FCA5A5;
  --sev-critical-bg: rgba(248, 113, 113, 0.14);
  --sev-critical-border: rgba(248, 113, 113, 0.34);
  --sev-high: #FBBF24;
  --sev-high-bg: rgba(245, 158, 11, 0.14);
  --sev-high-border: rgba(245, 158, 11, 0.32);
  --sev-medium: #FCD34D;
  --sev-medium-bg: rgba(251, 191, 36, 0.13);
  --sev-medium-border: rgba(251, 191, 36, 0.30);
  --sev-low: #5EEAD4;
  --sev-low-bg: rgba(45, 212, 191, 0.13);
  --sev-low-border: rgba(94, 234, 212, 0.30);
  --sev-info: #93C5FD;
  --sev-info-bg: rgba(59, 130, 246, 0.14);
  --sev-info-border: rgba(96, 165, 250, 0.28);
  --sev-unknown: #B4CBC7;
  --sev-unknown-bg: rgba(157, 184, 179, 0.12);
  --sev-unknown-border: rgba(157, 184, 179, 0.24);

  --state-ok: #5EEAD4;
  --state-ok-bg: rgba(45, 212, 191, 0.13);
  --state-ok-border: rgba(94, 234, 212, 0.30);
  --state-warn: #FCD34D;
  --state-warn-bg: rgba(245, 158, 11, 0.14);
  --state-warn-border: rgba(245, 158, 11, 0.32);
  --state-err: #FCA5A5;
  --state-err-bg: rgba(248, 113, 113, 0.14);
  --state-err-border: rgba(248, 113, 113, 0.34);
  --state-info: #93C5FD;
  --state-info-bg: rgba(59, 130, 246, 0.14);
  --state-info-border: rgba(96, 165, 250, 0.28);
  --state-running: #FBBF24;
  --state-running-bg: rgba(245, 158, 11, 0.14);
  --state-running-border: rgba(245, 158, 11, 0.30);
  --state-neutral: #B4CBC7;
  --state-neutral-bg: rgba(157, 184, 179, 0.12);
  --state-neutral-border: rgba(157, 184, 179, 0.24);

  --cat-code: #A5B4FC;
  --cat-secrets: #FCA5A5;
  --cat-iac: #FDBA74;
  --cat-deps: #5EEAD4;
  --cat-license: #D8B4FE;
  --cat-ai: #A5B4FC;
  --cat-container: #7DD3FC;
  --cat-config: #CBD5E1;
  --cat-threat: #67E8F9;
  --cat-exposure: #F0ABFC;
  --cat-other: #94A3B8;

  --surface-hover: rgba(94, 234, 212, 0.06);
  --primary-soft: rgba(45, 212, 191, 0.12);
  --console-bg: #020506;

  /* Graph fills lightened so nodes stay visible on the dark canvas —
     the previous #7f1d1d critical node was near-invisible in dark mode. */
  --graph-critical: #F87171;
  --graph-high: #FB923C;
  --graph-medium: #FBBF24;
  --graph-low: #FDE047;
  --graph-info: #60A5FA;
  --graph-unknown: #94A3B8;
  --graph-asset: #94A3B8;
  --graph-scanner-ok: #5EEAD4;
  --graph-scanner-err: #F87171;
  --graph-scanner-skip: #94A3B8;
  --on-sev-critical: #1A0606;
  --on-sev-high: #1A1206;
  --on-sev-medium: #1A1206;
  --on-sev-info: #06172E;

  --button-primary-bg: linear-gradient(135deg, #5AF2DF, #18B7A4);
  --button-primary-text: #001F1C;
  --button-primary-border: rgba(90, 242, 223, 0.75);
  --button-primary-hover-bg: linear-gradient(135deg, #75F7E7, #20C7B2);
  --button-secondary-bg: rgba(17, 28, 31, 0.96);
  --button-secondary-text: #EAFFFB;
  --button-secondary-border: rgba(90, 242, 223, 0.34);
  --button-secondary-hover-bg: rgba(22, 42, 44, 0.98);
  --button-disabled-bg: rgba(255, 255, 255, 0.06);
  --button-disabled-text: rgba(234, 255, 251, 0.58);
  --button-disabled-border: rgba(234, 255, 251, 0.22);
}

/* ===== Accents ==================================================== */
html[data-accent="blue"] {
  --primary-text: #0369A1;
  --primary: #0EA5E9;
  --primary-light: #7DD3FC;
  --primary-dark: #0369A1;
  --on-primary: #041C2A;
  --low: #0EA5E9;
  --grad-teal: linear-gradient(135deg, #7DD3FC 0%, #0EA5E9 50%, #0369A1 100%);
  --border: rgba(3, 105, 161, .16);
  --border-2: rgba(3, 105, 161, .24);
  --border-3: rgba(3, 105, 161, .34);
  --focus-ring: 0 0 0 3px rgba(3, 105, 161, 0.28);
  --button-primary-bg: linear-gradient(135deg, #7DD3FC, #0284C7);
  --button-primary-text: #041C2A;
  --button-primary-border: rgba(3, 105, 161, .48);
  --button-primary-hover-bg: linear-gradient(135deg, #BAE6FD, #0369A1);
  --button-secondary-text: #075985;
  --button-secondary-border: rgba(3, 105, 161, .34);
  --button-secondary-hover-bg: #F0F9FF;
}

html[data-accent="violet"] {
  --primary-text: #7E22CE;
  --primary: #A855F7;
  --primary-light: #D8B4FE;
  --primary-dark: #7E22CE;
  --on-primary: #24063B;
  --low: #A855F7;
  --grad-teal: linear-gradient(135deg, #D8B4FE 0%, #A855F7 50%, #7E22CE 100%);
  --border: rgba(126, 34, 206, .16);
  --border-2: rgba(126, 34, 206, .24);
  --border-3: rgba(126, 34, 206, .34);
  --focus-ring: 0 0 0 3px rgba(126, 34, 206, 0.28);
  --button-primary-bg: linear-gradient(135deg, #D8B4FE, #9333EA);
  --button-primary-text: #24063B;
  --button-primary-border: rgba(126, 34, 206, .48);
  --button-primary-hover-bg: linear-gradient(135deg, #E9D5FF, #7E22CE);
  --button-secondary-text: #6B21A8;
  --button-secondary-border: rgba(126, 34, 206, .34);
  --button-secondary-hover-bg: #FAF5FF;
}

html[data-accent="indigo"] {
  --primary-text: #4338CA;
  --primary: #6366F1;
  --primary-light: #A5B4FC;
  --primary-dark: #4338CA;
  --on-primary: #11133D;
  --low: #6366F1;
  --grad-teal: linear-gradient(135deg, #A5B4FC 0%, #6366F1 50%, #4338CA 100%);
  --border: rgba(67, 56, 202, .16);
  --border-2: rgba(67, 56, 202, .24);
  --border-3: rgba(67, 56, 202, .34);
  --focus-ring: 0 0 0 3px rgba(67, 56, 202, 0.28);
  --button-primary-bg: linear-gradient(135deg, #A5B4FC, #4F46E5);
  --button-primary-text: #11133D;
  --button-primary-border: rgba(67, 56, 202, .48);
  --button-primary-hover-bg: linear-gradient(135deg, #C7D2FE, #4338CA);
  --button-secondary-text: #3730A3;
  --button-secondary-border: rgba(67, 56, 202, .34);
  --button-secondary-hover-bg: #EEF2FF;
}

html[data-theme="dark"][data-accent="blue"] {
  --primary-text: #7DD3FC;
  --border: rgba(56, 189, 248, .14);
  --border-2: rgba(56, 189, 248, .23);
  --border-3: rgba(56, 189, 248, .34);
  --focus-ring: 0 0 0 3px rgba(56, 189, 248, 0.32);
  --button-secondary-bg: rgba(8, 24, 34, .96);
  --button-secondary-text: #F0F9FF;
  --button-secondary-hover-bg: rgba(12, 39, 55, .98);
}

html[data-theme="dark"][data-accent="violet"] {
  --primary-text: #D8B4FE;
  --border: rgba(192, 132, 252, .14);
  --border-2: rgba(192, 132, 252, .23);
  --border-3: rgba(192, 132, 252, .34);
  --focus-ring: 0 0 0 3px rgba(192, 132, 252, 0.32);
  --button-secondary-bg: rgba(30, 17, 40, .96);
  --button-secondary-text: #FAF5FF;
  --button-secondary-hover-bg: rgba(49, 25, 65, .98);
}

html[data-theme="dark"][data-accent="indigo"] {
  --primary-text: #A5B4FC;
  --border: rgba(129, 140, 248, .14);
  --border-2: rgba(129, 140, 248, .23);
  --border-3: rgba(129, 140, 248, .34);
  --focus-ring: 0 0 0 3px rgba(129, 140, 248, 0.32);
  --button-secondary-bg: rgba(20, 21, 48, .96);
  --button-secondary-text: #EEF2FF;
  --button-secondary-hover-bg: rgba(32, 34, 72, .98);
}

/* Gray Dark shares dark-mode contrast and semantic colors, with neutral chrome. */
html[data-theme="dark"][data-theme-variant="gray"] {
  --bg: #101215; --bg-2: #15181d;
  --surface: #181b20; --card: #181b20; --surface-soft: #1e2228;
  --text: #f1f3f6; --text-dim: #bcc4ce; --text-muted: #919ba9; --text-faint: #828c9a;
  --border: #2b3039; --border-2: #424b58; --border-3: #566173;
  --surface-hover: rgba(180, 190, 208, .07);
  --button-secondary-bg: #1e2228; --button-secondary-text: #f1f3f6;
  --button-secondary-border: #424b58; --button-secondary-hover-bg: #292e37;
}

/* ===== Base ======================================================= */
* { box-sizing: border-box; }
html, body { margin: 0; min-height: 100%; }
[hidden] { display: none !important; }
a { color: inherit; text-decoration: none; }
button, input, select, textarea { font: inherit; color: inherit; }
button { cursor: pointer; }
button:disabled { cursor: not-allowed; }
img, svg { display: block; max-width: 100%; }

/* One focus treatment for the whole product. Components may raise the
   offset, but must not remove the ring. */
:where(a, button, input, select, textarea, summary, [tabindex]):focus-visible {
  outline: var(--focus-width) solid var(--focus-color);
  outline-offset: var(--focus-offset);
  border-radius: inherit;
}

/* Heading baseline. Without this a page that does not match a component
   selector (e.g. the old .main-header on Platform Users) falls back to the
   UA default — a 32px h1 that is off the type scale. */
/* Size/weight only — margins are left to the components, which already set
   them, so this cannot collapse existing spacing. */
:where(h1, h2, h3, h4, h5, h6) {
  font-weight: var(--fw-semibold);
  line-height: var(--lh-tight);
  letter-spacing: -0.01em;
  color: var(--text);
}
:where(h1) { font-size: var(--fs-3xl); letter-spacing: -0.02em; }
:where(h2) { font-size: var(--fs-xl); }
:where(h3) { font-size: var(--fs-lg); }
:where(h4) { font-size: var(--fs-md); }
:where(h5, h6) { font-size: var(--fs-sm); }

/* Disclosure baseline. Zero specificity via :where() so components keep full
   control, but an unstyled <summary> still gets a usable hit area instead of
   collapsing to its 17px line box. */
:where(summary) {
  display: flex;
  align-items: center;
  min-height: var(--control-h-sm);
  border-radius: var(--r-sm);
  cursor: pointer;
}

.sr-only {
  position: absolute;
  width: 1px;
  height: 1px;
  padding: 0;
  margin: -1px;
  overflow: hidden;
  clip: rect(0, 0, 0, 0);
  white-space: nowrap;
  border: 0;
}

/* ===== Shared primitives ========================================== */

/* Uppercase mono label — the product's standard eyebrow/kicker. */
.ds-label {
  font-family: var(--mono);
  font-size: var(--fs-micro);
  font-weight: var(--fw-semibold);
  letter-spacing: var(--tracking-label);
  text-transform: uppercase;
  color: var(--text-muted);
}

/* Status pill. Tone is set by a modifier so the meaning is in the
   markup, not in an inline colour. */
.ds-pill {
  display: inline-flex;
  align-items: center;
  gap: var(--sp-2);
  min-height: 22px;
  padding: 2px var(--sp-2);
  border: 1px solid var(--state-neutral-border);
  border-radius: var(--r-pill);
  background: var(--state-neutral-bg);
  color: var(--state-neutral);
  font-family: var(--mono);
  font-size: var(--fs-micro);
  font-weight: var(--fw-semibold);
  letter-spacing: 0.04em;
  text-transform: uppercase;
  white-space: nowrap;
}
.ds-pill::before {
  content: "";
  width: 6px;
  height: 6px;
  border-radius: 50%;
  background: currentColor;
  flex: 0 0 auto;
}
.ds-pill-plain::before { display: none; }
.ds-pill.is-ok       { color: var(--state-ok);       background: var(--state-ok-bg);       border-color: var(--state-ok-border); }
.ds-pill.is-warn     { color: var(--state-warn);     background: var(--state-warn-bg);     border-color: var(--state-warn-border); }
.ds-pill.is-err      { color: var(--state-err);      background: var(--state-err-bg);      border-color: var(--state-err-border); }
.ds-pill.is-info     { color: var(--state-info);     background: var(--state-info-bg);     border-color: var(--state-info-border); }
.ds-pill.is-running  { color: var(--state-running);  background: var(--state-running-bg);  border-color: var(--state-running-border); }
.ds-pill.is-neutral  { color: var(--state-neutral);  background: var(--state-neutral-bg);  border-color: var(--state-neutral-border); }

/* A running state gets a slow pulse on the dot only — never the text. */
.ds-pill.is-running::before { animation: ds-pulse 1.8s var(--ease) infinite; }
@keyframes ds-pulse {
  0%, 100% { opacity: 1; }
  50% { opacity: 0.35; }
}

/* Severity swatch — used by legends and severity strips. */
.ds-sev-dot {
  display: inline-block;
  width: 9px;
  height: 9px;
  border-radius: 50%;
  flex: 0 0 auto;
  background: var(--sev-unknown);
}
.ds-sev-dot.sev-critical { background: var(--sev-critical); }
.ds-sev-dot.sev-high     { background: var(--sev-high); }
.ds-sev-dot.sev-medium   { background: var(--sev-medium); }
.ds-sev-dot.sev-low      { background: var(--sev-low); }
.ds-sev-dot.sev-info     { background: var(--sev-info); }
.ds-sev-dot.sev-unknown  { background: var(--sev-unknown); }

/* Inline link inside body copy. */
.u-link {
  color: var(--primary-text);
  text-decoration: underline;
  text-underline-offset: 2px;
  text-decoration-color: color-mix(in srgb, var(--primary-text) 45%, transparent);
  border-radius: var(--r-sm);
  transition: text-decoration-color var(--dur-1) var(--ease);
}
.u-link:hover { text-decoration-color: currentColor; }

/* Small typographic utilities. These replace the inline
   style="font-size:11px;color:var(--text-muted)" fragments that page scripts
   were emitting — the sizes had drifted off the scale (10.5/11.5/12.5px) and
   could not be themed or restyled. */
.u-meta {
  font-size: var(--fs-2xs);
  color: var(--text-muted);
}
.u-meta-mono {
  font-family: var(--mono);
  font-size: var(--fs-2xs);
  color: var(--text-muted);
}
.u-sub {
  font-size: var(--fs-xs);
  color: var(--text-muted);
}
.u-sub-mono {
  font-family: var(--mono);
  font-size: var(--fs-xs);
  color: var(--text-muted);
}
.u-num {
  font-family: var(--mono);
  font-size: var(--fs-xs);
  color: var(--text);
  min-width: 28px;
  text-align: right;
}
.u-key {
  font-family: var(--mono);
  font-size: var(--fs-2xs);
  color: var(--text-muted);
  min-width: 84px;
}

/* Skeleton shimmer for loading values. */
.ds-skeleton {
  color: transparent !important;
  background: linear-gradient(90deg,
    var(--border) 25%, var(--border-2) 37%, var(--border) 63%);
  background-size: 200% 100%;
  border-radius: var(--r-sm);
  animation: ds-shimmer 1.4s ease-in-out infinite;
}
@keyframes ds-shimmer {
  0% { background-position: 200% 0; }
  100% { background-position: -200% 0; }
}

/* ===== Motion policy ============================================== */
/* Honour the OS setting everywhere, including animations declared by
   page stylesheets that were written before this file existed. */
@media (prefers-reduced-motion: reduce) {
  *,
  *::before,
  *::after {
    animation-duration: 0.001ms !important;
    animation-iteration-count: 1 !important;
    transition-duration: 0.001ms !important;
    scroll-behavior: auto !important;
  }
}

/* ===== Coarse pointer ============================================= */
/* Touch devices get full-size targets without changing desktop density. */
@media (pointer: coarse) {
  :where(button, a.btn, [role="button"], [role="tab"], .nav-item, .modal-close, .btn-icon) {
    min-height: var(--tap-min);
  }
  :where(.btn-icon, .modal-close) { min-width: var(--tap-min); }
  /* Switch/checkbox labels are the real target — the input itself is
     opacity:0 and 0x0 — so the LABEL is what has to be thumb-sized. */
  :where(label.tog, label.toggle, label.notif-check, label.mobile-authorization,
         label.easm-authorize, .r-filter-toggle) {
    min-height: var(--tap-min);
  }
  :where(label.tog) { min-width: var(--tap-min); }
  /* Calendar event chips sit at the WCAG 2.5.8 24px minimum on a pointer
     device to keep three events per day cell; touch gets the full target. */
  :where(.cal-ev) { min-height: 36px; }
}
