/* Self-hosted fonts (2026-08-20 CDN sweep). One variable woff2 each, latin
   subset, weight axis 100-900 — no runtime Google Fonts fetch (a LAN-only box
   stalls on it). font-display:swap paints the fallback instantly. OFL-1.1,
   server/static/fonts/OFL.txt + docs/LICENSES.md. Every themed page loads
   tokens.css first, so these cover the whole app from one definition. */
@font-face{font-family:'Geist';font-style:normal;font-weight:100 900;
  font-display:swap;src:url('/static/fonts/GeistSans.woff2') format('woff2')}
@font-face{font-family:'Geist Mono';font-style:normal;font-weight:100 900;
  font-display:swap;src:url('/static/fonts/GeistMono.woff2') format('woff2')}
/* IBM Plex Sans 600 — the Helios VMS WORDMARK ONLY (2026-09-23). IBM's own
   Latin-1 split subset from @ibm/plex-sans 1.1.0, UNMODIFIED: the OFL's
   Reserved Font Name clause ("Plex") forbids shipping a subset we cut
   ourselves under that name. server/static/fonts/OFL-IBMPlexSans.txt.
   font-display:block — a wordmark that flashes a fallback face reads as a
   glitch; on the LAN the 22 KB file lands in milliseconds. */
@font-face{font-family:'IBM Plex Sans';font-style:normal;font-weight:600;
  font-display:block;src:url('/static/fonts/IBMPlexSans-SemiBold.woff2') format('woff2')}

/* ═══════════════════════════════════════════════════════════════════
 *  Helios VMS — Design Tokens
 *  ─────────────────────────────────────────────────────────────────
 *  The single source of truth for colours, spacing, type, motion.
 *  Every Helios page imports this and uses the variables — never
 *  hard-coded colour literals, never duplicated easing curves.
 *
 *  TWO THEMES. Dark values live on :root below; the light theme
 *  overrides them in the :root[data-theme="light"] block at the end of
 *  this file. Components never branch on theme — they consume tokens
 *  that already did. The --cam-* family is the one deliberate
 *  exception: video wells keep it dark in BOTH themes.
 *
 *  THE ACCENT RULE (2026-09-23, Helios VMS rebrand). The orange accent
 *  means ACTION and BRAND only: primary buttons, the focus ring, active
 *  navigation, the logo's core. It NEVER signals state. Why: orange sits
 *  between the two most important state colours (crimson error, amber
 *  warning) — measured 4.5-9 OKLab dE from both before the re-homing —
 *  so any state drawn in the accent is misread as the other two.
 *    selection      -> --select   (a NEUTRAL ring; never the accent)
 *    checked toggle -> --check    (ink in light, light ink in dark)
 *    attention      -> --attn     (amber-yellow, pulsing, inset)
 *    states         -> --ok / --warn / --bad / --info / --unknown
 *  CONTRAST (both themes, gated by tools/check_contrast.py and the
 *  Chromium sweep): every text token >= 4.5:1 on every surface; every
 *  glyph / meaningful-border token >= 3:1.
 *
 *  Source of truth: this file. If you find a value you want to use
 *  that isn't here, add it here first.
 * ═════════════════════════════════════════════════════════════════ */

