/*!
 * SpeedGoat Design System — speedgoat.css
 * v3.0.1 · 2026-08-20
 * Single distributable stylesheet. Drop this file, the fonts/ folder, and
 * the speedgoat-bg.* images into your app (e.g. Shiny www/) and load with
 * one line:
 *   <link rel="stylesheet" href="speedgoat.css">
 * Ships: @font-face (Inter + IBM Plex Mono), color tokens (canonical,
 * count-web HSL scales + aliases), typography, spacing/radius, effects,
 * .sg-prose for rendered markdown, the Leaflet map tokens, the six-slot
 * species palette (--sp-*), the shared background image (--bg-image +
 * .sg-canvas), and the [data-theme="dark"] scope.
 * BREAKING (v3.0.0): the radius scale is namespaced --sg-radius* (13/9/6px).
 * shadcn/ui already owns --radius, --radius-sm and --radius-md, so under the
 * plain names an app linking this file had every shadcn component silently
 * reshaped by load order. Migrate var(--radius…) -> var(--sg-radius…), and
 * GREP rather than assume: bare var(--radius) still RESOLVES, to 8px, so it
 * is defined, plausible and wrong; --radius-sm / --radius-md are undefined,
 * which invalidates the declaration and squares the corner off.
 * STILL BREAKING from v2.0.0: the count-web/shadcn names --primary and
 * --border are HSL component triples (consume as hsl(var(--primary)) or the
 * resolved var(--color-primary) / var(--color-border)); they are not resolved
 * colors. This aligns with count-web so hsl(var(--primary)) resolves the same
 * in an adopting app regardless of stylesheet load order.
 * Source of truth: this design system repo. Do not hand-edit — regenerate.
 */

/* ===== Fonts ===== */
/* ============================================================
   SpeedGoat — Self-hosted webfonts (SIL OFL)
   Inter (UI) + IBM Plex Mono (numerals). Latin + latin-ext subsets.
   Binaries in assets/fonts/. No runtime dependency on Google Fonts.
   ============================================================ */