:root {
  /* Dark UA widgets (scrollbars, form controls, <select> popups) */
  color-scheme: dark;
  /* Native checkbox / radio / range ink — CHECKED IS STATE, not action,
     so it is the neutral ink, never the accent (decision 6). */
  accent-color: var(--check);

  /* ── Surfaces ──────────────────────────────────────────────────
   *  INK-NEUTRAL (the brand ink #141615 is the card surface). No navy:
   *  the logo's navy #1B2233 belongs to the icon field alone.
   *    --bg         page background
   *    --surface    primary card / panel surface
   *    --surface-2  slightly elevated (hover, modal head)
   *    --surface-3  recessed (input bg, recessed buttons)
   */
  --bg:         #0f1110;
  --surface:    #141615;
  --surface-2:  #1b1d1c;
  --surface-3:  #232624;

  /* ── Lines & dividers ──────────────────────────────────────────
   *  --line..--line-3 are DECORATIVE hairlines (no contrast claim).
   *  --line-strong is for a border that CARRIES MEANING — a form
   *  control's boundary — and holds >= 3:1 on every surface. */
  --line:        rgba(246, 245, 242, 0.07);
  --line-2:      rgba(246, 245, 242, 0.115);
  --line-3:      rgba(246, 245, 242, 0.18);
  --line-strong: #75746f;
  /* Timeline coverage band (helios-timeline.js tlTokens): the recessive
     "footage exists" tint, derived from the hairline ramp so it stays quiet
     in BOTH themes — EVENTS carry color. */
  --coverage:      var(--line-3);
  --coverage-edge: var(--line);
  /* Playback dock (DESIGN §5k): the stretches Smart speed plays at 16x,
     shaded over the lanes — a darkening in both themes, deeper in dark. */
  --tl-quiet:      color-mix(in srgb, var(--shade) 38%, transparent);

  /* ── Neutral bases for alpha work ──────────────────────────────
   *  A translucent neutral is written as
   *      color-mix(in srgb, var(--tint) 8%, transparent)
   *  never as an rgba() literal, so it follows the theme:
   *    --tint   page-chrome tint: light ink on dark, ink on light
   *    --shade  shadow / scrim base (black in both themes)
   *    --hl     specular highlight base (white in both themes) */
  --tint:  #f6f5f2;
  --shade: #000000;
  --hl:    #ffffff;

  /* ── Text ──────────────────────────────────────────────────────
   *  --fg..--fg-3 are ALL >= 4.5:1 on every surface (text law).
   *  --fg-4 is placeholder / disabled ONLY — never content text. */
  --fg:    #f6f5f2;   /* brand light ink */
  --fg-1:  #d0d0cb;
  --fg-2:  #abaaa5;
  --fg-3:  #93928d;
  --fg-4:  #5e5e59;

  /* ── Accent: ORANGE — action + brand only (see THE ACCENT RULE) ──
   *  --accent          fills: primary button, active-nav tint base
   *  --accent-mark     glyphs / bars / underlines that carry meaning
   *                    (>= 3:1 on every surface)
   *  --accent-text     orange TEXT (>= 4.5:1, also on --accent-soft)
   *  --accent-ink      the label ON an accent fill */
  --accent:        #f59a5e;   /* brand accent-on-dark */
  --accent-hover:  #f08a4a;   /* deeper, not lighter: a lighter hover
                                 walks into the amber warn band */
  --accent-strong: #f08a4a;
  --accent-mark:   #f59a5e;
  --accent-text:   #f59a5e;
  --accent-ink:    #141615;   /* 8.4:1 on --accent, 7.3:1 on hover */
  --accent-soft:   rgba(245, 154, 94, 0.13);
  --accent-line:   rgba(245, 154, 94, 0.45);
  --accent-glow:   rgba(245, 154, 94, 0.28);

  /* ── Focus, selection, checked, links ──────────────────────────
   *  Focus is ACTION (the accent's job). Selection is NEUTRAL — a ring,
   *  never orange (neutral means selection; attention is never neutral).
   *  Checked toggles are STATE: solid ink. Links: neutral text, orange
   *  underline. */
  --focus:          #f59a5e;
  --focus-soft:     rgba(245, 154, 94, 0.22);
  --focus-w:        2px;
  --focus-offset:   2px;
  --select:         #d0d0cb;
  --select-soft:    rgba(246, 245, 242, 0.08);
  --check:          #f6f5f2;
  --check-ink:      #141615;
  --link:           var(--fg-1);
  --link-underline: var(--accent-mark);

  /* ── Helios Enhanced ───────────────────────────────────────────
   *  The certification mark for deep-integrated (dev-api) devices:
   *  a neutral chip; its spark glyph carries the accent-mark. */
  --enhanced:      var(--fg-2);
  --enhanced-soft: var(--surface-3);
  --enhanced-line: var(--line-2);

  /* ── The logo (partials/helios_lockup.html + /static/css/brand.css) ──
   *  The rings take --lockup-ink, "Helios" --lockup-ink, "VMS" the SOLID
   *  --lockup-muted (never an opacity: the brand file's 55% "VMS" is 3.6:1
   *  on the page), the core --lockup-dot — the brand orange in BOTH themes.
   *  The -dark / -light pins serve the macro's forced theme variants and
   *  the icon renderer (tools/render_brand_assets.py reads THIS file).
   *  --lockup-field is the app-icon / favicon ground: the logo navy, which
   *  belongs to the icon alone (the app's dark theme is ink, not navy). */
  --lockup-ink-dark:   #f6f5f2;   /* brand light ink */
  --lockup-ink-light:  #1b2233;   /* brand logo navy */
  --lockup-muted-dark:  #8b919c;  /* >= 4.8:1 on every dark surface */
  --lockup-muted-light: #5d6576;  /* >= 4.7:1 on every light surface */
  --lockup-dot:        #e8641e;
  --lockup-field:      #1b2233;
  --lockup-ink:        var(--lockup-ink-dark);
  --lockup-muted:      var(--lockup-muted-dark);

  /* ── Sun (the topbar daylight arc) ─────────────────────────────
   *  A BRAND use of the accent (the logo's core is the same orange);
   *  night dims it to neutral. Replaces the retired --brand-warm. */
  --sun:       var(--accent-mark);
  --sun-low:   var(--accent-hover);
  --sun-night: var(--fg-3);

  /* ── State colours ─────────────────────────────────────────────
   *  Status meaning, never decoration, and never colour alone (glyph,
   *  label or fixed position always travels with it):
   *    --ok       healthy, online, success, confirmed
   *    --warn     caution, degraded, needs review — AMBER-YELLOW, moved
   *               off orange so it cannot read as the accent
   *    --bad      error, offline, down, recording, alarm — CRIMSON, moved
   *               off pure red for the same reason
   *    --info     informational, in progress, new
   *    --unknown  nobody is vouching (renders neutral, never green)
   *  -soft (chip ground) / -line (chip border) / -text (>= 4.5:1, also on
   *  its own -soft chip) / -ink (the label on a solid fill). */
  --ok:         #36a46c;
  --ok-soft:    rgba(54, 164, 108, 0.14);
  --ok-line:    rgba(54, 164, 108, 0.30);
  --ok-strong:  #1f7a4c;
  --ok-text:    #52bd84;
  --ok-ink:     #0f1110;

  --warn:        #f5d547;
  --warn-ink:    #141615;
  --warn-soft:   rgba(245, 213, 71, 0.14);
  --warn-line:   rgba(245, 213, 71, 0.32);
  --warn-strong: #d9b21f;
  --warn-text:   #f7e08a;

  --bad:        #e8364f;
  --bad-ink:    #ffffff;   /* a text-bearing danger fill uses the STRONG
                              crimson (5.9:1 with this ink); on the base
                              crimson the same ink is only 4.1 */
  --bad-soft:   rgba(232, 54, 79, 0.16);
  --bad-line:   rgba(232, 54, 79, 0.34);
  --bad-strong: #c21f3a;
  --bad-text:   #ff7a8c;
  --rec:        var(--bad);     /* alias — recording uses the error crimson */
  --rec-glow:   rgba(232, 54, 79, 0.40);

  --info:       #6ca6f0;
  --info-soft:  rgba(108, 166, 240, 0.14);
  --info-line:  rgba(108, 166, 240, 0.32);
  --info-text:  #8dbbf5;
  --info-ink:   #141615;

  --unknown:    var(--fg-3);

  /* ── Attention ─────────────────────────────────────────────────
   *  "Look here": amber-yellow + pulse + INSET ring (decision 5). The
   *  warn family on purpose — attention is a caution, and neutral is
   *  already selection. The notification dot is attention too. */
  --attn:      var(--warn);
  --attn-soft: var(--warn-soft);
  --attn-line: var(--warn-line);
  --attn-glow: rgba(245, 213, 71, 0.35);

  /* ── Drawn regions (motion zones, watched regions) ─────────────
   *  Geometry the operator configured — informational blue, never the
   *  accent (it is not an action) and never neutral (that is selection).
   *  Tripwire LINES are caution: they use --evt-line (= --warn). */
  --zone:      var(--info);
  --zone-soft: var(--info-soft);

  /* ── Camera tile ───────────────────────────────────────────────
   *  Camera feed tiles are video — they keep their own near-black
   *  backdrop, one step darker than --surface. Ink-neutral, like the
   *  page: the old navy cast is gone. */
  --cam-bg:    #121413;
  --cam-grad1: #171918;
  --cam-grad2: #0e100f;
  --cam-text:  #4e504f;
  /* Tile-scoped chrome (2026-08-08): overlays, scrims, pills, toolbars
     and status dots that sit ON video. Defined ONCE — the light theme
     never overrides these, which is what keeps every tile state a dark
     well in both themes (the video-well law incl. reconnecting/offline). */
  --cam-scrim:   rgba(10, 11, 10, 0.60);   /* hover / ident-bar scrim */
  --cam-scrim-2: rgba(10, 11, 10, 0.86);   /* text-bearing pill/panel (§4 floor) */
  --cam-fg:      #ecece8;                  /* text on video wells */
  --cam-fg-2:    #aeaea9;
  --cam-line:    rgba(255, 255, 255, 0.14);
  --cam-tint:    #ffffff;                  /* alpha base ON video: stays
                                              white in both themes */

  /* ── the Inspect silhouette palette ─────────────────────────────
     One colour per person in a paused frame, assigned in the order the
     segmenter returns people (person 1 is always --sil-1). Tile chrome:
     never overridden by the light theme.
     NEVER GREEN (green means "confirmed"), NEVER ORANGE (the accent),
     NEVER AMBER-YELLOW (warn/attention), NEVER CRIMSON (error): a person
     drawn in any of those reads as a verdict about that person. 2026-09-23
     replaced the three that broke that (amber #f5a524 was ALWAYS person 1;
     orange #fb923c sat 2.9 dE from the accent; yellow #fde047 now reads as
     warn): min pairwise distance 5.1 -> 10.2 dE. */
  --sil-1: #f9a8d4;
  --sil-2: #22d3ee;
  --sil-3: #a78bfa;
  --sil-4: #fb7185;
  --sil-5: #60a5fa;
  --sil-6: #bae6fd;
  --sil-7: #e879f9;
  --sil-8: #94a3b8;
  /* Tile status + interaction — theme-invariant mirrors of the page
     tokens: the house tokens flip to their light-side values, which
     disappear on an always-dark video well. */
  --cam-ok:          #36a46c;
  --cam-warn:        #f5d547;
  --cam-bad:         #e8364f;
  --cam-info:        #6ca6f0;
  --cam-accent:      #f59a5e;                  /* ACTIONS on a tile */
  --cam-accent-soft: rgba(245, 154, 94, 0.16);
  --cam-bad-soft:    rgba(232, 54, 79, 0.20);
  --cam-select:      #f6f5f2;                  /* neutral selection ring */
  --cam-select-soft: rgba(246, 245, 242, 0.16);
  --cam-check:       #f6f5f2;                  /* a checked toggle ON video:  */
  --cam-check-ink:   #0f1110;                  /* light-ink fill, dark glyph  */
  --cam-attn:        #f5d547;                  /* attention ring on video */
  --cam-attn-soft:   rgba(245, 213, 71, 0.18);
  --cam-zone:        #6ca6f0;                  /* drawn regions on frames */
  --cam-zone-soft:   rgba(108, 166, 240, 0.16);
  --cam-deter:       #29c1e0;                  /* talk-down / deterrence */
  --cam-glass:       rgba(15, 17, 16, 0.80);   /* the bar (chips >= .75 floor) */
  --cam-glass-2:     rgba(15, 17, 16, 0.92);   /* the ⋯ overflow / popovers */
  /* Detection-box colours (2026-08-17). Class colour for a box drawn OVER
     footage, and deliberately NOT --evt-*: every --evt-* is redefined in the
     light block, but the video well stays dark in BOTH themes. These are the
     UniFi Protect register — bright, saturated, an instrument reading rather
     than chrome — and none of them is warm, so no box can read as the accent.

     THE HALO IS NOT DECORATION. Measured against pure white (concrete, blown
     highlights, a headlight) every family fails on its own (1.5-2.4:1), so
     --cam-evt-casing is a near-opaque DARK stroke laid under the colour —
     the same construction the map's coverage cones use. Neither stroke is
     optional; the pair is the guarantee. --cam-evt-ink is the text on a
     FILLED label tab, a real 4.5:1 text claim pinned by
     tools/check_contrast.py. */
  /* SCRUB SPAN (2026-09-16). The played portion of a progress bar is
     neutral (--cam-fg-2); the EVENT'S OWN SPAN — the part worth looking at —
     takes this blue. A selected RANGE on a scrubber reads correctly as a
     marquee; that is why it has its own name rather than a detection
     colour. */
  --cam-span:        #5b9df7;

  --cam-evt-person:  #2fbf71;   /* = the timeline lanes' --evt-person (the owner, 2026-10-04: boxes in the lanes' colours) */
  --cam-evt-vehicle: #5b9df7;   /* = the lanes' --evt-vehicle */
  --cam-evt-animal:  #c98bff;
  --cam-evt-object:  #c98bff;   /* fallback family; the overlay draws none */
  --cam-evt-ink:     #0f1110;   /* dark text ON a filled label tab */
  --cam-evt-casing:  rgba(15, 17, 16, 0.90);  /* dark halo UNDER the colour */

  /* ── Glass (§5h) ───────────────────────────────────────────────
   *  Overlay glass gets real blur (dialogs, drawers, popovers, sheets,
   *  flyouts, sticky chrome). Resting cards use --glass-card with NO blur.
   *  --glass-floor is the THIRD grade: house glass for a panel that
   *  carries BODY TEXT directly over PHOTOGRAPHIC ground (the §4 floor
   *  rule asks >= .90 alpha + blur there). */
  --glass:       rgba(20, 22, 21, 0.78);
  --glass-2:     rgba(20, 22, 21, 0.88);
  --glass-floor: rgba(20, 22, 21, 0.94);
  --glass-card:  rgba(27, 29, 28, 0.72);
  --glass-line:  rgba(246, 245, 242, 0.09);
  --glass-blur:  saturate(140%) blur(14px);

  /* Backdrop dim behind modals/palettes/drawers — themed: a .60 black
     backdrop on a white page reads as a blackout, not a modal.
     --dim is the scrim's COLOUR in every state; the state is opacity or
     display. A backdrop never rests at an alpha-0 colour (a token cannot
     say "off", and a tokenization pass reads an alpha-0 literal as the
     colour it approximates — the 2026-09-23 /cameras-v2 wash). Gate:
     tests/test_resting_overlays_playwright.py. */
  --dim:        rgba(0, 0, 0, 0.60);

  /* (--rail-glass retired 2026-10-16: the rail is an opaque --surface panel
     now — it floats over /live's video wall, and a menu over the wall is
     solid, docs/DESIGN.md §5h.) */

  /* ── Shadows ───────────────────────────────────────────────────
   *  -sm hairline lift, default cards/buttons, -lg floating elements. */
  --shadow-sm:
    0 1px 0 0 rgba(0, 0, 0, 0.4),
    0 1px 2px 0 rgba(0, 0, 0, 0.4);

  --shadow:
    0 1px 0 0 rgba(0, 0, 0, 0.5),
    0 2px 4px -1px rgba(0, 0, 0, 0.4),
    0 8px 16px -4px rgba(0, 0, 0, 0.5);

  --shadow-lg:
    0 1px 0 0 rgba(0, 0, 0, 0.5),
    0 4px 8px -2px rgba(0, 0, 0, 0.5),
    0 16px 32px -8px rgba(0, 0, 0, 0.6);

  /* ── Motion ────────────────────────────────────────────────────
   *    --ease  default; --ease-spring bouncy arrival;
   *    --ease-out-expo fast start, gentle stop; --ease-out-quint gentler;
   *    --ease-decelerate a panel sliding in or out over a busy page: --ease covers 75% of the way in its first
   *    80 ms, so one late frame shows the panel already there; this one spreads the travel across the frames */
  --ease:           cubic-bezier(.22, 1, .36, 1);
  --ease-spring:    cubic-bezier(.34, 1.56, .64, 1);
  --ease-out-expo:  cubic-bezier(.16, 1, .3, 1);
  --ease-out-quint: cubic-bezier(.22, 1, .36, 1);
  --ease-decelerate: cubic-bezier(0, 0, .2, 1);

  /* Durations */
  --t-fast:    0.12s;   /* hover state changes */
  --t-base:    0.20s;   /* normal transitions */
  --t-slow:    0.40s;   /* slide-ins, page transitions */
  --t-arrive:  0.55s;   /* layout shifts, grid rearrangements */

  /* ── Radii ─────────────────────────────────────────────────────*/
  --r-xs:   4px;
  --r-sm:   6px;
  --r:      8px;
  --r-lg:   12px;
  --r-pill: 100px;
  --r-tile: 10px;   /* camera feed tiles */

  /* ── Layout ────────────────────────────────────────────────────*/
  --sidebar-w:       224px;
  --sidebar-w-full:  224px;   /* expanded sidebar */
  --topbar-h:        52px;
  --inspector-w:     320px;   /* right-side event panel */
  --page-header-h:   72px;

  /* ── Type ──────────────────────────────────────────────────────
   *  Geist (sans) + Geist Mono (numerics) for ALL UI type. IBM Plex Sans
   *  600 is the wordmark's face and nothing else's. */
  --font-sans:  'Geist', -apple-system, BlinkMacSystemFont, system-ui, sans-serif;
  --font-mono:  'Geist Mono', ui-monospace, SFMono-Regular, Menlo, monospace;
  --font-brand: 'IBM Plex Sans', 'Helvetica Neue', Helvetica, Arial, sans-serif;

  --text-xs:  10.5px;   /* captions, source badges */
  --text-sm:  11.5px;   /* metadata, status pills */
  --text-md:  12.5px;   /* secondary copy, button labels */
  --text-base: 13px;    /* body */
  --text-lg:  14px;     /* card titles */
  --text-xl:  18px;     /* page titles */
  --text-2xl: 28px;     /* hero numbers */
  --text-3xl: 32px;     /* large metrics */
}

/* ═══════════════════════════════════════════════════════════════════
 *  Legacy compatibility aliases
 *  ─────────────────────────────────────────────────────────────────
 *  Older variable names still referenced by pages, aliased to the
 *  canonical tokens. Once no page reads a name it is deleted here.
 * ═════════════════════════════════════════════════════════════════ */
:root {
  /* Legacy NON-colour aliases, still read by pages. The colour aliases
     (--card, --text2…, --blue / --green / --red / --amber / --purple /
     --cyan, --brand…, --mint…) were retired 2026-09-23: a colour is read
     by its meaning — the state, event, accent or neutral token. */
  --mono:        var(--font-mono);
  --ease-out:    var(--ease-out-quint);
  --radius-tile: var(--r-tile);
}

/* ── One-site feel: cross-document view transitions ─────────────
 * Chrome crossfades between page navigations instead of a hard
 * flash. Shell elements named below are held visually static
 * across navigation. */
@view-transition { navigation: auto; }
.rail    { view-transition-name: helios-rail; }
.topbar  { view-transition-name: helios-topbar; }
.sidebar { view-transition-name: helios-sidebar; }

/* ── Event-type colours — ONE colour per type, site-wide ─────────────
 * Consumed by playback, multicam playback, live badges. Categorical data
 * colours: deliberately NOT the accent, and (since 2026-09-23) nothing in
 * the orange arc at all. The warm arc now holds exactly two meanings:
 * crimson error and amber-yellow caution, with the accent between them
 * reserved for action. Moved out of it:
 *   tripwire / loitering  -> the warn family (a line crossed IS a caution)
 *   audio                 -> lavender (was the warn orange, same hex)
 *   POS exception         -> olive (gold sat on the new warn)
 * -soft variants derive from the base, so a hue change is one edit. */