@font-face{
  font-family:'IBM Plex Mono';
  font-style:normal;
  font-weight:400;
  font-display:swap;
  src:url("fonts/ibm-plex-mono-400-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:'IBM Plex Mono';
  font-style:normal;
  font-weight:400;
  font-display:swap;
  src:url("fonts/ibm-plex-mono-400.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:'IBM Plex Mono';
  font-style:normal;
  font-weight:500;
  font-display:swap;
  src:url("fonts/ibm-plex-mono-500-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:'IBM Plex Mono';
  font-style:normal;
  font-weight:500;
  font-display:swap;
  src:url("fonts/ibm-plex-mono-500.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:'IBM Plex Mono';
  font-style:normal;
  font-weight:600;
  font-display:swap;
  src:url("fonts/ibm-plex-mono-600-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:'IBM Plex Mono';
  font-style:normal;
  font-weight:600;
  font-display:swap;
  src:url("fonts/ibm-plex-mono-600.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:'Inter';
  font-style:normal;
  font-weight:400;
  font-display:swap;
  src:url("fonts/inter-400-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:'Inter';
  font-style:normal;
  font-weight:400;
  font-display:swap;
  src:url("fonts/inter-400.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:'Inter';
  font-style:normal;
  font-weight:500;
  font-display:swap;
  src:url("fonts/inter-500-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:'Inter';
  font-style:normal;
  font-weight:500;
  font-display:swap;
  src:url("fonts/inter-500.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:'Inter';
  font-style:normal;
  font-weight:600;
  font-display:swap;
  src:url("fonts/inter-600-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:'Inter';
  font-style:normal;
  font-weight:600;
  font-display:swap;
  src:url("fonts/inter-600.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:'Inter';
  font-style:normal;
  font-weight:700;
  font-display:swap;
  src:url("fonts/inter-700-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:'Inter';
  font-style:normal;
  font-weight:700;
  font-display:swap;
  src:url("fonts/inter-700.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:'Inter';
  font-style:normal;
  font-weight:800;
  font-display:swap;
  src:url("fonts/inter-800-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:'Inter';
  font-style:normal;
  font-weight:800;
  font-display:swap;
  src:url("fonts/inter-800.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;
}


/* ===== Colors (canonical) ===== */
/* ============================================================
   SpeedGoat — Color tokens  (CANONICAL)
   Source of truth: count-web (production shadcn/ui + Tailwind v4).
   Figma was the original starting point; the production app is the
   authority, so these values mirror count-web/src/app/globals.css.

   Structure:
     1. Raw semantic scales as HSL triples  (--action-primary: H,S,L)
     2. Ready-to-use color aliases           (--color-*  → hsl(...))
     3. Legacy compatibility aliases         (--primary, --success, …)
        kept so older DS cards keep working, repointed to canon.
   Prefer the semantic scales for anything new.
   ============================================================ */
:root{
  /* ---------- 1. RAW SEMANTIC SCALES (H,S,L triples) ---------- */

  /* base / neutrals */
  --base-4: 215, 16%, 47%; /* @kind color */
  --base-5: 213, 27%, 84%; /* @kind color */
  --base-invert: 0, 0%, 100%; /* @kind color */
  --primitive-blue-700: 212, 82%, 66%; /* @kind color */

  /* text ramp */
  --text-base-1: 222, 47%, 11%; /* @kind color */ /* primary text */
  --text-base-2: 215, 25%, 27%; /* @kind color */
  --text-base-3: 215, 16%, 47%; /* @kind color */ /* labels / muted */
  --text-base-4: 215, 20%, 65%; /* @kind color */ /* placeholder */
  --text-base-5: 214, 32%, 91%; /* @kind color */
  --text-primary-1: 216, 86%, 30%; /* @kind color */
  --text-primary-2: 217, 67%, 44%; /* @kind color */
  --text-primary-3: 217, 67%, 44%; /* @kind color */
  --text-primary-4: 212, 100%, 81%; /* @kind color */

  /* surface */
  --surface-primary-1: 208, 94%, 7%; /* @kind color */
  --surface-primary-2: 212, 86%, 15%; /* @kind color */
  --surface-primary-3: 214, 54%, 97%; /* @kind color */
  --surface-secondary-1: 210, 40%, 96%; /* @kind color */
  --surface-secondary-2: 210, 40%, 98%; /* @kind color */
  --surface-background-1: 210, 40%, 96%; /* @kind color */
  --surface-background-2: 210, 40%, 98%; /* @kind color */

  /* action */
  --action-primary: 217, 85%, 36%; /* @kind color */ /* #0E4AAA — primary actions */
  --action-primary-hovered: 215, 86%, 25%; /* @kind color */
  --action-primary-focused: 215, 86%, 25%; /* @kind color */
  --action-primary-disabled: 217, 54%, 79%; /* @kind color */
  --action-secondary: 0, 0%, 100%; /* @kind color */
  --action-secondary-hovered: 210, 40%, 96%; /* @kind color */
  --action-secondary-focused: 210, 40%, 96%; /* @kind color */
  --action-secondary-disabled: 210, 40%, 98%; /* @kind color */

  /* line / borders */
  --line-primary-1: 217, 67%, 44%; /* @kind color */
  --line-primary-2: 217, 54%, 67%; /* @kind color */
  --line-primary-3: 217, 55%, 87%; /* @kind color */
  --line-primary-4: 218, 52%, 96%; /* @kind color */
  --line-secondary-1: 217, 33%, 17%; /* @kind color */
  --line-secondary-2: 215, 20%, 65%; /* @kind color */
  --line-secondary-3: 214, 32%, 91%; /* @kind color */
  --line-secondary-4: 210, 40%, 96%; /* @kind color */
  --line-secondary-5: 210, 40%, 98%; /* @kind color */

  /* primary ramp */
  --primary-1: 212, 86%, 15%; /* @kind color */
  --primary-3: 217, 67%, 44%; /* @kind color */
  --primary-4: 212, 100%, 81%; /* @kind color */
  --primary-5: 217, 54%, 92%; /* @kind color */
  --primary-core: 217, 85%, 36%; /* @kind color */
  --primary-hovered: 214, 86%, 20%; /* @kind color */
  --primary-disabled: 217, 54%, 79%; /* @kind color */

  /* feedback — success */
  --success-1: 143, 55%, 22%; /* @kind color */
  --success-2: 143, 55%, 34%; /* @kind color */
  --success-3: 143, 55%, 47%; /* @kind color */ /* #36BA68 */
  --success-4: 143, 55%, 56%; /* @kind color */
  --success-5: 141, 54%, 95%; /* @kind color */

  /* feedback — warning */
  --warning-1: 31, 70%, 20%; /* @kind color */
  --warning-2: 30, 70%, 34%; /* @kind color */
  --warning-3: 31, 90%, 56%; /* @kind color */ /* #F4922A */
  --warning-4: 31, 100%, 62%; /* @kind color */
  --warning-5: 37, 100%, 94%; /* @kind color */

  /* feedback — error */
  --error-1: 8, 73%, 18%; /* @kind color */
  --error-2: 8, 73%, 18%; /* @kind color */
  --error-3: 9, 75%, 50%; /* @kind color */ /* #DF3D20 */
  --error-4: 9, 75%, 58%; /* @kind color */
  --error-5: 6, 76%, 95%; /* @kind color */

  /* shadcn base aliases */
  --background: 0, 0%, 100%; /* @kind color */
  --foreground: 222.2, 84%, 4.9%; /* @kind color */
  --card: 0, 0%, 100%; /* @kind color */
  --muted: 210, 40%, 96.1%; /* @kind color */
  --border-hsl: 214, 32%, 91%; /* @kind color */
  --input-hsl: 214, 32%, 91%; /* @kind color */
  --ring: 222.2, 84%, 4.9%; /* @kind color */
  --destructive: 0, 84.2%, 60.2%; /* @kind color */
  --radius: 0.5rem; /* @kind radius */

  /* ---------- 2. READY-TO-USE COLOR ALIASES ---------- */
  --color-action-primary: hsl(var(--action-primary));
  --color-action-primary-hovered: hsl(var(--action-primary-hovered));
  --color-action-primary-disabled: hsl(var(--action-primary-disabled));
  --color-primary: hsl(var(--primary)); /* count-web alias for the resolved primary */
  --color-action-secondary: hsl(var(--action-secondary));
  --color-action-secondary-hovered: hsl(var(--action-secondary-hovered));

  --color-text-base-1: hsl(var(--text-base-1));
  --color-text-base-2: hsl(var(--text-base-2));
  --color-text-base-3: hsl(var(--text-base-3));
  --color-text-base-4: hsl(var(--text-base-4));
  --color-text-primary-1: hsl(var(--text-primary-1));
  --color-text-primary-2: hsl(var(--text-primary-2));

  --color-success: hsl(var(--success-3));
  --color-warning: hsl(var(--warning-3));
  --color-error: hsl(var(--error-3));

  --color-line-primary-2: hsl(var(--line-primary-2));
  --color-line-primary-3: hsl(var(--line-primary-3));
  --color-line-primary-4: hsl(var(--line-primary-4));
  --color-border: hsl(var(--line-secondary-3));
  --color-surface-secondary-1: hsl(var(--surface-secondary-1));

  /* ---------- 3. LEGACY COMPATIBILITY ALIASES ----------
     Older DS cards + StatCard/Button specimens use these names.
     Repointed to canonical count-web values (primary now #0E4AAA). */
  --primary: 217, 85%, 36%; /* @kind color */ /* HSL component, count-web-aligned (#0E4AAA). Consume via hsl(var(--primary)) or var(--color-primary) — never bare var(--primary). */
  --primary-ink: hsl(var(--action-primary-hovered));
  --primary-soft: hsl(var(--primary-5));

  /* ---------- INK CHROME ----------
     The brand's signature ink-framed workspace: dark chrome (header, sidebar,
     console panels) sitting BESIDE light cards in a light app. This is NOT the
     dark theme — tokens/dark.css is a whole-page scope you opt into with
     data-theme="dark", and it remaps everything. Ink chrome coexists with light
     surfaces and is always dark, in both themes.

     Until now the system shipped three ink surfaces and NOTHING to put on them —
     no text ramp, no divider, no primary. dark.css even says "the ink chrome
     already reads dark and is unchanged" while providing nothing to build it
     with, so every app that needed ink chrome invented its own ramp. The
     ipm-tool ui_kit did exactly that, in 153 hardcoded hex occurrences, and
     independently rediscovered --ink-3 as #233149.

     Contrast on --ink (#0F172A), computed: ink-text-1 14.89:1, -2 12.42:1,
     -3 8.12:1, -4 5.12:1 — all four clear WCAG AA for body text, and the first
     three clear AAA. Reserve -4 for labels and disabled states by HIERARCHY,
     not because it fails contrast. The brand primary cannot be used on ink at
     all (#0E4AAA is 2.19:1 — fails), which is why --ink-primary is brightened
     to #4D9BFF (6.33:1, AA) with --ink-primary-2 at 8.17:1 (AAA).

     CAVEAT on the two structural steps: --ink-line (1.48:1) and --ink-hover
     (1.74:1) are below the 3:1 that WCAG 1.4.11 asks of meaningful UI
     boundaries. They are fine as decorative dividers and hover washes — which
     is all the ink chrome uses them for — but if a boundary is the ONLY thing
     conveying a control's extent or state, do not rely on them alone. */
  --ink:#0F172A;                /* default ink chrome */

  /* The standard ink chrome gradient, as ONE token. There is no new colour
     here: it is --ink over --ink-deep, which is exactly what the ipm-tool
     header already drew inline (index.html: linear-gradient(180deg,
     var(--ink), var(--ink-deep))). Named only so the two-stop recipe has one
     definition instead of being retyped per surface. --ink alone stays the
     FLAT ink surface — a sidebar, or a solid header.

     v3.0.1 removed an --ink-grad-from/--ink-grad-to pair added in v3.0.0:
     it duplicated --ink / --ink-deep at values 2-3 units off them, which is
     the collision this system exists to prevent. Nothing outside the repo
     had a chance to consume them. */
  --ink-gradient: linear-gradient(180deg, var(--ink), var(--ink-deep));
  --ink-2:#172033;              /* panel on ink */
  --ink-3:#243049;              /* raised panel on ink */
  --ink-deep:#0C1424;           /* frame behind ink panels — deepest step */
  --ink-raised:#1F2A40;         /* card/well on ink */
  --ink-line:#2A3650;           /* divider / border on ink */
  --ink-hover:#33415A;          /* hover + active on ink */
  --ink-text-1:#E4EBF6;         /* primary text on ink */
  --ink-text-2:#CDD8EA;         /* body text on ink */
  --ink-text-3:#9FB0CC;         /* labels on ink */
  --ink-text-4:#7C8AA3;         /* dimmest — labels/disabled only, not body */
  --ink-primary:#4D9BFF;        /* primary action on ink (brightened for contrast) */
  --ink-primary-2:#7FB3F0;      /* primary hover / secondary accent on ink */

  --success: hsl(var(--success-3));
  --success-ink: hsl(var(--success-2));
  --success-soft: hsl(var(--success-5));
  --warning: hsl(var(--warning-3));             /* was #FBAC12 (Figma) → now #F4922A */
  --warning-ink: hsl(var(--warning-2));
  --warning-soft: hsl(var(--warning-5));
  --danger: hsl(var(--error-3));                /* was #FA3232 → now #DF3D20 */
  --danger-soft: hsl(var(--error-5));

  /* projection/forecast accent — app-specific, not a count-web token.
     Retained as a named data-viz accent only. */
  --projection:#FF671F;
  --projection-ink:#e55a1a;
  --projection-soft:rgba(255,103,31,.12);

  --text-1: hsl(var(--text-base-1));
  --text-2: hsl(var(--text-base-2));
  --text-3: hsl(var(--text-base-3));
  --text-muted: hsl(var(--text-base-4));

  --border: 214.3, 31.8%, 91.4%; /* @kind color */ /* HSL component, count-web-aligned. Consume via hsl(var(--border)) or var(--color-border). */
  --border-2: hsl(var(--line-secondary-2));
  --bg: hsl(var(--surface-background-1));
  --surface:#ffffff;
  --surface-2: hsl(var(--surface-secondary-2));


  /* ---------- BRAND MARK ----------
     The two tones the logo itself is drawn in. These are NOT the UI action
     colour: --action-primary (#0E4AAA) is for buttons, links and active state;
     these are for the mark, the wordmark, and the brand name when it is set in
     type (Speed in --brand-light, Goat in --brand-dark; Goat goes white on ink
     chrome, where the dark tone disappears).
     Source: the brand-kit lockups in design/brand-source/ — three files agree on
     this pair, and it matches the historical favicon.
     The ribbon palette's --vb-high and --vb-medium, and map strata 2 and 5, were
     #004672 and #0D7DC5 — the same two colours carrying export drift. They now
     reference these, so the brand pair has exactly one definition. */
  --brand-dark:#004673;   /* @kind color */
  --brand-light:#0f7dc3;  /* @kind color */

  /* data ribbon palette (value boxes, categorical series) — app data-viz ramp */
  --vb-high: var(--brand-dark);
  --vb-medium: var(--brand-light);
  --vb-dark:#0A5A9C;
  --vb-grey:#64748b;
  --vb-low:#5D6578;
}


/* ===== Typography ===== */
/* ============================================================
   SpeedGoat — Typography tokens
   UI face: Inter. Numerics/code: IBM Plex Mono.
   The scale below reflects sizes used across the IPM tool.
   ============================================================ */
:root{
  /* --- families --- */
  --ui:"Inter", system-ui, -apple-system, sans-serif; /* @kind font */
  --mono:"IBM Plex Mono", ui-monospace, "SF Mono", monospace; /* @kind font */

  /* --- weights --- */
  --w-regular:400; /* @kind font */
  --w-medium:500; /* @kind font */
  --w-semibold:600; /* @kind font */
  --w-bold:700; /* @kind font */
  --w-heavy:800; /* @kind font */ /* metric values, key figures */

  /* --- type scale (px) --- */
  --fs-display:23px;  /* headline metric values */
  --fs-h1:21px;       /* section / placeholder titles */
  --fs-h2:17px;       /* card titles */
  --fs-lg:15px;       /* brand name, emphasized body */
  --fs-base:14.5px;   /* body default */
  --fs-body:13.5px;   /* dense body, buttons */
  --fs-sm:12.5px;     /* controls, pills, secondary */
  --fs-xs:11.5px;     /* captions, links */
  --fs-2xs:11px;      /* stat labels */
  --fs-micro:9.5px;   /* uppercase eyebrow labels */

  /* --- tracking --- */
  --track-tight:-.02em;  /* large / heavy figures */
  --track-snug:-.01em;
  --track-wide:.04em;    /* uppercase labels */
  --track-caps:.08em;    /* small caps eyebrows */

  /* --- line height --- */
  --lh-tight:1.1; /* @kind font */
  --lh-snug:1.3; /* @kind font */
  --lh-body:1.55; /* @kind font */
}


/* ===== Spacing & radius ===== */
/* ============================================================
   SpeedGoat — Spacing, radius & density tokens

   RADIUS IS NAMESPACED, ON PURPOSE. shadcn/ui — which both count-web
   and telemetry-web are built on — defines --radius (0.5rem = 8px),
   --radius-sm (calc(--radius - 4px) = 4px) and --radius-md
   (calc(--radius - 2px) = 6px). This system's brand radii are 13/9/6,
   so the same three names carried different values in the two places.
   An app linking dist/speedgoat.css would have had every shadcn
   component silently reshaped by stylesheet load order — cards 8→13px,
   anything rounded-sm 4→9px — with nothing in the app changed to cause
   it. That is the --primary/--border collision fixed at v2.0.0, in a
   different guise.

   --sg-* cannot collide, so adopting the stylesheet no longer changes
   an app's geometry. A consuming app opts in by referencing --sg-radius*
   explicitly, or by mapping its own --radius onto ours.
   ============================================================ */
:root{
  --pad:20px;        /* card interior padding */
  --gap:18px;        /* grid / stack gap */
  --fs:14.5px;       /* base font-size hook (density-linked) */

  --sg-radius:13px;     /* cards, panels */
  --sg-radius-sm:9px;   /* controls, buttons, small cards */
  --sg-radius-md:6px;   /* shadcn rounded-md — buttons, inputs, selects */
  --sg-radius-xs:6px;   /* chips, inline tags */

  /* discrete step values used across layouts */
  --space-1:4px;
  --space-2:8px;
  --space-3:12px;
  --space-4:14px;
  --space-5:18px;
  --space-6:22px;
  --space-7:28px;
}

/* Density modes — set data-density on a container to rescale. */
[data-density="compact"]{ --pad:14px; --gap:12px; --fs:13.5px; --sg-radius:10px; }
[data-density="comfy"]{ --pad:28px; --gap:24px; --fs:15.5px; --sg-radius:15px; }


/* ===== Effects ===== */
/* ============================================================
   SpeedGoat — Elevation & effect tokens
   Shadows are soft, low-spread, slate-tinted. Cards lean on
   a hairline border + faint shadow rather than heavy elevation.
   ============================================================ */
:root{
  --shadow:0 1px 2px rgba(15,23,42,.05), 0 2px 6px rgba(15,23,42,.04);   /* resting card */
  --shadow-pop:0 8px 28px rgba(15,23,42,.16);                            /* popovers, menus, modals */

  /* accent shadows for filled buttons */
  --shadow-primary:0 1px 2px rgba(0,0,0,.20);
  --shadow-success:0 1px 3px rgba(54,186,104,.40);

  --focus-ring:0 0 0 3px rgba(14,74,170,.28);   /* keyboard focus */
  --hairline:1px solid var(--color-border);
}

/* ============================================================
   Motion

   README documents the motion rule — "fast and functional: 0.13–0.22s ease …
   no bounce, no spring, no entrance animation on content" — but shipped no
   token for it, so every surface hardcoded its own duration and none of them
   honoured a reduced-motion preference. Flagged by the ipm-tool regeneration,
   whose loading state honoured the query the system itself did not.

   --motion-fast   micro-feedback: color, background, border on hover/press
   --motion-base   the default for transform and layout-affecting changes
   --motion-slow   the top of the documented range — rail collapse, drawers

   prefers-reduced-motion is a stated accessibility need, not a preference to
   weigh: vestibular disorders make large transform animation genuinely
   unpleasant. Durations collapse to ~0 rather than to `none` so that
   transitionend / animationend listeners still fire and no state machine that
   waits on them can hang.
   ============================================================ */
:root{
  --motion-fast:.13s;   /* @kind other */
  --motion-base:.18s;   /* @kind other */
  --motion-slow:.22s;   /* @kind other */
  --motion-ease:ease;   /* @kind other */
}

@media (prefers-reduced-motion: reduce){
  :root{
    --motion-fast:.01ms;   /* @kind other */
    --motion-base:.01ms;   /* @kind other */
    --motion-slow:.01ms;   /* @kind other */
  }
  /* Catch-all for anything not yet routed through the tokens above. */
  *, *::before, *::after{
    animation-duration:.01ms !important;
    animation-iteration-count:1 !important;
    transition-duration:.01ms !important;
    scroll-behavior:auto !important;
  }
}


/* ===== Prose (rendered markdown) ===== */
/* ============================================================
   SpeedGoat — Prose (`.sg-prose`)
   Long-form rendered output: markdown, Rmd/Quarto reports, vignettes.

   WHY THIS EXISTS. Markdown is one of the estate's larger surfaces and none of
   it was branded:
     * 17 pkgdown sites, every one on plain `template: bootstrap: 5`;
     * `includeMarkdown()` About/contact pages in 8+ Shiny apps;
     * `spdgt.assist`'s chat gadget, which renders md → HTML and already takes a
       `css` argument with nothing to give it;
     * six copies of the IPM Analysis Report, all `theme: spacelab` with their
       stylesheet fetched from a **Google Drive URL**.
   The token layer had a type SCALE but nothing that applied it to a document.

   HOW TO USE IT. Wrap the rendered HTML, don't restyle it:
     Shiny:   div(class = "sg-prose", includeMarkdown("www/About.md"))
     Rmd:     <div class="sg-prose"> … </div>, or set it on <body>
     gadget:  pass `css` pointing at speedgoat.css and wrap the message body

   SCOPED ON PURPOSE. Every rule is under `.sg-prose`, so linking
   speedgoat.css cannot restyle an app's own headings and tables — the mistake
   that made the radius namespacing necessary. Nothing here is an element
   selector at top level.

   Uses only semantic tokens, so `[data-theme="dark"]` inverts it for free —
   there is deliberately no dark block in this file. Verified by gate 12, which
   now also scans this file for var() references that do not resolve.
   ============================================================ */

.sg-prose{
  font-family:var(--ui);
  font-size:var(--fs-base);
  line-height:var(--lh-body);
  color:var(--text-1);
  /* A measure, not a full-bleed column. Long-form text past ~78 characters is
     measurably harder to track back to the next line, and these reports are
     read on wide dashboards. Override with `max-width` where a table needs it. */
  max-width:78ch;
}
.sg-prose > :first-child{ margin-top:0; }
.sg-prose > :last-child{ margin-bottom:0; }

/* --- headings: the existing scale, not new sizes --------------------------- */
.sg-prose h1,
.sg-prose h2,
.sg-prose h3,
.sg-prose h4,
.sg-prose h5,
.sg-prose h6{
  font-family:var(--ui);
  color:var(--text-1);
  line-height:var(--lh-snug);
  letter-spacing:var(--track-snug);
  margin:1.9em 0 .6em;
}
.sg-prose h1{ font-size:var(--fs-h1); font-weight:var(--w-bold); letter-spacing:var(--track-tight); }
.sg-prose h2{ font-size:var(--fs-h2); font-weight:var(--w-semibold); }
.sg-prose h3{ font-size:var(--fs-lg); font-weight:var(--w-semibold); }
.sg-prose h4{ font-size:var(--fs-base); font-weight:var(--w-semibold); }
/* h5/h6 become eyebrow labels rather than shrinking below body size — the IPM
   report nests four levels deep (## Abundance → #### Preseason Abundance). */
.sg-prose h5,
.sg-prose h6{
  font-size:var(--fs-micro);
  font-weight:var(--w-bold);
  text-transform:uppercase;
  letter-spacing:var(--track-caps);
  color:var(--text-3);
}
/* A rule under the top two levels, the way the report's sections read. */
.sg-prose h1,
.sg-prose h2{
  padding-bottom:.3em;
  border-bottom:1px solid hsl(var(--line-secondary-3));
}

/* --- body ----------------------------------------------------------------- */
.sg-prose p{ margin:0 0 1.05em; }
.sg-prose strong{ font-weight:var(--w-semibold); color:var(--text-1); }
.sg-prose small{ font-size:var(--fs-sm); color:var(--text-2); }
.sg-prose hr{
  border:0;
  border-top:1px solid hsl(var(--line-secondary-3));
  margin:2em 0;
}

/* --- links: brand blue, underlined. Underline is not decoration here — these
       documents get printed and read at a glance, and colour alone is not an
       accessible affordance. ------------------------------------------------ */
.sg-prose a{
  color:var(--color-action-primary);
  text-decoration:underline;
  text-underline-offset:2px;
  text-decoration-thickness:1px;
}
.sg-prose a:hover{ color:hsl(var(--action-primary-hovered)); }

/* --- lists ---------------------------------------------------------------- */
.sg-prose ul,
.sg-prose ol{ margin:0 0 1.05em; padding-left:1.4em; }
.sg-prose li{ margin:.3em 0; }
.sg-prose li > ul,
.sg-prose li > ol{ margin:.3em 0; }
.sg-prose li::marker{ color:var(--text-3); }
.sg-prose dt{ font-weight:var(--w-semibold); margin-top:.8em; }
.sg-prose dd{ margin:.2em 0 0 1.4em; color:var(--text-2); }

/* --- numbers are monospaced and tabular ----------------------------------
   The brand's most-lost rule: every number, metric and code value uses
   var(--mono). In a report that means inline code AND table cells, because a
   column of proportional digits does not align and cannot be scanned. */
.sg-prose code,
.sg-prose kbd,
.sg-prose samp{
  font-family:var(--mono);
  font-size:.92em;
  font-variant-numeric:tabular-nums;
}
.sg-prose :not(pre) > code{
  background:hsl(var(--surface-secondary-1));
  color:var(--text-1);
  padding:.1em .38em;
  border-radius:var(--sg-radius-xs);
}
.sg-prose pre{
  font-family:var(--mono);
  font-size:var(--fs-sm);
  line-height:var(--lh-snug);
  background:hsl(var(--surface-secondary-1));
  border:1px solid hsl(var(--line-secondary-3));
  border-radius:var(--sg-radius-md);
  padding:12px 14px;
  overflow-x:auto;
  margin:0 0 1.05em;
}
.sg-prose pre > code{ background:none; padding:0; border-radius:0; }

/* --- tables: the report is mostly tables ---------------------------------- */
.sg-prose table{
  border-collapse:collapse;
  width:100%;
  font-size:var(--fs-sm);
  margin:0 0 1.3em;
  /* Numeric columns must align, so the whole table is tabular. */
  font-variant-numeric:tabular-nums;
}
.sg-prose thead th{
  text-align:left;
  font-size:var(--fs-micro);
  font-weight:var(--w-bold);
  text-transform:uppercase;
  letter-spacing:var(--track-wide);
  color:var(--text-3);
  padding:0 10px 7px;
  border-bottom:1px solid hsl(var(--line-secondary-3));
  white-space:nowrap;
}
.sg-prose tbody td,
.sg-prose tbody th{
  padding:7px 10px;
  border-bottom:1px solid hsl(var(--line-secondary-4));
  vertical-align:top;
}
.sg-prose tbody tr:last-child td{ border-bottom:0; }
.sg-prose caption{
  caption-side:bottom;
  text-align:left;
  font-size:var(--fs-xs);
  color:var(--text-3);
  padding-top:8px;
}

/* --- blockquote: used for the report's "Using this report" guidance -------- */
.sg-prose blockquote{
  margin:0 0 1.05em;
  padding:2px 0 2px 14px;
  border-left:3px solid hsl(var(--line-secondary-2));
  color:var(--text-2);
}
.sg-prose blockquote > :last-child{ margin-bottom:0; }

/* --- figures -------------------------------------------------------------- */
.sg-prose img{ max-width:100%; height:auto; border-radius:var(--sg-radius-md); }
.sg-prose figure{ margin:0 0 1.3em; }
.sg-prose figcaption{
  font-size:var(--fs-xs);
  color:var(--text-3);
  padding-top:6px;
}

/* --- a wide variant, for a report that is mostly tables ------------------
   `.sg-prose.sg-prose-wide` drops the measure. Separate rather than an
   override so a consumer opts in per document instead of fighting max-width. */
.sg-prose-wide{ max-width:none; }


/* ===== Background image ===== */
/* ============================================================
   SpeedGoat — Background image
   The shared brand backdrop (soft layered mountains) used across the
   SpeedGoat apps. Optimized to webp/avif (~15 KB, was ~1.6 MB PNG).
   Binaries live in assets/ (source) and dist/ (distributable); the
   url()s below are relative, so they resolve from either location —
   exactly like assets/fonts.css.
     • light backdrop in :root
     • dusk variant in the [data-theme="dark"] scope
   ============================================================ */
:root{
  --bg-image: image-set(
    url("speedgoat-bg.avif") type("image/avif"),
    url("speedgoat-bg.webp") type("image/webp")); /* @kind other */
}
[data-theme="dark"], .dark{
  --bg-image: image-set(
    url("speedgoat-bg-dark.avif") type("image/avif"),
    url("speedgoat-bg-dark.webp") type("image/webp")); /* @kind other */
}

/* Full-bleed brand canvas. Put .sg-canvas on <body> (or a full-height
   wrapper). Mirrors what the apps set by hand: cover / center / fixed.
   The first background-image is a plain fallback for engines without
   image-set(); var(--bg-image) wins everywhere else and follows the
   dark scope automatically. */
.sg-canvas{
  background-image: url("speedgoat-bg.webp");
  background-image: var(--bg-image);
  background-size: cover;
  background-repeat: no-repeat;
  background-position: center center;
  background-attachment: fixed;
}


/* ===== Map tokens (Leaflet) ===== */
/* ============================================================
   SpeedGoat — Map tokens  (Leaflet)
   Canonicalizes the hardcoded literals in count-web's map layers
   (map/, map-as-lines/, canvas-pin-marker) so every map draws from
   one place instead of inline hex.

   Colour rule holds on maps too: ONE brand hue. The stratum ramp is
   a blue→neutral progression, not a rainbow — green/amber/red stay
   status-only, and --projection stays forecast-only.
   ============================================================ */
:root{
  /* ---------- polygons: management units & subunits ---------- */
  --map-boundary-stroke: hsl(var(--text-base-1)); /* @kind color */ /* outer management-unit outline (was "black") */
  --map-unit-stroke: hsl(var(--line-secondary-2)); /* @kind color */ /* resting subunit edge (was #b1b1b1) */
  --map-unit-stroke-selected: hsl(var(--base-invert)); /* @kind color */ /* selected subunit edge */
  --map-unit-fill-selected: hsl(var(--text-base-1)); /* @kind color */ /* selected subunit body (was #000000) */

  /* ---------- lines & transects ---------- */
  --map-line: hsl(var(--action-primary)); /* @kind color */ /* default transect (was #2560BC — stale Figma primary) */
  --map-line-hovered: hsl(var(--warning-3)); /* @kind color */ /* hovered transect (was #F0953C) */

  /* ---------- point markers ---------- */
  --map-marker-dot: hsl(var(--base-invert)); /* @kind color */ /* centre dot of a radius marker */
  --map-marker-ring: hsl(var(--action-primary)); /* @kind color */ /* fallback when no stratum colour applies */

  /* ---------- stratum ramp ----------
     Strata normally carry a data-driven `mapColor`. This is the
     fallback ramp when they don't — 9 steps to match the nine
     stratified variants the design library defines. */
  --map-stratum-1: hsl(var(--primary-1)); /* @kind color */
  --map-stratum-2: var(--brand-dark); /* @kind color */
  --map-stratum-3:#0A5A9C; /* @kind color */
  --map-stratum-4: hsl(var(--action-primary)); /* @kind color */
  --map-stratum-5: var(--brand-light); /* @kind color */
  --map-stratum-6: hsl(var(--primary-3)); /* @kind color */
  --map-stratum-7: hsl(var(--primary-4)); /* @kind color */
  --map-stratum-8: hsl(var(--primary-5)); /* @kind color */
  --map-stratum-9:#5D6578; /* @kind color */

  /* ---------- geometry ----------
     Unitless so they can be read straight into Leaflet pathOptions. */
  --map-weight:2;                  /* resting stroke */
  --map-weight-selected:4;         /* selected stroke */
  --map-weight-hit:10;             /* invisible hit area on lines */
  --map-dash:6 6;                  /* @kind other */ /* design/planned geometry */
  --map-fill-opacity:.6;           /* resting fill */
  --map-fill-opacity-selected:.7;  /* selected fill */
  --map-marker-radius:10;          /* default marker radius, px */
  --map-point-radius:50;           /* default point radius, metres */
}


/* ===== Species palette ===== */
/* ============================================================
   SpeedGoat — Species palette  (categorical data-viz)

   The estate's FIRST multi-hue categorical scale, and the reason it
   is allowed: every other ramp here is a blue→neutral progression
   because the one-brand-hue rule governs CHROME. This scale encodes
   data, the way --projection does — it is not chrome, and separation
   is the whole job. Nothing else in tokens/ could serve it:
   --vb-* is the vulnerability-band ribbon, --map-stratum-* is a
   single-hue fallback ramp, and --projection is forecast-only.

   Canonicalizes the species colours that lived as literals in the
   telemetry app, so a species colour has one definition across
   deck.gl IconLayer tints, Leaflet markers, and chart series.

   SIX SLOTS, DELIBERATELY. Six well-separated hues stay reliably
   distinguishable at the 24-40px an icon is drawn at; thirteen do
   not, and a scale that promises more separation than the eye can
   resolve is worse than a short one. It is short because it can
   afford to be: the SG animal masks give every species its own
   SILHOUETTE, so colour is not carrying identity alone. A map with
   more than six species in view should stop colouring BY species
   and spend colour on something the shape cannot say — status, sex,
   collar age — while the silhouette keeps saying which animal.

   Reserved, and not available to this scale: red (--danger) is
   mortality, green and amber are status, orange (--projection) is
   forecast. Magenta is NA. The six below avoid all of them.

   Assignment is per project, in slot order, for the species
   actually mapped — NOT a permanent species→hue registry. Only the
   v1 telemetry pair is aliased below; add an alias when a species
   is mapped often enough that consistency across apps matters.
   ============================================================ */
:root{
  /* ---------- the scale ---------- */
  --sp-1:#0E4AAA; /* @kind color */ /* brand blue — the anchor; keeps the brand hue present */
  --sp-2:#0E7C86; /* @kind color */ /* teal */
  --sp-3:#5B43C9; /* @kind color */ /* violet */
  --sp-4:#8A5A2B; /* @kind color */ /* ochre — dark and desaturated, so it reads brown, not amber */
  --sp-5:#6F7A1B; /* @kind color */ /* olive — yellow-green, clear of status green */
  --sp-6:#5D6578; /* @kind color */ /* slate — the quiet slot, for the least charismatic species in view */

  /* ---------- outside the scale ---------- */
  /* Unknown/unrecorded species. Magenta because it must not read as
     a species at all — it is the same NA convention the telemetry
     layers already use, promoted out of app literals. */
  --sp-na:#C4198C; /* @kind color */

  /* Icon STATES, referenced rather than re-picked, so an app cannot
     quietly choose its own mortality red. */
  --sp-mortality: var(--danger);        /* reserved — no species may use it */
  --sp-muted: hsl(var(--text-base-4));  /* collar dropped / no recent fix */
  --sp-halo:#ffffff;                    /* @kind color */ /* the under-layer that makes a tint read on terrain */

  /* ---------- named assignments (v1 telemetry pair) ---------- */
  --sp-muledeer: var(--sp-1);
  --sp-elk: var(--sp-2);
}

/* Dark basemaps and dark theme: the scale is raw hex, so unlike the
   semantic tokens it cannot invert for free. Each slot is lifted in
   lightness and slightly desaturated — same hue order, same slot
   meanings, enough luminance to hold against a dark canvas. */
[data-theme="dark"], .dark{
  --sp-1:#5B93E8; /* @kind color */
  --sp-2:#3FB3BC; /* @kind color */
  --sp-3:#9B87F0; /* @kind color */
  --sp-4:#C89A5E; /* @kind color */
  --sp-5:#A8B347; /* @kind color */
  --sp-6:#9AA3B5; /* @kind color */
  --sp-na:#E86AB8; /* @kind color */
}


/* ===== Dark theme scope ===== */
/* ============================================================
   SpeedGoat — Dark theme scope
   Opt-in: set data-theme="dark" (or class="dark") on <html> or any
   container. Remaps the HSL semantic scales + the few hard-coded
   aliases so every token-driven component follows. The ink chrome
   (header/sidebar) already reads dark and is unchanged.
   ============================================================ */
[data-theme="dark"], .dark{
  /* surfaces & background */
  --background: 208, 80%, 8%;      /* @kind color */
  --foreground: 210, 40%, 96%;     /* @kind color */
  --card: 212, 45%, 12%;           /* @kind color */
  --muted: 214, 30%, 18%;          /* @kind color */
  --surface:#0f1a2e;
  --surface-secondary-1: 214, 30%, 16%;  /* @kind color */
  --surface-secondary-2: 213, 35%, 14%;  /* @kind color */
  --surface-background-1: 208, 62%, 9%;  /* @kind color */
  --surface-background-2: 213, 35%, 12%; /* @kind color */

  /* text ramp */
  --text-base-1: 210, 40%, 96%;    /* @kind color */
  --text-base-2: 214, 25%, 82%;    /* @kind color */
  --text-base-3: 215, 18%, 66%;    /* @kind color */
  --text-base-4: 215, 16%, 52%;    /* @kind color */
  --text-primary-2: 212, 90%, 74%; /* links / primary text on dark @kind color */
  --text-primary-3: 212, 90%, 74%; /* @kind color */

  /* lines / borders */
  --line-primary-3: 213, 45%, 42%; /* @kind color */
  --line-primary-4: 214, 28%, 22%; /* @kind color */
  --line-secondary-2: 215, 18%, 42%; /* @kind color */
  --line-secondary-3: 215, 24%, 22%; /* @kind color */
  --line-secondary-4: 215, 26%, 18%; /* @kind color */
  --border: 215, 24%, 22%;           /* @kind color */ /* count-web --border component, dark-aware */

  /* primary wash / focus tint */
  --primary-5: 214, 50%, 22%;      /* @kind color */

  /* feedback soft fills (tinted for dark) */
  --success-5: 143, 42%, 15%;      /* @kind color */
  --warning-5: 33, 48%, 16%;       /* @kind color */
  --error-5: 8, 48%, 17%;          /* @kind color */

  /* elevation reads via borders on dark; keep a soft ambient shadow */
  --shadow:0 1px 2px rgba(0,0,0,.4), 0 2px 8px rgba(0,0,0,.35);
  --shadow-pop:0 10px 32px rgba(0,0,0,.55);
}