:root {
  --evt-person:      #2fbf71;
  --evt-vehicle:     #5b9df7;
  --evt-motion:      #8a93a0;
  --evt-alarm:       #e8364f;   /* intrusion / tamper — the error crimson */
  --evt-line:        var(--warn);
  --evt-object:      #a855f7;
  --evt-audio:       #b3a9fa;
  /* POS: an exception is "a register did something worth a look";
     ordinary sales render as neutral ticks, never this colour. */
  --evt-pos:         #b8d84a;
  /* Deterrence: the one moment Helios ACTS instead of watching, so it
     cannot borrow a detection hue. Cyan is the free arc. ONE hue for the
     family; the OUTCOME is carried by silhouette (filled = fired, hollow =
     stayed silent). Only the failure case borrows --warn. */
  --evt-deterrence:  #29c1e0;
  /* Access control: magenta is the only arc not already carrying meaning.
     ONE hue for access, with denial as a brighter TREATMENT of the same hue
     — never --evt-alarm: scanning the error colour must return intrusions
     only, or "when was someone refused" stops being answerable. The three
     states rank by SHAPE (base / taller / full height + a head); hue
     carries the family. A comment must record the reason, not the value. */
  --evt-access:       #d957c4;
  --evt-access-deny:  #ff5ea8;
  --evt-access-alarm: #ff8fd6;
  /* The label ON an event fill (timeline chips, markers): it knocks out to
     the page ground — dark on the bright dark-theme fills, near-white on
     the deep light-theme ones. Gated (>= 4.5:1) on the fills that carry
     text: person, vehicle, motion, alarm, access. */
  --evt-ink:         #0f1110;

  --evt-person-soft:       color-mix(in srgb, var(--evt-person) 14%, transparent);
  --evt-vehicle-soft:      color-mix(in srgb, var(--evt-vehicle) 14%, transparent);
  --evt-motion-soft:       color-mix(in srgb, var(--evt-motion) 14%, transparent);
  --evt-alarm-soft:        color-mix(in srgb, var(--evt-alarm) 14%, transparent);
  --evt-line-soft:         color-mix(in srgb, var(--evt-line) 14%, transparent);
  --evt-object-soft:       color-mix(in srgb, var(--evt-object) 14%, transparent);
  --evt-audio-soft:        color-mix(in srgb, var(--evt-audio) 14%, transparent);
  --evt-pos-soft:          color-mix(in srgb, var(--evt-pos) 14%, transparent);
  --evt-deterrence-soft:   color-mix(in srgb, var(--evt-deterrence) 14%, transparent);
  --evt-access-soft:       color-mix(in srgb, var(--evt-access) 14%, transparent);
  --evt-access-deny-soft:  color-mix(in srgb, var(--evt-access-deny) 16%, transparent);
  --evt-access-alarm-soft: color-mix(in srgb, var(--evt-access-alarm) 18%, transparent);
}

/* ═══════════════════════════════════════════════════════════════════
 *  LIGHT THEME — its own designed theme, not an inversion.
 *  Stamped as data-theme="light" on <html> by partials/_head.html.
 *  The brand's paper: page #FAFAF8, white cards, surface-2 #F0F0EB,
 *  hairlines ≈ #E1E1DB; shadows do the elevation work that glow/glass
 *  does in dark. Video stays in a dark well (--cam-* is NOT overridden).
 *  Every text/surface pair here is gated by tools/check_contrast.py.
 * ═════════════════════════════════════════════════════════════════ */
:root[data-theme="light"] {
  color-scheme: light;

  --bg:         #fafaf8;   /* brand page */
  --surface:    #ffffff;
  --surface-2:  #f0f0eb;   /* brand surface-2 */
  --surface-3:  #e8e8e2;

  --line:        rgba(20, 22, 21, 0.11);   /* ≈ brand border #E1E1DB on the page */
  --line-2:      rgba(20, 22, 21, 0.17);
  --line-3:      rgba(20, 22, 21, 0.28);
  --line-strong: #7c7f7c;
  --tl-quiet:    color-mix(in srgb, var(--shade) 5%, transparent);

  --tint:  #141615;

  --lockup-ink:    var(--lockup-ink-light);
  --lockup-muted:  var(--lockup-muted-light);

  /* Brand ink #141615 and body #474B49. The brand's muted #6A6F6C is
     4.48:1 on #F0F0EB — one step under the text law — so the muted steps
     are darkened just enough to clear 4.5 on every surface. */
  --fg:    #141615;
  --fg-1:  #474b49;
  --fg-2:  #545855;
  --fg-3:  #626663;
  --fg-4:  #a9aca9;

  /* The brand pair as used on the web (white on #E8641E) is 3.35:1 and
     fails AA; the label is INK (5.4:1) and the hover is #D65A1B, the
     darkest orange that keeps the ink label above 4.5 (4.6:1). #E8641E is
     2.7-2.9:1 on the raised surfaces, so meaningful glyphs, bars and
     underlines use --accent-mark (#C4501A, >= 3.8:1 everywhere). */
  --accent:        #e8641e;   /* brand accent */
  --accent-hover:  #d65a1b;
  --accent-strong: #c4501a;   /* brand hover — pressed state */
  --accent-mark:   #c4501a;
  --accent-text:   #a54105;
  --accent-ink:    #141615;
  --accent-soft:   rgba(232, 100, 30, 0.10);
  --accent-line:   rgba(196, 80, 26, 0.55);
  --accent-glow:   rgba(232, 100, 30, 0.22);

  --focus:          #c4501a;   /* >= 3.8:1 on every light surface */
  --focus-soft:     rgba(196, 80, 26, 0.20);
  --select:         #474b49;
  --select-soft:    rgba(20, 22, 21, 0.06);
  --check:          #141615;
  --check-ink:      #ffffff;

  --ok:        #177a4b;
  --ok-soft:   rgba(23, 122, 75, 0.10);
  --ok-line:   rgba(23, 122, 75, 0.28);
  --ok-strong: #0f5c37;
  --ok-text:   #0a6a3e;
  --ok-ink:    #ffffff;

  --warn:        #a27e00;
  --warn-ink:    #141615;
  --warn-soft:   rgba(162, 126, 0, 0.12);
  --warn-line:   rgba(162, 126, 0, 0.35);
  --warn-strong: #755b0b;
  --warn-text:   #755b0b;

  --bad:        #a8123c;
  --bad-ink:    #ffffff;
  --bad-soft:   rgba(168, 18, 60, 0.09);
  --bad-line:   rgba(168, 18, 60, 0.30);
  --bad-strong: #8a0e31;
  --bad-text:   #a8123c;
  --rec-glow:   rgba(168, 18, 60, 0.25);

  --info:       #2c67af;
  --info-soft:  rgba(44, 103, 175, 0.10);
  --info-line:  rgba(44, 103, 175, 0.30);
  --info-text:  #255c9e;
  --info-ink:   #ffffff;

  --attn-glow:  rgba(162, 126, 0, 0.30);

  /* elevation: shadows replace glow/glass (frosted plastic reads wrong
     on white — light glass is near-opaque white + an INK shadow) */
  --shadow-sm:
    0 1px 2px rgba(20, 22, 21, 0.06),
    0 1px 1px rgba(20, 22, 21, 0.04);
  --shadow:
    0 1px 3px rgba(20, 22, 21, 0.08),
    0 4px 12px -2px rgba(20, 22, 21, 0.08);
  --shadow-lg:
    0 4px 8px -2px rgba(20, 22, 21, 0.10),
    0 16px 32px -8px rgba(20, 22, 21, 0.16);

  --glass:       rgba(255, 255, 255, 0.88);
  --glass-2:     rgba(255, 255, 255, 0.94);
  --glass-floor: rgba(255, 255, 255, 0.96);
  --glass-card:  rgba(255, 255, 255, 0.78);
  --glass-line:  rgba(20, 22, 21, 0.10);

  --dim:        rgba(20, 22, 21, 0.42);

  /* Categorical event ramp for light — same hue per type, weights picked
     so every marker holds >= 3:1 on EVERY light surface (gated). */
  --evt-person:      #177a47;
  --evt-vehicle:     #2f66c4;
  --evt-motion:      #667079;
  --evt-alarm:       #a8123c;
  --evt-object:      #7434d4;
  --evt-audio:       #7c66e8;
  --evt-pos:         #667a00;
  --evt-deterrence:  #0e7d94;
  --evt-access:       #a63393;
  --evt-access-deny:  #c31a70;
  --evt-access-alarm: #d6438f;
  --evt-ink:          #fafaf8;
}
