/* DGR UI kit 1.3.1 – the look and feel of the DGR software suite (Dinokeng ANPR, NetWatch, Operations Planner, ...).
   Master copy: OneDrive ...\10 - Claude\Programs\Master\ui. Do not edit a program's copy: change the Master and raise the version.
   1 Tokens · 2 Base · 3 Top bar and tabs · 4 Layout, tiles, cards · 5 Buttons and fields · 6 Pills · 7 Tables
   8 Dialogs, side panel, toasts, alarm banner · 9 Charts · 10 Sign-in, Ctrl+K · 11 Motion · 12 Print and wall display
   13 Program tools (1.1.0): maintenance status, display settings, top-bar extras, table tools, settings pages, logs, manual,
      version history, job progress, update banner, side panel sizing, import preview, diagrams and drag and drop, wall cards, footer
   14 Live toggles (1.1.1) · 15 Clickable links and requirements in diagrams (1.2.1)
   16 Taken from Dinokeng ANPR, the reference program (1.3.0): switches, help buttons, progress, undo countdown, links,
      phone tables, phone pages, Hub sign-in button, dialog button order, ANPR's timings */

/* ==========================================================================================================
   1. Tokens – the Dinokeng ANPR values (the reference program); light, dark by the system, dark by choice
   ========================================================================================================== */
/* Dinokeng Game Reserve design system: Primary Green #253012, Light Green #cacaa6, Legacy Green #35491a,
   Earth Brown #6d3b18, Cream #ebece0, White, Black. Oswald (headings, uppercase) + Montserrat (text). */
:root {
  color-scheme: light;
  --bg: #ffffff;              /* surface */
  --surface-1: #ffffff;
  --surface-2: #ebece0;       /* surface-soft (cream) */
  --border: #cacaa6;          /* line */
  --line-strong: #35491a;
  --text-primary: #000000;    /* ink */
  --text-secondary: #35491a;  /* ink-muted */
  --text-muted: #35491a;
  --grid: #dcdec9;
  --series-1: #253012;        /* primary green */
  --series-2: #6d3b18;        /* earth brown */
  --chart-2: #cc7a33;         /* lighter brown for charts next to the dark green */
  --series-3: #8c8f5e;        /* deeper light-green tint */
  --reserve-fill: #ebece0;
  --reserve-line: #35491a;
  --good: #35491a;
  --warning: #6d3b18;
  --critical: #c0000c;
  --st-on-bg: #dcefd6; --st-on-fg: #1e5a17; --st-off-bg: #c0000c; --st-off-fg: #ffffff;
  --st-wait-bg: #fbe7c6; --st-wait-fg: #7a4500;
  --accent: #253012;
  --on-accent: #ffffff;
  --band: #cacaa6; --on-band: #253012;
  --bar: #253012; --on-bar: #ffffff; --bar-muted: #cacaa6;
  --focus-ring: #6d3b18;
  /* property map: even lightness steps (L* 95/86/74/57/39) in the brand's greens; "none" is neutral */
  --q0: #f2f2ec; --q1: #cfd99f; --q2: #a9bf6c; --q3: #76943d; --q4: #46641f;
  --map-bg: #ffffff; --map-line: #b9bba9; --map-edge: #253012; --map-halo: #ffffff;
  --road-main: #6d3b18; --road-minor: #9b7b5e; --map-dot: #6d3b18; --map-dot-ring: #ffffff; --map-cam: #ffffff; --map-cam-line: #000000; --map-hover: #6d3b18;
  --display: "Oswald", "Arial Narrow", sans-serif;
  --sans: "Montserrat", "Segoe UI", Arial, sans-serif;
  --r: 8px;                   /* corners of cards, tiles, buttons, fields, menus; pills are round */
  --ctl-h: 34px;              /* height of every field, list and button in a control row */
}
/* dark (1.2.0, "Night"): warm near-black charcoal; cards a step lighter; the brand green only on the top bar, the hero tile and accents.
   Text 14:1, secondary 9:1, muted 6:1 on cards; warning amber and alarm red far apart. */
@media (prefers-color-scheme: dark) {
  :root:not([data-theme="light"]) {
    color-scheme: dark;
    --bg: #101210; --surface-1: #1a1d18; --surface-2: #252923; --border: #353b31; --line-strong: #6b7560;
    --text-primary: #f1f0e8; --text-secondary: #c9cbb6; --text-muted: #9da18e; --grid: #2a2f26;
    --series-1: #cacaa6; --series-2: #d6a47a; --series-3: #a8c47a; --chart-2: #e8955a;
    --reserve-fill: #252923; --reserve-line: #6b7560;
    --good: #9fd88a; --warning: #f2b450; --critical: #ff6b5e;
    --st-on-bg: #1d3a18; --st-on-fg: #a9e59b; --st-off-bg: #c9302a; --st-off-fg: #ffffff;
    --st-wait-bg: #43340f; --st-wait-fg: #ffd27a; --st-maint-bg: #173450; --st-maint-fg: #a9d4f5;
    --accent: #cacaa6; --on-accent: #1b2410; --band: #2b3324; --on-band: #eceedd;
    --bar: #253012; --on-bar: #ffffff; --bar-muted: #cacaa6; --focus-ring: #f2b450;
    --map-bg: #141712; --map-line: #101210; --map-edge: #ebece0; --map-halo: #141712;
    --q0: #20241c; --q1: #3e5524; --q2: #5f8236; --q3: #93b45c; --q4: #d3e3a0;
    --road-main: #e09a62; --road-minor: #b9977a; --map-dot: #e09a62; --map-dot-ring: #141712; --map-cam: #1a1d18; --map-cam-line: #ffffff; --map-hover: #f2b450;
  }
}
:root[data-theme="dark"] {
  color-scheme: dark;
    --bg: #101210; --surface-1: #1a1d18; --surface-2: #252923; --border: #353b31; --line-strong: #6b7560;
    --text-primary: #f1f0e8; --text-secondary: #c9cbb6; --text-muted: #9da18e; --grid: #2a2f26;
    --series-1: #cacaa6; --series-2: #d6a47a; --series-3: #a8c47a; --chart-2: #e8955a;
    --reserve-fill: #252923; --reserve-line: #6b7560;
    --good: #9fd88a; --warning: #f2b450; --critical: #ff6b5e;
    --st-on-bg: #1d3a18; --st-on-fg: #a9e59b; --st-off-bg: #c9302a; --st-off-fg: #ffffff;
    --st-wait-bg: #43340f; --st-wait-fg: #ffd27a; --st-maint-bg: #173450; --st-maint-fg: #a9d4f5;
    --accent: #cacaa6; --on-accent: #1b2410; --band: #2b3324; --on-band: #eceedd;
    --bar: #253012; --on-bar: #ffffff; --bar-muted: #cacaa6; --focus-ring: #f2b450;
    --map-bg: #141712; --map-line: #101210; --map-edge: #ebece0; --map-halo: #141712;
    --q0: #20241c; --q1: #3e5524; --q2: #5f8236; --q3: #93b45c; --q4: #d3e3a0;
    --road-main: #e09a62; --road-minor: #b9977a; --map-dot: #e09a62; --map-dot-ring: #141712; --map-cam: #1a1d18; --map-cam-line: #ffffff; --map-hover: #f2b450;
}
* { box-sizing: border-box; }
[hidden] { display: none !important; }

/* ==========================================================================================================
   2. Base
   ========================================================================================================== */
:root { --ease: cubic-bezier(.2, .7, .2, 1); --ease-out: cubic-bezier(.2, .8, .2, 1); --ease-in: cubic-bezier(.4, 0, 1, 1);
  --shimmer: linear-gradient(90deg, var(--surface-2) 25%, color-mix(in srgb, var(--surface-2) 35%, var(--surface-1)) 50%, var(--surface-2) 75%);
  interpolate-size: allow-keywords; }
* { box-sizing: border-box; }
[hidden] { display: none !important; }
html { -webkit-text-size-adjust: 100%; scroll-behavior: smooth; }
body { margin: 0; background: var(--bg); color: var(--text-primary); font: 14px/1.5 var(--sans); }
:focus-visible { outline: 2px solid var(--focus-ring); outline-offset: 2px; }
h1, h2, h3 { font-family: var(--display); text-transform: uppercase; letter-spacing: .02em; }
a { color: inherit; }
:where(main, dialog, .tip) a:where(:not(.btn)) { text-decoration: underline; text-decoration-color: var(--line-strong); text-underline-offset: 3px; }
.muted { color: var(--text-muted); }
.spacer { flex: 1; }
.num, td.num, th.num { font-variant-numeric: tabular-nums; }
.plate, td.plate, span.plate { font-weight: 700; letter-spacing: .04em; font-family: ui-monospace, Consolas, monospace; }
.sticky { position: sticky; top: 0; z-index: 5; }

/* ==========================================================================================================
   3. Top bar (header), main tabs, sub-tabs, period buttons
   ========================================================================================================== */
header { display: flex; flex-wrap: wrap; align-items: center; gap: 12px 24px; padding: 12px 24px; background: var(--bar); color: var(--on-bar); }
header .logo { height: 44px; width: auto; display: block; margin-right: 8px; }
header h1 { font: 700 22px/26px var(--display); text-transform: uppercase; margin: 0; letter-spacing: .02em; }
header .sub { color: var(--bar-muted); font-size: 12.5px; }
header .btn { border-color: var(--bar-muted); background: transparent; color: var(--on-bar); }
header .btn.icon { padding: 6px 8px; line-height: 0; }
header .btn.solid { background: var(--on-bar); color: var(--bar); border-color: var(--on-bar); }
header .user { color: var(--bar-muted); font-size: 12.5px; }
header .search { display: flex; gap: 6px; }
header .search input { width: 160px; padding: 7px 10px; border: 1px solid var(--bar-muted); border-radius: var(--r); background: transparent; color: var(--on-bar); font: 600 13px var(--sans); text-transform: uppercase; }
header .search input::placeholder { color: var(--bar-muted); text-transform: none; font-weight: 400; }
header .statusgrp { display: flex; align-items: center; gap: 12px; padding: 0 12px; border-left: 1px solid rgba(255,255,255,.18); border-right: 1px solid rgba(255,255,255,.18); }
header .btn.live-btn { display: inline-flex; align-items: center; gap: 7px; }
header .btn.live-btn::before { content: ""; flex: 0 0 9px; width: 9px; height: 9px; border-radius: 50%; background: #9aa0a6; }
header .btn.live-btn[aria-pressed="true"]::before { background: #2fbf5b; box-shadow: 0 0 0 3px rgba(47,191,91,.25); animation: dgr-livepulse 2s infinite; }
header .btn.live-btn[aria-pressed="false"] { opacity: .8; }
@keyframes dgr-livepulse { 50% { box-shadow: 0 0 0 5px rgba(47,191,91,.05); } }
header .menu-btn { display: none; }
/* phones: one row (logo, title, status, ☰); ☰ opens the rest */
@media (max-width: 640px) {
  header { padding: 8px 12px; gap: 8px; }
  header .logo { height: 28px; }
  header > .title { flex: 1; min-width: 0; }
  header h1 { font-size: 15px; white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }
  header .sub { font-size: 11px; white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }
  header .statusgrp { border: 0; padding: 0; margin-left: auto; order: 2; }
  header .menu-btn { display: inline-flex; order: 3; }
  header .in-menu { display: none !important; }
  header.open .in-menu { display: inline-flex !important; order: 11; }
  header.open .search.in-menu { display: flex !important; flex-basis: 100%; order: 10; }
  header.open .search input { flex: 1; }
  header .spacer { display: none; }
}
nav.bar { display: flex; flex-wrap: wrap; align-items: center; gap: 8px 16px; padding: 0 24px; background: var(--surface-1); border-bottom: 1px solid var(--border); }
.tabs { display: flex; gap: 2px; max-width: 100%; overflow-x: auto; scrollbar-width: none; position: relative; }
.tabs button { border: 0; background: transparent; color: var(--text-secondary); cursor: pointer; font: 700 15px/20px var(--display); text-transform: uppercase;
  letter-spacing: .04em; padding: 14px 14px 11px; border-bottom: 3px solid transparent; transition: color .2s; }
.tabs button[aria-selected="true"] { color: var(--text-primary); border-bottom-color: var(--series-2); }
.tabcount { display: inline-block; min-width: 18px; padding: 0 6px; margin-left: 4px; border-radius: 9px; background: var(--st-off-bg); color: var(--st-off-fg); font: 700 11.5px/18px var(--sans); text-align: center; vertical-align: 1px; }
.subbar { display: flex; flex-wrap: wrap; gap: 6px; padding: 8px 24px; background: var(--surface-2); border-bottom: 1px solid var(--border); position: relative; }
.subbar:empty { display: none; }
.subbar button { position: relative; z-index: 1; border: 1px solid transparent; background: transparent; color: var(--text-secondary); font: 600 13.5px var(--sans); padding: 5px 12px; border-radius: 999px; cursor: pointer; transition: color .2s; }
.subbar button:hover { border-color: var(--line-strong); }
.subbar button[aria-selected="true"] { background: var(--surface-1); color: var(--text-primary); border-color: var(--line-strong); }
@media (max-width: 640px) { .tabs { flex-wrap: wrap; overflow: visible; } .tabs button { padding: 8px 10px; } .subbar { padding: 6px 12px; flex-wrap: nowrap; overflow-x: auto; scrollbar-width: none; } .subbar button { white-space: nowrap; } }
.seg { display: inline-flex; border: 1px solid var(--line-strong); border-radius: var(--r); overflow: hidden; margin: 8px 0; }
.seg button { border: 0; background: transparent; color: var(--text-secondary); padding: 6px 12px; font: 600 13px var(--sans); cursor: pointer; transition: background .2s, color .2s; }
.seg button + button { border-left: 1px solid var(--line-strong); }
.seg button[aria-pressed="true"] { background: var(--accent); color: var(--on-accent); }
/* sliding markers (dgr-ui.js adds them): the line under the main tabs, the pill behind the sub-tabs */
.dgr-uline { position: absolute; top: 0; height: 3px; border-radius: 2px; background: var(--series-2); transition: left .38s var(--ease-out), width .38s var(--ease-out), top .38s var(--ease-out); pointer-events: none; }
.dgr-pill { position: absolute; z-index: 0; border-radius: 999px; background: var(--surface-1); border: 1px solid var(--line-strong); transition: left .38s var(--ease-out), width .38s var(--ease-out), top .38s var(--ease-out); pointer-events: none; }
html.dgr-js .tabs button[aria-selected="true"] { border-bottom-color: transparent; }
html.dgr-js .subbar button[aria-selected="true"] { background: transparent; border-color: transparent; }
html.nomotion .tabs button[aria-selected="true"] { border-bottom-color: var(--series-2); }
html.nomotion .subbar button[aria-selected="true"] { background: var(--surface-1); border-color: var(--line-strong); }
html.nomotion .dgr-uline, html.nomotion .dgr-pill { display: none; }

/* ==========================================================================================================
   4. Layout: page grid, tiles (key figures), cards
   ========================================================================================================== */
main { padding: 24px 24px 48px; max-width: var(--page-max, 1440px); margin: 0 auto; display: grid; gap: 20px; }
main.dash { max-width: var(--page-max, none); grid-template-columns: repeat(var(--cols, 1), minmax(0, 1fr)); grid-auto-flow: row dense; align-items: stretch; gap: var(--gap, 20px); }
main.dash > * { min-width: 0; }
main.dash > [data-w="full"] { grid-column: 1 / -1; }
.grid2 { display: grid; grid-template-columns: repeat(auto-fit, minmax(420px, 1fr)); gap: 20px; }
.grid2 > * { min-width: 0; }
@media (max-width: 640px) { main { padding: 14px 12px 40px; } main.dash { grid-template-columns: 1fr !important; } main.dash > * { grid-column: auto !important; } .grid2 { grid-template-columns: 1fr; } }
.kpis { display: grid; grid-template-columns: repeat(auto-fit, minmax(180px, 1fr)); gap: 12px; }
.kpis > * { min-width: 0; overflow-wrap: anywhere; }
.tile, .card { background: var(--surface-1); border: 1px solid var(--border); border-radius: var(--r); }
.tile { padding: 14px 16px; border-top: 4px solid var(--border); transition: box-shadow .25s var(--ease-out), transform .25s var(--ease-out); }
.tile .label { font: 700 13px/18px var(--display); text-transform: uppercase; letter-spacing: .05em; color: var(--text-secondary); }
.tile .value { font: 700 34px/1.1 var(--display); margin-top: 6px; font-variant-numeric: tabular-nums; position: relative; }
.tile .note { color: var(--text-muted); font-size: 12px; line-height: 17px; margin-top: 4px; }
.tile.hero { background: var(--bar); border-color: var(--bar); color: var(--on-bar); }
.tile.hero .label, .tile.hero .note { color: var(--bar-muted); }
.tile.hero .value { font-size: 46px; }
.tile.alert { border-top-color: var(--series-2); }
.tile-link { cursor: pointer; }
.tile:hover, .tile-link:hover { transform: translateY(-2px); box-shadow: 0 6px 18px rgba(20, 30, 10, .12); }
.tile-link:hover { border-color: var(--series-1); }
.card { padding: 18px 20px; min-width: 0; transition: box-shadow .25s var(--ease-out), opacity .3s; }
.card h2 { font: 700 18px/24px var(--display); text-transform: uppercase; letter-spacing: .02em; margin: 0 0 2px; }
.card h3 { font: 700 15px/20px var(--display); text-transform: uppercase; letter-spacing: .03em; margin: 18px 0 6px; }
.card .desc { color: var(--text-muted); font-size: 13px; line-height: 19px; margin-bottom: 12px; }
.cardhead { display: flex; flex-wrap: wrap; gap: 8px 12px; align-items: flex-start; }
.cardhead > .ch-main { flex: 1; min-width: 240px; }
.sys-head { display: flex; align-items: center; justify-content: space-between; gap: 8px; }
.sys-head h2 { margin: 0; }
.kvs { display: flex; flex-direction: column; margin-top: 6px; }
.kvs .kv { display: flex; justify-content: space-between; align-items: baseline; gap: 16px; padding: 8px 0; border-bottom: 1px solid var(--border); }
.kvs .kv:last-child { border-bottom: 0; }
.kvs .kv > span:first-child { color: var(--text-secondary); font-size: 13px; flex: none; }
.kvs .kv > span:last-child { font-weight: 600; text-align: right; overflow-wrap: anywhere; min-width: 0; }
.kvs .kv .sub { display: block; font-weight: 400; font-size: 12px; color: var(--text-secondary); }
.mini { display: grid; grid-template-columns: repeat(auto-fit, minmax(150px, 1fr)); gap: 10px; margin: 14px 0; }
.mini > div { border: 1px solid var(--border); border-top: 4px solid var(--border); border-radius: var(--r); padding: 8px 12px; }
.mini b { display: block; font: 700 13px/18px var(--display); text-transform: uppercase; letter-spacing: .05em; color: var(--text-secondary); }
.mini span { font-size: 15px; font-weight: 700; }
.notice { background: var(--surface-2); border-left: 4px solid var(--series-2); padding: 10px 14px; font-size: 13.5px; }
.notice.bad, .errbox { background: var(--surface-2); border-left: 4px solid var(--critical); padding: 10px 14px; }
.empty { color: var(--text-muted); padding: 18px 12px; }

/* ==========================================================================================================
   5. Buttons, fields, forms, save bar
   ========================================================================================================== */
.btn { padding: 7px 14px; border: 1px solid var(--line-strong); border-radius: var(--r); background: var(--surface-1); color: var(--text-primary); font: 700 13px/18px var(--sans);
  cursor: pointer; text-decoration: none; display: inline-flex; align-items: center; justify-content: center; gap: 6px; white-space: nowrap; transition: background .15s, box-shadow .15s, transform .1s; }
.btn:hover { background: var(--surface-2); }
.btn:active { transform: translateY(1px); }
.btn:disabled { opacity: .5; cursor: default; }
.btn.primary { background: var(--accent); color: var(--on-accent); border-color: var(--accent); }
.btn.primary:hover { filter: brightness(1.12); }
.btn.danger { border-color: var(--critical); color: var(--critical); }
.btn.small { padding: 4px 10px; font-size: 12px; line-height: 16px; }
.btn.icon { padding: 6px 8px; line-height: 0; }
.btn.is-busy::before { content: ""; display: inline-block; width: 11px; height: 11px; margin-right: 7px; vertical-align: -1px; border: 2px solid currentColor; border-right-color: transparent; border-radius: 50%; animation: dgr-spin .7s linear infinite; }
.btn.is-done::before { content: "✓"; margin-right: 5px; font-weight: 700; } .btn.is-done:not(.primary):not(.danger):not(.solid) { border-color: var(--st-on-fg); color: var(--st-on-fg); }
.btn.is-fail:not(.primary):not(.danger):not(.solid) { border-color: var(--critical); color: var(--critical); } .btn.is-fail::before { content: "✗"; margin-right: 5px; font-weight: 700; }
@keyframes dgr-spin { to { transform: rotate(360deg); } }
input.in, select.in, textarea.in { width: 100%; padding: 0 10px; border: 1px solid var(--line-strong); border-radius: var(--r); background: var(--surface-1); color: var(--text-primary); font: 13.5px var(--sans); }
input.in:not([type=checkbox]):not([type=radio]), select.in { height: var(--ctl-h); }
textarea.in { padding: 8px 10px; min-height: 72px; resize: vertical; }
input.in[readonly] { background: var(--surface-2); border-color: var(--border); }
input.in.bad { border-color: var(--critical); border-width: 2px; }
input[type=file]::file-selector-button { margin-right: 10px; padding: 7px 14px; border: 1px solid var(--line-strong); border-radius: var(--r); background: var(--surface-1); color: var(--text-primary); font: 700 13px var(--sans); cursor: pointer; }
.form { display: grid; grid-template-columns: repeat(auto-fill, minmax(220px, 1fr)); gap: 14px 16px; }
.field label { display: block; font-size: 12.5px; font-weight: 700; color: var(--text-secondary); margin-bottom: 4px; }
.field .hint { font-size: 12px; color: var(--text-muted); margin-top: 3px; line-height: 16px; }
.chk { display: flex; align-items: center; gap: 8px; font-size: 13.5px; padding-top: 22px; }
.controls, .filters, .toolbar { display: flex; flex-wrap: wrap; gap: 8px 10px; align-items: center; margin: 0 0 10px; }
.savebar { position: sticky; bottom: 0; z-index: 4; display: flex; flex-wrap: wrap; align-items: center; gap: 10px 16px; background: var(--surface-1); border-top: 3px solid var(--series-2); padding: 12px 24px; }
.savebar .msg { font-size: 13.5px; font-weight: 600; } .savebar .msg.err { color: var(--critical); }
details summary { cursor: pointer; color: var(--text-secondary); margin-top: 8px; font-size: 13px; font-weight: 600; }
details::details-content { block-size: 0; overflow: clip; transition: block-size .32s var(--ease-out), content-visibility .32s allow-discrete; }
details[open]::details-content { block-size: auto; }
.popmenu { position: fixed; z-index: 1100; background: var(--surface-1); border: 1px solid var(--line-strong); border-radius: 8px; box-shadow: 0 6px 24px rgba(0,0,0,.18); padding: 4px; min-width: 220px; animation: dgr-rise .2s var(--ease); }
.popmenu button { display: block; width: 100%; text-align: left; border: 0; background: none; padding: 8px 12px; border-radius: 6px; font: inherit; color: inherit; cursor: pointer; }
.popmenu button:hover, .popmenu button:focus { background: var(--surface-2); outline: none; }

/* ==========================================================================================================
   6. Pills: who a vehicle / person is, and status (always colour + symbol + word)
   ========================================================================================================== */
.badge { display: inline-flex; align-items: center; gap: 5px; font-size: 12px; font-weight: 600; padding: 1px 9px; border-radius: 9999px; border: 1px solid var(--line-strong); color: var(--text-secondary); white-space: nowrap; }
.badge.res { background: color-mix(in srgb, #7fb24a 30%, transparent); border-color: color-mix(in srgb, #7fb24a 55%, transparent); color: var(--text-primary); }
.badge.vis { background: color-mix(in srgb, #b98a5a 28%, transparent); border-color: color-mix(in srgb, #b98a5a 55%, transparent); color: var(--text-primary); }
.badge.flag { background: var(--st-off-bg); border-color: var(--st-off-bg); color: var(--st-off-fg); }   /* solid red with white text in both themes */
.badge.watch { background: color-mix(in srgb, #a9aeb5 35%, transparent); border-color: #a9aeb5; color: var(--text-primary); }
.badge.bus { background: #1f3f8a; border-color: #1f3f8a; color: #fff; }
.badge.school { background: color-mix(in srgb, #5aa9e6 35%, transparent); border-color: #5aa9e6; color: var(--text-primary); }
.badge.svc { background: color-mix(in srgb, #f2d64b 40%, transparent); border-color: #d9bb2a; color: var(--text-primary); }
.badge.warn { border-color: var(--series-2); color: var(--series-2); }
.badge.ok { border-color: var(--series-2); color: var(--text-primary); }
.badge.act { background: var(--st-off-bg); border-color: var(--st-off-bg); color: var(--st-off-fg); }
.badge.inside { background: var(--accent); border-color: var(--accent); color: var(--on-accent); }
.badge.flag::before { content: "⚑ "; } .badge.res::before { content: "⌂ "; } .badge.watch::before { content: "◉ "; }
.st { display: inline-flex; align-items: center; gap: 6px; min-width: 104px; justify-content: center; padding: 4px 12px; border-radius: 9999px; font-weight: 700; font-size: 13px; letter-spacing: .02em; text-transform: uppercase; white-space: nowrap; }
.st::before { font-size: 12px; line-height: 1; }
.st.on { background: var(--st-on-bg); color: var(--st-on-fg); } .st.on::before { content: "✓"; }
.st.off { background: var(--st-off-bg); color: var(--st-off-fg); } .st.off::before { content: "✕"; font-weight: 900; }
.st.wait { background: var(--st-wait-bg); color: var(--st-wait-fg); } .st.wait::before { content: "…"; }
.st.none { background: var(--surface-2); color: var(--text-muted); }
.dot { width: 8px; height: 8px; border-radius: 50%; display: inline-block; }
.dot.good { background: var(--good); } .dot.warn { background: var(--warning); } .dot.bad { background: var(--critical); }

/* ==========================================================================================================
   7. Tables (rows as cards on phones)
   ========================================================================================================== */
.tablewrap { overflow-x: auto; max-height: var(--tbl-h, 440px); overflow-y: auto; border: 1px solid var(--border); }
.tablewrap.tall { max-height: var(--tbl-h-tall, max(560px, calc(100vh - 360px))); }
table { width: 100%; border-collapse: collapse; font-size: 13px; }
th, td { text-align: left; padding: 8px 12px; border-bottom: 1px solid var(--border); white-space: nowrap; }
th { background: var(--band); color: var(--on-band); font-weight: 700; position: sticky; top: 0; z-index: 1; }
tbody tr { transition: background-color .18s; }
tbody tr.click { cursor: pointer; } tbody tr.click:hover, tbody tr:hover { background: color-mix(in srgb, var(--surface-2) 70%, transparent); }
tr.this td { background: var(--surface-2); font-weight: 600; }
tr.rowin > td { animation: dgr-row .55s var(--ease); }
@keyframes dgr-row { from { opacity: 0; transform: translateY(-6px); background: color-mix(in srgb, var(--series-1) 26%, transparent); } 60% { background: color-mix(in srgb, var(--series-1) 14%, transparent); } }
.rd-new { display: inline-block; padding: 0 6px; border-radius: 9999px; background: var(--st-on-bg); color: var(--st-on-fg); font-size: 11px; font-weight: 700; text-transform: uppercase; }
@media (max-width: 640px) {
  table.cards thead { display: none; }
  table.cards tr { display: block; border-bottom: 1px solid var(--border); padding: 6px 0; }
  table.cards td { display: flex; justify-content: space-between; gap: 12px; border: 0; padding: 4px 12px; white-space: normal; }
  table.cards td::before { content: attr(data-l); color: var(--text-muted); font-size: 12px; }
}

/* ==========================================================================================================
   8. Dialogs, the side panel, confirmations, toasts, the alarm banner, the busy bar, skeletons
   ========================================================================================================== */
dialog { border: 1px solid var(--line-strong); border-radius: 8px; padding: 0; background: var(--surface-1); color: var(--text-primary); width: min(520px, calc(100vw - 32px)); }
dialog::backdrop { background: rgba(10, 14, 5, .55); }
dialog .dh { background: var(--bar); color: var(--on-bar); padding: 12px 18px; font: 700 17px/22px var(--display); text-transform: uppercase; letter-spacing: .03em; }
dialog .db { padding: 18px; display: grid; gap: 12px; }
dialog .df { display: flex; flex-wrap: wrap; gap: 8px; justify-content: flex-end; padding: 0 18px 18px; }
dialog .derr { color: var(--critical); font-weight: 600; font-size: 13px; min-height: 18px; }
dialog.wide { width: min(900px, calc(100vw - 32px)); } dialog.xwide { width: min(1180px, calc(100vw - 32px)); max-width: none; }
dialog.confirm { width: min(460px, calc(100vw - 32px)); }
dialog[open]:not(.side) { animation: dgr-dlg-in .2s var(--ease) both; }
dialog[open]::backdrop { animation: dgr-fade-in .2s ease both; }
dialog.dgr-closing:not(.side) { animation: dgr-dlg-out .2s var(--ease-in) both; }
dialog.dgr-closing::backdrop { animation: dgr-fade-out .2s ease both; }
dialog.side { position: fixed; inset: 0 0 0 auto; margin: 0; height: 100vh; max-height: 100vh; width: min(560px, 100vw); border-radius: 0; z-index: 60;
  border-width: 0 0 0 1px; box-shadow: -8px 0 32px rgba(0,0,0,.2); overflow: auto; flex-direction: column; }
dialog.side[open] { display: flex; animation: dgr-side-in .25s var(--ease) both; }
dialog.side.dgr-closing { animation: dgr-side-out .26s var(--ease-in) both; }
dialog.side .db { flex: 1; align-content: start; }
dialog.side .df { position: sticky; bottom: 0; background: var(--surface-1); padding-top: 12px; border-top: 1px solid var(--border); }
dialog.side[open] .db > * { animation: dgr-rise .25s var(--ease) both; }
dialog.side[open] .db > :nth-child(2) { animation-delay: 60ms; } dialog.side[open] .db > :nth-child(3) { animation-delay: 110ms; }
dialog.side[open] .db > :nth-child(4) { animation-delay: 160ms; } dialog.side[open] .db > :nth-child(n+5) { animation-delay: 200ms; }
.dgr-scrim { position: fixed; inset: 0; z-index: 59; background: rgba(12, 16, 6, .28); opacity: 0; pointer-events: none; transition: opacity .3s var(--ease-out); }
.dgr-scrim.on { opacity: 1; pointer-events: auto; }
@media (max-width: 700px) { dialog[open]:not(.side) { margin: auto 0 0; width: 100vw; max-width: 100vw; max-height: 88vh; border-radius: 14px 14px 0 0; animation: dgr-sheet-in .25s var(--ease) both; } }
#toasts { position: fixed; inset: auto auto 20px 50%; transform: translateX(-50%); margin: 0; border: 0; padding: 0; background: transparent; overflow: visible;
  display: flex; flex-direction: column; gap: 8px; align-items: center; z-index: 1200; max-width: 92vw; }
#toasts:empty { display: none; }
.toastmsg { display: flex; gap: 12px; align-items: flex-start; background: var(--accent); color: var(--on-accent); padding: 10px 16px; border-radius: 8px; box-shadow: 0 4px 16px rgba(0,0,0,.2); max-width: 90vw;
  animation: dgr-toast-in .25s var(--ease) both; position: relative; overflow: hidden; }
.toastmsg.err { background: var(--st-off-bg); color: var(--st-off-fg); }
.toastmsg.dgr-leaving { animation: dgr-toast-out .25s var(--ease-in) both; }
.toastmsg .tx { background: none; border: 0; color: inherit; cursor: pointer; font-size: 14px; padding: 0 2px; opacity: .8; }
.alarmbar { display: flex; flex-wrap: wrap; align-items: center; gap: 8px 12px; padding: 9px 24px; background: var(--st-off-bg); color: var(--st-off-fg); font-size: 14.5px; overflow: hidden; animation: dgr-drop .45s var(--ease-out) both; }
.alarmbar.dgr-leaving { animation: dgr-lift .3s var(--ease-in) both; }
.alarmbar .btn { background: transparent; color: #fff; border-color: rgba(255,255,255,.7); }
.alarmbar .btn:hover { background: rgba(255,255,255,.15); }
.alarmbar .ab-txt { flex: 1; min-width: 200px; }
.tabcount.dgr-pop { animation: dgr-pop .6s var(--ease-out); }
.connbar { padding: 8px 24px; background: var(--st-off-bg); color: var(--st-off-fg); font-weight: 600; font-size: 13.5px; }
.busybar { position: fixed; left: 0; right: 0; top: 0; height: 3px; z-index: 1000; pointer-events: none; overflow: hidden; }
.busybar::before { content: ""; position: absolute; top: 0; bottom: 0; width: 35%; background: var(--series-1); animation: dgr-busy 1.1s ease-in-out infinite; }
.card.loading { opacity: .62; cursor: progress; }
.skel { display: block; height: 12px; border-radius: 6px; background: var(--shimmer); background-size: 200% 100%; animation: dgr-shimmer 1.3s linear infinite; }

/* ==========================================================================================================
   9. Charts (dgr-ui.js draws them): grid, axes, lines, bars, tooltip, legend
   ========================================================================================================== */
.chart { position: relative; width: 100%; }
.chart svg { display: block; width: 100%; height: auto; overflow: visible; }
.chart text { fill: var(--text-muted); font: 11px var(--sans); }
.chart .gl { stroke: var(--grid); stroke-width: 1; }
.chart .ln { fill: none; stroke-width: 2; stroke-linejoin: round; stroke-linecap: round; }
.drawin .ln { stroke-dasharray: 1; stroke-dashoffset: 1; animation: dgr-draw .9s var(--ease) forwards; }
.drawin .br { transform-box: fill-box; transform-origin: 50% 100%; animation: dgr-grow .6s var(--ease) both; }
.drawin .bx { transform-box: fill-box; transform-origin: 0 50%; animation: dgr-growx .6s var(--ease) both; }
.drawin .end { animation: dgr-fade-in .3s .75s both; }
.legend { display: flex; flex-wrap: wrap; gap: 16px; font-size: 12.5px; color: var(--text-secondary); margin-bottom: 6px; }
.legend i { display: inline-block; width: 10px; height: 10px; margin-right: 6px; vertical-align: -1px; border-radius: 2px; }
.tip { position: absolute; pointer-events: none; background: var(--surface-1); border: 1px solid var(--line-strong); border-radius: 4px; padding: 8px 10px; font-size: 12.5px; white-space: nowrap; z-index: 3; color: var(--text-primary); opacity: 0; transition: opacity .15s; }
.tip.on { opacity: 1; }
.tip b { font-variant-numeric: tabular-nums; }
.hbars { display: flex; flex-direction: column; gap: 7px; }
.hbars .r { display: grid; grid-template-columns: 110px 1fr 54px; gap: 10px; align-items: center; font-size: 13px; }
.hbars .t { height: 12px; background: var(--surface-2); border-radius: 6px; overflow: hidden; }
.hbars .t i { display: block; height: 100%; border-radius: 0 6px 6px 0; background: var(--series-1); }
.hbars b { text-align: right; font-variant-numeric: tabular-nums; }
/* the kit's heat map is boxes (.heat > .r > i); a program's <table class="heat"> keeps its table layout (1.2.6) */
.heat:not(table) { display: flex; flex-direction: column; gap: 2px; }
.heat .r { display: grid; grid-template-columns: 34px repeat(24, 1fr); gap: 2px; align-items: center; }
.heat .r span { font-size: 11px; color: var(--text-muted); }
.heat .r i { height: 16px; border-radius: 3px; }

/* ==========================================================================================================
   10. Sign-in page, Ctrl + K finder
   ========================================================================================================== */
.signin { min-height: 100vh; display: grid; grid-template-columns: minmax(0, 1fr) minmax(0, 1fr); }
.signin .brand { background: var(--bar); display: flex; align-items: center; justify-content: center; padding: 64px; }
.signin .brand img { width: min(360px, 80%); height: auto; }
.signin .panel { display: flex; align-items: center; justify-content: center; padding: 48px 24px; }
.signin form { width: 100%; max-width: 380px; display: grid; gap: 4px; }
.signin h1 { font: 700 36px/42px var(--display); margin: 0 0 4px; }
.signin .lead { color: var(--text-secondary); font-weight: 600; margin: 0 0 28px; }
.signin label { font-weight: 600; font-size: 14px; margin-top: 12px; }
.signin input { width: 100%; padding: 11px 12px; font: 16px var(--sans); color: var(--text-primary); background: var(--surface-1); border: 1px solid var(--line-strong); border-radius: 4px; }
.signin button[type=submit] { margin-top: 20px; padding: 12px; border: 0; border-radius: 8px; background: var(--accent); color: var(--on-accent); font: 700 16px var(--sans); cursor: pointer; }
.signin .note { color: var(--text-muted); font-size: 13px; margin-top: 20px; }
@media (max-width: 760px) { .signin { grid-template-columns: 1fr; grid-template-rows: auto 1fr; } .signin .brand { padding: 32px; } .signin .brand img { width: min(220px, 70%); } }
.dgr-finder { position: fixed; left: 50%; top: 12vh; z-index: 1300; width: min(620px, calc(100% - 24px)); background: var(--surface-1); color: var(--text-primary); border: 1px solid var(--line-strong); border-radius: 14px;
  box-shadow: 0 20px 60px rgba(0,0,0,.3); overflow: hidden; transform: translate(-50%, -8px) scale(.98); opacity: 0; pointer-events: none; transition: all .25s var(--ease-out); }
.dgr-finder.on { transform: translate(-50%, 0); opacity: 1; pointer-events: auto; }
.dgr-finder input { width: 100%; height: 56px; border: 0; border-bottom: 1px solid var(--border); padding: 0 18px; font: 600 17px var(--sans); background: transparent; color: inherit; outline: none; }
.dgr-finder .r { max-height: 50vh; overflow-y: auto; padding: 6px 0; }
.dgr-finder .g { font: 700 11px var(--display); letter-spacing: .1em; text-transform: uppercase; color: var(--text-muted); padding: 8px 18px 4px; }
.dgr-finder button { display: flex; gap: 10px; align-items: center; width: 100%; border: 0; background: none; color: inherit; padding: 9px 18px; text-align: left; cursor: pointer; font: 14px var(--sans); }
.dgr-finder button.sel, .dgr-finder button:hover { background: var(--surface-2); }
.dgr-finder .k { margin-left: auto; color: var(--text-muted); font-size: 12px; }
.dgr-finderbg { position: fixed; inset: 0; z-index: 1299; background: rgba(12, 16, 6, .3); opacity: 0; pointer-events: none; transition: opacity .25s; }
.dgr-finderbg.on { opacity: 1; pointer-events: auto; }

/* ==========================================================================================================
   11. Motion: pages, things that appear and leave. All off with html.nomotion or Windows' "fewer animations"
   ========================================================================================================== */
main.dgr-in { animation: dgr-vin .22s var(--ease) both; }   /* 1.3.0: ANPR's page enter – the page as one, no stagger */
.dgr-appear:not([hidden]) { animation: dgr-rise .4s var(--ease-out) both; }
details[open] > :not(summary) { animation: dgr-rise-s .22s var(--ease) both; }
::view-transition-old(root) { animation: dgr-fade-out .18s var(--ease-in) both; }
::view-transition-new(root) { animation: dgr-fade-in .32s var(--ease-out) both; }
@keyframes dgr-rise { from { opacity: 0; transform: translateY(12px); } }
@keyframes dgr-fade-in { from { opacity: 0; } } @keyframes dgr-fade-out { to { opacity: 0; } }
@keyframes dgr-dlg-in { from { opacity: 0; transform: translateY(10px) scale(.98); } }
@keyframes dgr-vin { from { opacity: 0; transform: translateY(6px); } }
@keyframes dgr-rise-s { from { opacity: 0; transform: translateY(8px); } }
@keyframes dgr-dlg-out { to { opacity: 0; transform: translateY(8px) scale(.97); } }
@keyframes dgr-sheet-in { from { opacity: 0; transform: translateY(40px); } }
@keyframes dgr-side-in { from { transform: translateX(100%); } } @keyframes dgr-side-out { to { transform: translateX(100%); } }
@keyframes dgr-toast-in { from { opacity: 0; transform: translateY(8px); } } @keyframes dgr-toast-out { to { opacity: 0; transform: translateY(10px); } }
@keyframes dgr-drop { from { opacity: 0; max-height: 0; padding-top: 0; padding-bottom: 0; } to { max-height: 90px; } }
@keyframes dgr-lift { from { max-height: 90px; } to { opacity: 0; max-height: 0; padding-top: 0; padding-bottom: 0; } }
@keyframes dgr-pop { 0% { transform: scale(.4); } 55% { transform: scale(1.35); } 100% { transform: scale(1); } }
@keyframes dgr-draw { to { stroke-dashoffset: 0; } } @keyframes dgr-grow { from { transform: scaleY(0); } } @keyframes dgr-growx { from { transform: scaleX(0); } }
@keyframes dgr-shimmer { from { background-position: 200% 0; } to { background-position: -200% 0; } }
@keyframes dgr-busy { from { left: -35%; } to { left: 100%; } }
html.nomotion *, html.nomotion *::before, html.nomotion *::after, html.nomotion ::view-transition-group(*) { animation: none !important; transition: none !important; }
html.nomotion .drawin .ln { stroke-dashoffset: 0; }
html.nomotion .btn.is-busy::before { animation: dgr-spin .7s linear infinite !important; }   /* 1.3.0: the spinner says "working" – it keeps turning */
html.nomotion .toastmsg .undobar { animation: dgr-undobar 10s linear forwards !important; }    /* the undo countdown is information, not decoration */
html.nomotion { scroll-behavior: auto; }
@media (prefers-reduced-motion: reduce) {
  html:not(.motion-on) *, html:not(.motion-on) *::before, html:not(.motion-on) *::after { animation-duration: 1ms !important; animation-iteration-count: 1 !important; transition-duration: 1ms !important; }
  html:not(.motion-on) .drawin .ln { stroke-dashoffset: 0; } html:not(.motion-on) { scroll-behavior: auto; }
  html:not(.motion-on) .btn.is-busy::before { animation-duration: .7s !important; animation-iteration-count: infinite !important; }
  html:not(.motion-on) .toastmsg .undobar { animation-duration: 10s !important; }
}

/* ==========================================================================================================
   12. Print and wall display
   ========================================================================================================== */
@media print { header, nav.bar, .subbar, .savebar, .btn, #toasts, .dgr-finder { display: none !important; } .card, .tile { break-inside: avoid; box-shadow: none; } }
body.wall header .in-menu, body.wall nav.bar, body.wall .subbar { display: none !important; }
body.wall .tile .value { font-size: 56px; }

/* ==========================================================================================================
   13. Program tools (1.1.0, from Dinokeng NetWatch): status colours for devices, maintenance, display settings,
       top-bar extras, table tools, settings pages, long text (logs, code, manual, version history), job progress,
       update banner, side panel sizing, import preview, diagrams (network map, site plan, drag and drop), wall cards
   ========================================================================================================== */
/* --- extra tokens: maintenance (blue) and the small status marks (dots, stripes) used on the green top bar and in diagrams.
       The marks use the same values in light and dark because they always sit next to their word or on the dark bar. */
:root { --maint: #5aa9e6; --st-maint-bg: #d6e9f8; --st-maint-fg: #0f4c7a;
  --mark-on: #2fbf5b; --mark-wait: #f2b33d; --mark-off: #ff5a4f; --mark-maint: #5aa9e6; --mark-none: #9aa0a6; }

/* --- display settings (Settings → Display): table height and spacing, set on <html> */
:root[data-tables="tall"] { --tbl-h: 60vh; --tbl-h-tall: 75vh; }
:root[data-tables="screen"] { --tbl-h: calc(100vh - 300px); --tbl-h-tall: calc(100vh - 220px); }
:root[data-density="compact"] { --gap: 12px; }
:root[data-density="compact"] .card { padding: 12px 14px; }
:root[data-density="compact"] main { padding-top: 14px; }
:root[data-density="compact"] th, :root[data-density="compact"] td { padding: 5px 8px; }
:root[data-density="compact"] .card .desc { margin-bottom: 8px; }
.dispform { display: grid; grid-template-columns: repeat(auto-fill, minmax(240px, 1fr)); gap: 14px 16px; }

/* --- page layout helpers: a card over the full width; a card hidden by a sub-tab; one card alone fills the row */
.full { grid-column: 1 / -1; }
.offsub { display: none !important; }
main.dash.solo > section.card:not(.offsub) { grid-column: 1 / -1 !important; }

/* --- top bar extras: version (click for the version history), server load meter, status dots, links to the other programs */
header .ver { color: var(--bar-muted); font-size: 12.5px; white-space: nowrap; cursor: pointer; }
header .ver:empty { display: none; }
header .ver:hover { color: var(--on-bar); text-decoration: underline; }
header .livegrp { display: flex; align-items: center; gap: 10px; }
@media (max-width: 1600px) { header .ver { display: none; } }
.pcw { display: inline-grid; grid-template-columns: auto 44px auto; align-items: center; gap: 1px 6px; padding: 3px 8px; background: transparent;
  border: 1px solid rgba(255,255,255,.18); border-radius: 6px; color: var(--bar-muted); font: 600 10.5px/1.15 var(--sans); cursor: pointer; font-variant-numeric: tabular-nums; }
.pcw:hover { border-color: rgba(255,255,255,.45); color: var(--on-bar); }
.pcw[hidden] { display: none; }
.pcw .l { text-align: left; } .pcw .v { text-align: right; min-width: 28px; }
.pcw .b { height: 4px; border-radius: 2px; background: rgba(255,255,255,.18); overflow: hidden; }
.pcw .b i { display: block; height: 100%; background: var(--mark-on); }
.pcw .b.hi i { background: var(--mark-wait); } .pcw .b.top i { background: var(--mark-off); }
.dots { display: flex; flex-wrap: wrap; gap: 3px; align-items: center; max-width: 170px; cursor: pointer; padding: 5px 7px; border-radius: 6px; border: 1px solid rgba(255,255,255,.25); }
.dots:empty { display: none; }
.dots:hover { border-color: rgba(255,255,255,.6); }
.dots i { width: 9px; height: 9px; border-radius: 50%; background: var(--mark-on); }
.dots i.off { background: var(--mark-off); border-radius: 1px; }
.dots i.wait { background: var(--mark-wait); border-radius: 1px; transform: rotate(45deg) scale(.8); }
.dots i.maint { background: var(--mark-maint); } .dots i.none { background: var(--mark-none); }
header .peer { padding-left: 10px; padding-right: 10px; }
@media (max-width: 1750px) { header .peer, header .user { display: none; } }
@media (max-width: 1400px) { .dots { display: none !important; } header .search input { width: 130px; } }
@media (max-width: 900px) { .pcw { display: none !important; } }
body.wall .pcw, body.wall .dots { display: none !important; }

/* --- status: maintenance is the fifth state (↻, blue); a small pill for lists */
.st.maint { background: var(--st-maint-bg); color: var(--st-maint-fg); } .st.maint::before { content: "↻"; }
.st.sm { min-width: 0; padding: 2px 9px; font-size: 11.5px; }
.badge.add { border-color: var(--st-on-fg); color: var(--st-on-fg); }
.badge.upd { border-color: var(--series-2); color: var(--series-2); }
.badge.err { border-color: var(--critical); color: var(--critical); }

/* --- tiles that are buttons (filter the list below), the status edge of a tile, the chosen tile, skeleton and changed values */
button.tile { font: inherit; color: inherit; text-align: left; cursor: pointer; width: 100%; }
button.tile:hover { border-color: var(--line-strong); }
.tile.k-down { border-top-color: var(--critical); } .tile.k-warn { border-top-color: var(--warning); }
.tile.k-maint { border-top-color: var(--maint); } .tile.k-events { border-top-color: var(--series-2); }
.tile.hero.k-up { border-top-color: var(--bar); }
.tile.sel { outline: 3px solid var(--focus-ring); outline-offset: -1px; }
.tile .value.bump { animation: dgr-bump 1.4s var(--ease); }
@keyframes dgr-bump { 0%, 25% { color: var(--series-1); } }
.tile .value.skelv { color: transparent; position: relative; }
.tile .value.skelv::after { content: ""; position: absolute; left: 0; top: 6px; width: 72px; height: 28px; border-radius: 6px; background: var(--shimmer); background-size: 200% 100%; animation: dgr-shimmer 1.3s linear infinite; }
.skel-lines { display: grid; gap: 10px; padding: 8px 0; }
.skel-lines .skel:nth-child(3n) { width: 62%; } .skel-lines .skel:nth-child(3n+2) { width: 84%; }

/* --- a list of things with a status (devices, cameras, people): status · name and detail · key figures; a reason line under it */
.devlist { display: grid; gap: 0; margin-top: 8px; }
.devrow { display: grid; grid-template-columns: auto minmax(0, 1fr) auto; gap: 4px 12px; align-items: center; text-align: left; padding: 9px 4px;
  border: 0; border-bottom: 1px solid var(--border); background: none; color: inherit; font: inherit; cursor: pointer; width: 100%; }
.devrow:last-child { border-bottom: 0; }
.devrow:hover { background: var(--surface-2); }
.devrow .st { align-self: start; margin-top: 2px; }
.devrow .dn { display: block; font-weight: 700; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.devrow .ds { display: block; font-size: 12px; color: var(--text-muted); overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.devrow .dm { font-size: 12px; color: var(--text-secondary); text-align: right; white-space: nowrap; }
.devrow .dm b { color: var(--text-primary); }
.devrow .dr { grid-column: 2 / -1; font-size: 12px; font-weight: 600; color: var(--warning); }
.devrow .dr.bad { color: var(--critical); }
.card.site h2, .card h2.withsub { display: flex; align-items: baseline; gap: 10px; }
.card h2 small { font: 600 12.5px var(--sans); text-transform: none; letter-spacing: 0; color: var(--text-secondary); }

/* --- table tools: search and count, column picker, sortable headers, edit-in-place tables, selected and grouped rows */
.tbar { display: flex; flex-wrap: wrap; gap: 8px 10px; align-items: center; margin: 0 0 10px; }
:is(.controls, .filters, .toolbar, .tbar) > label:not(.chk) { font-size: 13px; font-weight: 600; color: var(--text-secondary); margin: 0; }
:is(.controls, .filters, .toolbar, .tbar) > span:not(.muted):not(.badge) { font-size: 13px; color: var(--text-secondary); }
:is(.controls, .filters, .toolbar, .tbar) .muted { font-size: 13px; }
:is(.controls, .filters, .toolbar, .tbar) .chk { padding: 0; }
:is(.controls, .filters, .toolbar, .tbar) :is(.btn, .colpick > summary) { min-height: var(--ctl-h); }
:is(.controls, .filters, .toolbar, .tbar) > label:not(.chk) + :is(input, select) { margin-left: -4px; }
@media (max-width: 640px) {
  :is(.controls, .filters, .toolbar, .tbar) > label:not(.chk) { flex: 0 0 100%; margin: 2px 0 -4px; }
  :is(.controls, .filters, .toolbar, .tbar) > :is(input, select) { flex: 1 1 100%; width: 100% !important; margin-left: 0; }
}
.tbar .tq { width: 220px; max-width: 100%; }
.tbar .tcount { font-size: 12.5px; }
.colpick { position: relative; display: inline-block; flex: none; }
.colpick > summary { list-style: none; cursor: pointer; display: inline-flex; align-items: center; gap: 4px; margin-top: 0; font: 700 12px/16px var(--sans); padding: 4px 10px;
  border: 1px solid var(--line-strong); border-radius: var(--r); color: var(--text-primary); background: var(--surface-1); }
.colpick > summary .muted { font-weight: 600; }
.colpick > summary::-webkit-details-marker { display: none; }
.colpick[open] > summary { background: var(--surface-2); }
.colpick .cp { position: absolute; z-index: 20; top: calc(100% + 4px); right: 0; left: auto; max-width: calc(100vw - 32px); background: var(--surface-1); border: 1px solid var(--line-strong);
  border-radius: var(--r); padding: 8px 12px; min-width: 190px; box-shadow: 0 4px 14px rgba(0,0,0,.15); }
.colpick .cp label { display: flex; gap: 8px; align-items: center; font-size: 13px; padding: 3px 0; white-space: nowrap; cursor: pointer; }
.colpick .cp .muted { font-size: 12px; display: block; margin-top: 6px; }
th.sortable { cursor: pointer; user-select: none; }
th.sortable:hover, th.sortable:focus-visible { text-decoration: underline; outline: none; }
th .sarr { font-size: 10px; }
table.edit td { padding: 4px 6px; }
table.edit input.in, table.edit select.in { padding: 0 7px; min-width: 70px; height: 30px; }
td.empty .eh { display: block; font-size: 12.5px; margin-top: 4px; }
table.keys td { padding: 5px 10px 5px 0; white-space: normal; }
tr.sel td { background: color-mix(in srgb, var(--accent) 12%, transparent); }
tr.sel td:first-child { box-shadow: inset 4px 0 0 var(--accent); }
tr.runsep td { border-top: 2px solid var(--line-strong); }
time.rel { white-space: nowrap; border-bottom: 1px dotted var(--line-strong); cursor: help; }
.over { font-weight: 700; color: var(--series-2); }

/* --- settings pages: section pills on the left of the settings, day picker, a button that wants attention */
.subnav { display: flex; flex-wrap: wrap; gap: 6px; margin: 0 0 18px; }
.subnav button { border: 1px solid var(--line-strong); background: var(--surface-1); color: var(--text-secondary); border-radius: 9999px; padding: 6px 14px; font: 600 13.5px var(--sans); cursor: pointer; }
.subnav button[aria-pressed="true"] { background: var(--accent); border-color: var(--accent); color: var(--on-accent); }
@media (max-width: 900px) { .subnav { flex-wrap: nowrap; overflow-x: auto; scrollbar-width: none; padding-bottom: 2px; } .subnav button { white-space: nowrap; flex: none; } }
.daypick { display: flex; flex-wrap: wrap; gap: 4px 12px; } .daypick label { display: inline-flex; gap: 4px; align-items: center; font-size: 13.5px; }
.btn.primary.attn { box-shadow: 0 0 0 3px color-mix(in srgb, var(--accent) 30%, transparent); }
textarea.in.mono { min-height: 160px; font: 12.5px/1.4 ui-monospace, Consolas, monospace; }

/* --- long text: log viewer, code and command output, numbered steps */
.logview { margin: 0; max-height: 560px; overflow: auto; background: var(--surface-2); border: 1px solid var(--line-strong); border-radius: var(--r); padding: 10px 12px;
  font: 12px/1.45 ui-monospace, Consolas, monospace; white-space: pre-wrap; word-break: break-word; }
.logview .lv-w { color: var(--series-2); font-weight: 600; } .logview .lv-e { color: var(--critical); font-weight: 700; } .logview .lv-d { opacity: .7; }
.logview mark { background: var(--accent); color: var(--on-accent); border-radius: 2px; }
.code { font: 12px/17px ui-monospace, Consolas, monospace; white-space: pre-wrap; background: var(--surface-2); padding: 10px; border-radius: 4px; max-height: 420px; overflow: auto; }
.steps { margin: 6px 0 12px; padding-left: 20px; } .steps li { margin: 4px 0; }
mark.d { background: var(--series-2); color: #fff; padding: 0 1px; }

/* --- version history (dialog and page) */
.changelog { font-size: 14px; line-height: 21px; max-height: 70vh; overflow: auto; }
.changelog h3 { font: 700 18px/24px var(--display); text-transform: uppercase; margin: 18px 0 6px; }
.changelog h3:first-child { margin-top: 0; }
.changelog .cur { font: 600 11px/16px var(--sans); text-transform: none; border: 1px solid var(--line-strong); border-radius: 9999px; padding: 1px 8px; margin-left: 8px; vertical-align: middle; }
.changelog ul { margin: 4px 0 8px; padding-left: 20px; } .changelog li { margin: 2px 0; } .changelog p { margin: 6px 0; }
.changelog code { font-size: 12.5px; background: var(--surface-2); padding: 0 4px; border-radius: 3px; }
.changelog table { border-collapse: collapse; margin: 8px 0; font-size: 13px; }
.changelog td, .changelog th { border: 1px solid var(--border); padding: 3px 8px; text-align: left; }
.cl-page { max-height: none; }
/* 1.3.1: the version history page as in Dinokeng ANPR – search, Open all / Close all, one folding entry per version
   (DGR.changelog). Heading: "## <version> – <date> · <what it brought>" */
.cl-tools { display: flex; flex-wrap: wrap; align-items: center; gap: 8px; margin: 8px 0 12px; }
.cl-tools input[type=search] { width: 260px; max-width: 100%; }
.cl-page details { border-bottom: 1px solid var(--border); padding: 6px 0; }
.cl-page details[hidden] { display: none; }
.cl-page details > :not(summary) { margin-left: 18px; }
.cl-page summary .cur { font: 600 11px/16px var(--sans); text-transform: none; border: 1px solid var(--line-strong); border-radius: 9999px; padding: 1px 8px; margin-left: 8px; vertical-align: middle; }
.cl-page li.cl-off { display: none; }
@media (max-width: 640px) { .cl-page summary { font-size: 14px; line-height: 20px; } .cl-tools input[type=search] { width: 100%; } }
.cl-page summary { cursor: pointer; font: 700 16px/24px var(--display); text-transform: uppercase; color: var(--text-primary); }

/* --- the manual: contents with search on the left, the text on the right; prints on its own */
.man { display: grid; grid-template-columns: 250px minmax(0, 1fr); gap: 24px; align-items: start; }
.man-toc { position: sticky; top: 12px; display: flex; flex-direction: column; gap: 10px; max-height: calc(100vh - 24px); overflow: auto; }
.man-toc nav a { display: block; padding: 4px 8px; border-radius: 6px; color: var(--text-secondary); text-decoration: none; font-size: 14px; }
.man-toc nav a:hover, .man-toc nav a.on { background: var(--surface-2); color: var(--text-primary); }
.man-toc nav a.sub { padding-left: 20px; font-size: 13px; }
.man-body { max-width: 980px; line-height: 1.55; }
.man-body h2 { margin: 28px 0 8px; padding-top: 6px; border-top: 1px solid var(--border); }
.man-body h2:first-child { border-top: 0; margin-top: 0; }
.man-body h3 { margin: 18px 0 6px; font-size: 15px; }
.man-body table { border-collapse: collapse; margin: 8px 0 12px; width: 100%; }
.man-body th, .man-body td { border: 1px solid var(--border); padding: 6px 10px; text-align: left; vertical-align: top; white-space: normal; }
.man-body th { background: var(--surface-2); color: var(--text-primary); position: static; }
.man-body ul { margin: 6px 0 10px 20px; padding: 0; }
.man-body code { background: var(--surface-2); padding: 1px 5px; border-radius: 4px; }
.man-note { color: var(--text-muted); font-size: 13px; }
.man-sec[hidden] { display: none; }
mark.mhit { background: #ffe58a; color: #222; border-radius: 2px; }
main.manual { max-width: var(--page-max, none); margin: 0 auto; padding: 20px 24px 40px; display: block; }
@media (max-width: 760px) { .man { grid-template-columns: 1fr; } .man-toc { position: static; max-height: none; } }
@media print {
  .man-toc, .upd-banner, body[data-view="manual"] main:not(.manual) { display: none !important; }
  body[data-view="manual"] main.manual { display: block !important; padding: 0; }
  .man { display: block; } .man-body { border: 0; box-shadow: none; max-width: none; }
  .man-sec[hidden] { display: block !important; }
  .man-body h2 { break-before: auto; break-after: avoid; } .man-body table { table-layout: fixed; }
  .man-body td, .man-body th { overflow-wrap: anywhere; }
}

/* --- update banner (a new version is installed: reload) and a notice line under the tabs */
.upd-banner { display: flex; gap: 10px; align-items: center; justify-content: center; padding: 6px 12px; background: var(--st-on-bg); color: var(--st-on-fg); font-size: 14px; }
.upd-banner a { color: inherit; font-weight: 700; cursor: pointer; text-decoration: underline; }
.noticebar:empty { display: none; }

/* --- the busy bar shows itself while the page waits for the server (body.busy); cards dim a little */
.busybar.auto { display: none; }
body.busy .busybar.auto { display: block; }
body.busy main:not([hidden]) .card { transition: opacity .2s; opacity: .62; }

/* --- progress: a bar with stripes while it runs, and the job bar under the tabs (what runs now, its steps, its waits) */
.im-bar { flex: 0 0 auto; width: 640px; max-width: 100%; height: 10px; border-radius: 5px; background: color-mix(in srgb, var(--series-1) 15%, transparent); overflow: hidden; }
.im-bar i { display: block; height: 100%; width: 0; background: var(--series-1); transition: width .4s;
  background-image: linear-gradient(45deg, rgba(255,255,255,.18) 25%, transparent 25% 50%, rgba(255,255,255,.18) 50% 75%, transparent 75%); background-size: 16px 16px; animation: dgr-stripes 1s linear infinite; }
@keyframes dgr-stripes { to { background-position: 16px 0; } }
.jobbar { display: none; padding: 6px 24px; font-size: 13px; background: var(--surface-2); border-bottom: 1px solid var(--border); }
.jobbar.on { display: flex; flex-direction: column; gap: 4px; }
.jobbar .jb { display: grid; grid-template-columns: minmax(0, 360px) minmax(0, 640px) auto; align-items: center; gap: 10px; }
.jobbar .jb > b { overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.jobbar .jb .btn { margin-left: 6px; }
.jobbar .im-bar { max-width: 640px; }
.jobbar .jb-now { display: flex; align-items: center; gap: 8px; font-weight: 600; min-width: 0; }
.jobbar .jb-now > span { overflow: hidden; text-overflow: ellipsis; white-space: nowrap; min-width: 0; }
.jobbar .jb-dot { width: 9px; height: 9px; border-radius: 50%; background: var(--series-2); flex: none; animation: dgr-blink 1.4s ease-in-out infinite; }
@keyframes dgr-blink { 50% { opacity: .25; } }
.jobbar .jb-wbar { display: inline-block; width: 120px; height: 6px; border-radius: 3px; background: var(--border); vertical-align: middle; overflow: hidden; margin-left: 4px; }
.jobbar .jb-wbar i { display: block; height: 100%; background: var(--series-2); transition: width 1s linear; }
.jobbar .jb-log summary { cursor: pointer; font-size: 12.5px; color: var(--text-muted); margin-top: 0; }
.jobbar .jb-log ol { list-style: none; margin: 4px 0 2px; padding: 0; font-size: 12.5px; display: grid; gap: 2px; }
.jobbar .jb-log li { overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.jobbar .jb-log li.bad { color: var(--critical); }
@media (max-width: 900px) { .jobbar { padding: 6px 12px; } .jobbar .jb { grid-template-columns: 1fr; } }

/* --- the side panel can be made wider (drag its edge, or ‹ ›); its content never gets wider than the panel */
dialog .db { grid-template-columns: minmax(0, 1fr); }
dialog .db > * { min-width: 0; }
dialog.side { width: min(var(--qv-w, 560px), 100vw); max-width: 100vw; }
.qv-grip { position: absolute; left: 0; top: 0; bottom: 0; width: 8px; cursor: ew-resize; z-index: 2; touch-action: none; }
.qv-grip:hover, .qv-grip.on { background: color-mix(in srgb, var(--series-2) 35%, transparent); }
.qv-dh { display: flex; align-items: center; gap: 10px; }
.qv-dh > .qv-title { flex: 1; min-width: 0; display: flex; flex-wrap: wrap; align-items: center; gap: 6px 10px; overflow-wrap: anywhere; }
.qv-title .st { font-family: var(--sans); text-transform: none; letter-spacing: 0; }
.qv-size { display: inline-flex; gap: 4px; flex: none; }
.qv-size .btn { border-color: var(--bar-muted); background: transparent; color: var(--on-bar); padding: 2px 9px; line-height: 1.2; font-size: 16px; }
@media (max-width: 640px) { .qv-size, .qv-grip { display: none; } dialog.side { width: 100vw; } }
dialog.side .kvs .kv > span:last-child { white-space: normal; }
dialog.side th, dialog.side td { padding: 6px 8px; }
dialog.side .tablewrap { max-height: none; }
/* readings you can click to see their history; the path from here to the core */
.qv-metrics { display: grid; grid-template-columns: repeat(auto-fill, minmax(min(130px, 100%), 1fr)); gap: 8px; }
.qv-metrics button { text-align: left; border: 1px solid var(--border); border-top: 4px solid var(--border); border-radius: var(--r); padding: 6px 10px; background: var(--surface-1); color: inherit; font: inherit; cursor: pointer; }
.qv-metrics button[aria-pressed="true"] { border-top-color: var(--series-2); }
.qv-metrics button:disabled { cursor: default; }
.qv-metrics b { display: block; font: 700 12px/16px var(--display); text-transform: uppercase; letter-spacing: .05em; color: var(--text-secondary); }
.qv-metrics span { font-size: 15px; font-weight: 700; }
.qv-path { display: flex; flex-wrap: wrap; align-items: center; gap: 4px 6px; font-size: 13px; }
.qv-path a { cursor: pointer; }
.ifmore { margin-top: 6px; }

/* --- import preview: counts of new / update / skipped above the table */
.imp-sum { display: flex; flex-wrap: wrap; gap: 8px; margin: 6px 0 10px; }

/* --- diagrams (network map, site plan): a pannable canvas, boxes with a status stripe, lines, flows, groups, legend */
.mapwrap { position: relative; overflow: auto; height: max(460px, calc(100vh - 340px)); border: 1px solid var(--border); border-radius: var(--r); background: var(--surface-2); cursor: grab; touch-action: none; }
.mapwrap.drag { cursor: grabbing; user-select: none; }
.mapwrap svg { display: block; }
.mapwrap .empty { padding: 40px; text-align: center; }
.maplegend { display: flex; flex-wrap: wrap; gap: 6px 14px; font-size: 12.5px; color: var(--text-secondary); margin: 0 0 8px; }
.maplegend span { display: inline-flex; align-items: center; gap: 6px; }
.maplegend svg { overflow: visible; }
.maplegend i { display: inline-block; width: 10px; height: 10px; border-radius: 50%; }
.mp-node, .pl-node { cursor: pointer; }
.mp-node rect.box, .pl-node rect.box { fill: var(--surface-1); stroke: var(--line-strong); stroke-width: 1; }
.mp-node:hover rect.box, .mp-node:focus rect.box, .pl-node:hover rect.box { stroke: var(--series-2); stroke-width: 2; }
.mp-node.hit rect.box { stroke: var(--focus-ring); stroke-width: 3; }
.mp-node.core rect.box { fill: var(--accent); stroke: var(--accent); }
.mp-node.core text { fill: var(--on-accent) !important; }
.mp-node text.n, .pl-node text.n { font: 700 12.5px var(--sans); fill: var(--text-primary); }
.mp-node text.s, .pl-node text.s { font: 11px var(--sans); fill: var(--text-muted); }
rect.stripe.up { fill: var(--mark-on); } rect.stripe.warn { fill: var(--mark-wait); } rect.stripe.down { fill: var(--mark-off); }
rect.stripe.maint { fill: var(--mark-maint); } rect.stripe.unknown { fill: var(--mark-none); }
.mp-edge { fill: none; stroke: var(--line-strong); stroke-linecap: round; opacity: .75; }
.mp-edge.down { stroke: var(--critical); stroke-dasharray: 6 5; opacity: 1; }
.mp-edge.manual { stroke-dasharray: 2 6; opacity: .6; }
.mp-edge.site { stroke-dasharray: 8 6; opacity: .6; }
.mp-extra { fill: none; stroke: var(--maint); stroke-width: 2.5px; stroke-dasharray: 7 5; opacity: .9; }
.mp-flow { fill: none; stroke: var(--series-2); stroke-linecap: round; stroke-dasharray: 2 14; animation: dgr-flow 1.4s linear infinite; opacity: .9; }
.mp-flow.up { animation-direction: reverse; }
@keyframes dgr-flow { to { stroke-dashoffset: -16; } }
.mp-lbl { font: 600 10.5px var(--sans); fill: var(--text-secondary); paint-order: stroke; stroke: var(--surface-2); stroke-width: 4px; stroke-linejoin: round; }
.mp-sec, .pl-head { font: 700 12px var(--display); letter-spacing: .05em; text-transform: uppercase; fill: var(--text-muted); }
.mp-site rect { fill: var(--surface-1); fill-opacity: .55; stroke: var(--border); stroke-width: 1.5; }
.mp-site.core rect { stroke: var(--accent); stroke-width: 2; }
.mp-site text { font: 700 12px var(--display); letter-spacing: .05em; text-transform: uppercase; fill: var(--text-secondary); }
.mp-site .cnt { font: 600 11px var(--sans); letter-spacing: 0; text-transform: none; fill: var(--text-muted); }
.mp-sitelbl { paint-order: stroke; stroke: var(--surface-2); stroke-width: 4px; stroke-linejoin: round; }
/* the plan of one group (site): the groups on the left, the drawing, then the add row */
.plan { display: grid; grid-template-columns: 220px minmax(0, 1fr); gap: 18px; align-items: start; }
.plan-sites { display: flex; flex-direction: column; gap: 4px; position: sticky; top: 140px; max-height: calc(100vh - 160px); overflow: auto; }
.plan-sites button { display: flex; justify-content: space-between; gap: 8px; text-align: left; border: 1px solid var(--border); background: var(--surface-1); color: var(--text-primary);
  border-radius: var(--r); padding: 7px 10px; font: 600 13.5px var(--sans); cursor: pointer; }
.plan-sites button[aria-pressed="true"] { background: var(--accent); color: var(--on-accent); border-color: var(--accent); }
.plan-sites button small { font-weight: 600; opacity: .75; }
@media (max-width: 900px) { .plan { grid-template-columns: 1fr; } .plan-sites { flex-direction: row; flex-wrap: wrap; position: static; max-height: none; } }
.plan-svg { border: 1px solid var(--border); border-radius: var(--r); background: var(--surface-2); overflow-x: auto; margin: 6px 0 14px; }
.plan-svg svg { display: block; touch-action: pan-y; user-select: none; }
.pl-node.remote rect.box { fill: var(--surface-2); stroke-dasharray: 4 3; }
.pl-node.switch rect.box { stroke-dasharray: 6 3; stroke-width: 1.5; }
.pl-link { fill: none; stroke: var(--line-strong); stroke-width: 2.2px; }
.pl-link.radio { stroke-dasharray: 8 5; }
.pl-link.failover { stroke: var(--maint); stroke-dasharray: 3 5; }
.plan-add { display: flex; flex-wrap: wrap; gap: 8px 10px; align-items: center; margin-top: 10px; }
.plan-add select, .plan-add input { width: auto; min-width: 160px; }
/* drag and drop to link two boxes: the box you drag from, the box you drop on, the line while dragging */
.dragsrc rect.box { stroke: var(--series-2); stroke-width: 2.5; }
.droptgt rect.box { stroke: var(--accent); stroke-width: 3; fill: var(--surface-2); }
.pl-drag { stroke: var(--accent); stroke-width: 2.5; stroke-dasharray: 6 4; fill: none; pointer-events: none; }
.dragging, .dragging .pl-node { cursor: crosshair; }

/* --- wall display: one card per problem with a coloured edge; a big "all fine" when there is none */
.wallbar { display: none; }
body.wall .wallbar { display: flex; align-items: center; gap: 16px; }
body.wall .wallbar .clock { font: 700 30px/1 var(--display); color: var(--on-bar); }
body.wall header .btn:not(.wall-exit), body.wall header .user, body.wall header .ver, body.wall .tbar { display: none !important; }
.wallgrid { display: grid; grid-template-columns: repeat(auto-fill, minmax(360px, 1fr)); gap: 12px; }
.wallcard { border: 1px solid var(--border); border-left: 6px solid var(--warning); border-radius: var(--r); padding: 12px 16px; background: var(--surface-1); }
.wallcard.down { border-left-color: var(--critical); } .wallcard.maint { border-left-color: var(--maint); }
.wallcard b { display: block; font: 700 22px/28px var(--display); text-transform: uppercase; }
.wallok { font: 700 40px/50px var(--display); text-transform: uppercase; color: var(--st-on-fg); text-align: center; padding: 60px 0; }
@media print { .jobbar, .tbar, header .ver, .live-btn { display: none !important; } }

/* --- the band at the foot of every page: "Dinokeng Game Reserve: <program>" */
footer.band { background: var(--band); color: var(--on-band); padding: 12px 24px; font: 700 14px/20px var(--display); text-transform: uppercase; letter-spacing: .03em; display: flex; flex-wrap: wrap; gap: 6px; }
footer.band span { font-weight: 300; }

/* --- from Dinokeng ANPR, kept for every program (1.1.0): dashboard cards line up, empty tables, clickable rows, keys, touch, tablets */
main.dash > .card { display: flex; flex-direction: column; }
main.dash > .card > .chart { margin-top: auto; }
main.dash > .card > .desc { min-height: 38px; }
.kpis svg { max-width: 100%; }
.cardhead > .btn, .cardhead > a.btn, .cardhead > label { margin-top: 2px; }
td.empty { padding: 26px 16px !important; text-align: center; color: var(--text-muted); white-space: normal; }
td.empty::before { content: "∅"; display: block; font-size: 22px; opacity: .45; margin-bottom: 2px; }
tr.click { cursor: pointer; }
tr.click:hover td { background: var(--surface-2); }
.badge.in { color: var(--text-primary); border-color: var(--series-2); }
.btn.is-busy { cursor: progress; }
.btn.is-done:not(.primary):not(.danger):not(.solid) { border-color: var(--st-on-fg); color: var(--st-on-fg); }
kbd { font: 600 12px ui-monospace, Consolas, monospace; border: 1px solid var(--line-strong); border-bottom-width: 2px; border-radius: 4px; padding: 1px 6px; background: var(--surface-2); }
input[type=file] { font: 13px var(--sans); color: var(--text-secondary); }
dialog.side form { display: flex; flex-direction: column; min-height: 100%; }
@media (max-width: 1500px) { header { column-gap: 14px; } header .user { display: none; } }
@media (max-width: 760px) {
  header, nav.bar, main { padding-left: 16px; padding-right: 16px; }
  .kpis { grid-template-columns: 1fr 1fr; } .tile.hero { grid-column: 1 / -1; }
  .tile .value { font-size: 28px; } .tile.hero .value { font-size: 36px; }
  .sticky { position: static; }
  .savebar { margin: 0 -16px -48px; padding: 12px 16px; }
}
@media (max-width: 640px) {
  table.cards, table.cards tbody { display: block; width: 100%; }
  table.cards td[data-l=""]::before { content: none; }
  table.cards td.empty { text-align: center; justify-content: center; }
  main.dash .tablewrap { max-height: none; border: 0; }
}
@media (pointer: coarse) { .btn { min-height: 40px; } .btn.small { min-height: 34px; } .in { min-height: 40px; } }
body.wall header .search, body.wall .subbar, body.wall footer, body.wall .filters, body.wall .savebar { display: none !important; }
body.wall main { zoom: 1.2; }
/* fields in a filter row without .in still look like fields */
.filters select, .filters input:not([type=checkbox]):not([type=radio]) { height: var(--ctl-h); padding: 0 10px; border: 1px solid var(--line-strong); border-radius: var(--r);
  background: var(--surface-1); color: var(--text-primary); font: 13.5px var(--sans); }
@media (max-width: 640px) { header.open .user.in-menu { display: block !important; order: 12; flex-basis: 100%; } }

/* ==========================================================================================================
   14. Live toggles (1.1.1): a Live switch anywhere on a page (not only in the top bar) and what it changes
   ========================================================================================================== */
/* the switch: grey dot when off; green pulsing dot and the pressed look when on */
.btn.live-btn { display: inline-flex; align-items: center; gap: 7px; }
.btn.live-btn::before { content: ""; flex: 0 0 9px; width: 9px; height: 9px; border-radius: 50%; background: var(--mark-none); }
.btn.live-btn[aria-pressed="true"]::before { background: var(--mark-on); box-shadow: 0 0 0 3px rgba(47,191,91,.25); animation: dgr-livepulse 2s infinite; }
main .btn.live-btn[aria-pressed="true"] { border-color: var(--st-on-fg); background: var(--st-on-bg); color: var(--st-on-fg); }
/* the line that says it is live: "live: 14 links every 5 s · 12:31:05" */
.live-note { display: inline-flex; align-items: center; gap: 6px; color: var(--st-on-fg); font-weight: 600; }
.live-note::before { content: ""; width: 8px; height: 8px; border-radius: 50%; background: var(--mark-on); animation: dgr-blink 1.4s ease-in-out infinite; }
/* lines with a live reading are drawn full strength; a number that just changed flashes once */
.mp-edge.live { opacity: 1; }
.mp-chg { animation: dgr-chg 1.2s var(--ease-out); }
@keyframes dgr-chg { 0%, 30% { fill: var(--series-2); } }

/* ==========================================================================================================
   15. Clickable links and requirements in diagrams (1.2.1)
   ========================================================================================================== */
/* a wide invisible line on top of each link takes the click; the link brightens on hover */
.mp-hit { fill: none; stroke: transparent; stroke-width: 14px; pointer-events: stroke; cursor: pointer; }
.mp-link:hover .mp-edge, .mp-link:focus .mp-edge, .mp-link:hover .mp-extra { opacity: 1; stroke: var(--series-2); }
.mp-link:focus { outline: none; }
/* a link that cannot carry what it must: amber, dashed, its label says by how much */
.mp-edge.short, .mp-extra.short { stroke: var(--mark-wait); stroke-dasharray: 10 4; opacity: 1; }   /* amber in both themes: brown is the normal line colour in light mode */
.mp-need { font-weight: 700; fill: var(--text-secondary); }
.mp-need.short { fill: var(--warning); }
/* (1.2.2) the link type in the middle of a link: radio = circle with waves, cable = a small plug */
.mp-med circle, .mp-med rect { fill: var(--surface-1); stroke: var(--line-strong); stroke-width: 1.2; }
.mp-med path { fill: none; stroke: var(--text-secondary); stroke-width: 1.4; stroke-linecap: round; }
.mp-med .dot { fill: var(--text-secondary); stroke: none; }
.mp-med.radio circle:not(.dot) { stroke: var(--series-2); }
.mp-med { pointer-events: none; }
/* folding a group (site) into one tile: click its name; the folded tile is dashed and says how many it holds */
.mp-fold { cursor: pointer; }
.mp-fold:hover text, .mp-fold:focus text { fill: var(--text-primary); text-decoration: underline; }
.mp-fold:focus { outline: none; }
.mp-node.folded rect.box { stroke-dasharray: 5 3; stroke-width: 1.6; fill: var(--surface-2); }
/* the same clickable links and requirement colour on a plan */
.pl-lk { cursor: pointer; }
.pl-lk:hover .pl-link { stroke: var(--series-2); }
.pl-link.short { stroke: var(--mark-wait); stroke-dasharray: 10 4; }
/* (1.2.2) the add button at the end of a list of groups */
.plan-sites .pl-siteadd { justify-content: center; border-style: dashed; color: var(--text-secondary); background: transparent; }
.plan-sites .pl-siteadd:hover { color: var(--text-primary); border-color: var(--line-strong); }

/* ==========================================================================================================
   16. Unreachable (1.2.3): a device that is down only because something on its path is down.
       Neutral and dashed – not a second alarm – and always says what it is behind.
   ========================================================================================================== */
.st.unreach { background: transparent; color: var(--text-secondary); box-shadow: inset 0 0 0 1px var(--line-strong); } .st.unreach::before { content: "⇣"; }
.dots i.unreach { background: transparent; box-shadow: inset 0 0 0 1.5px var(--mark-off); }
rect.stripe.unreach { fill: var(--mark-none); }
.mp-node.unreach rect.box, .mp-node:has(rect.stripe.unreach) rect.box { stroke-dasharray: 4 3; opacity: .8; }
.tile.k-unreach { border-top-color: var(--line-strong); border-top-style: dashed; }

/* ==========================================================================================================
   17. Statistics pages (1.2.4): ranking bars with a target line, values with decimals, print as a report
   ========================================================================================================== */
.hbars .t { position: relative; }
.hbars .t .tg { position: absolute; top: -3px; bottom: -3px; width: 2px; margin-left: -1px; background: var(--text-primary); opacity: .55; border-radius: 1px; }
.hbars .t i.low { background: var(--warning); }
.hbars .num { text-align: right; font-variant-numeric: tabular-nums; white-space: nowrap; }
.hbars .r small { font-size: 11px; }
.st-av .r, #stBusy .hbars .r { grid-template-columns: minmax(90px, 32%) 1fr 74px; }
@media print {
  body[data-view="stats"] main:not(#view-stats) { display: none !important; }
  body[data-view="stats"] #view-stats { display: grid !important; grid-template-columns: 1fr 1fr; padding: 0; }
  body[data-view="stats"] #view-stats .seg, body[data-view="stats"] #view-stats .cardhead .btn { display: none !important; }
}

/* ==========================================================================================================
   18. Motion that tells (1.2.5): a row that changed flashes once; a failure runs down the lines behind it
   ========================================================================================================== */
.devrow.chg-bad { animation: dgr-chg-bad 1.8s var(--ease-out); }
.devrow.chg-good { animation: dgr-chg-good 1.8s var(--ease-out); }
@keyframes dgr-chg-bad { 0%, 25% { background: color-mix(in srgb, var(--critical) 22%, transparent); } }
@keyframes dgr-chg-good { 0%, 25% { background: color-mix(in srgb, var(--mark-on) 22%, transparent); } }
/* the red dashes run from the failed device outwards along every link behind it */
.mp-ripple { fill: none; stroke: var(--critical); stroke-width: 2.5px; stroke-linecap: round; stroke-dasharray: 3 13; opacity: .85; animation: dgr-flow 1.1s linear infinite; pointer-events: none; }
.mp-edge.unreach { stroke: var(--line-strong); stroke-dasharray: 3 5; opacity: .7; }
.wallok { animation: dgr-rise .6s var(--ease-out) both; }
.rl-msg { white-space: normal; font-family: ui-monospace, Consolas, monospace; font-size: 12.5px; }

/* ==========================================================================================================
   16. Taken from Dinokeng ANPR, the reference program (1.3.0, from ANPR's UI inventory 2.6.3-test1)
       Every DGR program follows ANPR's look and feel; these are ANPR parts the other programs need too.
   ========================================================================================================== */
/* --- on/off switch: a checkbox that looks like a switch (ANPR: boom gate per vehicle). <label class="tgl"><input type="checkbox"> Text</label> */
.tgl { display: inline-flex; align-items: center; gap: 8px; cursor: pointer; font-weight: 600; }
.tgl input { appearance: none; -webkit-appearance: none; flex: none; width: 38px; height: 22px; margin: 0; border-radius: 11px; position: relative; cursor: pointer;
  background: var(--mark-none); border: 1px solid var(--line-strong); transition: background .15s, border-color .15s; }
.tgl input::after { content: ""; position: absolute; top: 2px; left: 2px; width: 16px; height: 16px; border-radius: 50%; background: var(--surface-1); box-shadow: 0 1px 2px rgba(0,0,0,.35); transition: left .15s; }
.tgl input:checked { background: var(--st-on-fg); border-color: var(--st-on-fg); }
.tgl input:checked::after { left: 18px; }
.tgl input:disabled { opacity: .5; cursor: not-allowed; }
.tgl input:focus-visible { outline: 2px solid var(--focus-ring); outline-offset: 2px; }
.tgl .sub { font-size: 12px; font-weight: 400; color: var(--text-secondary); }
.field label.tgl { display: inline-flex; margin: 0 0 4px; }

/* --- the ? next to a card title: opens the manual at the right place; the place flashes once (DGR.flash) */
.hlp { margin-left: 8px; border: 1px solid var(--line-strong); background: none; color: var(--text-muted); border-radius: 50%; width: 20px; height: 20px; padding: 0;
  font: 700 12px/18px var(--sans); cursor: pointer; vertical-align: 3px; text-transform: none; letter-spacing: 0; }
.hlp:hover { color: var(--text-primary); border-color: var(--text-primary); }
.dgr-flash { animation: dgr-flash 2.4s ease-out; border-radius: 4px; }
@keyframes dgr-flash { 0%, 40% { background: color-mix(in srgb, var(--series-1) 30%, transparent); } }
body.wall .hlp { display: none !important; }

/* --- something that needs a look: a ring pulses out twice (ANPR: alarm dot, gate view alert) – DGR.pulse(el) */
.dgr-pulse { animation: dgr-pulse 1.1s ease-out 2; }
@keyframes dgr-pulse { 0% { box-shadow: 0 0 0 0 color-mix(in srgb, var(--series-1) 70%, transparent); } 100% { box-shadow: 0 0 0 12px transparent; } }

/* --- a small state dot after a tab name (ANPR: Helpers): <span class="tabdot warn"></span>; .busy pulses */
.tabdot { display: inline-block; width: 8px; height: 8px; border-radius: 50%; margin-left: 4px; vertical-align: 1px; background: var(--mark-on); }
.tabdot.err { background: var(--critical); } .tabdot.warn { background: var(--warning); } .tabdot.off { background: var(--mark-none); }
.tabdot.busy { animation: dgr-blink 1.2s ease-in-out infinite; }

/* --- progress bars: one look for uploads, updates, exports and recalculations – 10 px high, at most 640 px wide.
       <div class="im-prog"><div class="im-bar"><i style="width:40%"></i></div><span>40 %</span></div>; .ind = length unknown (DGR.progress) */
.im-prog { display: flex; align-items: center; gap: 10px; margin: 0 0 12px; font-size: 13px; }
.im-bar { box-sizing: border-box; }
.im-prog .im-bar { flex: 1 1 auto; width: auto; max-width: 640px; min-width: 120px; }
.im-prog > span { flex: 0 1 auto; white-space: nowrap; overflow: hidden; text-overflow: ellipsis; color: var(--text-secondary); }
.im-bar.ind i { width: 35% !important; animation: dgr-indmove 1.2s ease-in-out infinite; }
.im-bar.done i { animation: none; background-image: none; }
@keyframes dgr-indmove { from { margin-left: -35%; } to { margin-left: 100%; } }

/* --- the undo countdown: a thin bar runs down over the 10 s the Undo button stays (DGR.toast with undo adds it) */
.toastmsg .undobar { position: absolute; left: 0; bottom: 0; height: 3px; width: 100%; background: currentColor; opacity: .5; transform-origin: left; animation: dgr-undobar 10s linear forwards; }
@keyframes dgr-undobar { to { transform: scaleX(0); } }

/* --- links in the text of pages and dialogs: the text colour, a soft underline */
:where(main, dialog, .tip) a:where(:not(.btn)) { color: inherit; text-decoration: underline; text-decoration-color: var(--line-strong); text-underline-offset: 3px; }
:where(main, dialog, .tip) a:where(:not(.btn)):hover { text-decoration-color: currentColor; }

/* --- dialog buttons: Cancel / Close on the left, the action on the right (primary or danger), whatever order a program passes them in */
dialog .df .btn:not(.primary):not(.danger) { order: 0; }
dialog .df .btn.danger { order: 1; }
dialog .df .btn.primary { order: 2; }

/* --- pills (1.3.0): unknown state, and maintenance as a pill (from NetWatch) */
.badge.unknown { background: var(--surface-2); color: var(--text-muted); border-color: var(--border); }
.badge.maint { background: var(--st-maint-bg); border-color: var(--st-maint-bg); color: var(--st-maint-fg); }
.badge.maint::before { content: "↻ "; }

/* --- sign in: the Dinokeng Hub button first, the program's own form second */
.hubbtn { display: block; text-align: center; padding: 12px 24px; border-radius: 8px; background: var(--accent); color: var(--on-accent); font: 700 16px/24px var(--sans); text-decoration: none; margin: 4px 0 0; }
.hubbtn:hover { filter: brightness(1.12); }
.hubbtn:focus-visible { outline: 2px solid var(--focus-ring); outline-offset: 2px; }
.or { display: flex; align-items: center; gap: 10px; margin: 18px 0 2px; font-size: 13px; color: var(--text-secondary); }
.or::before, .or::after { content: ""; flex: 1; border-top: 1px solid var(--line-strong); opacity: .5; }

/* --- tables on phones: every row its own small card (ANPR) */
@media (max-width: 640px) {
  table.cards tr { border: 1px solid var(--border); border-radius: 6px; margin: 0 0 8px; padding: 4px 0; }
  table.cards tbody { display: block; padding: 8px; }
  table.cards td { padding: 4px 10px; text-align: right; }
  table.cards td::before { text-align: left; }
}

/* --- ANPR on small screens: a slimmer top bar that scrolls away, tighter tabs */
@media (max-width: 760px) {
  header .logo { height: 34px; }
  .sticky { position: static; }
  .tabs button { padding: 12px 7px 9px; font-size: 13.5px; letter-spacing: .02em; }
}
@media (max-width: 640px) { header .logo { height: 28px; } .tabs button { padding: 8px 10px; } }

/* --- phone pages (ANPR's /m ranger app): <body class="phone"> – bigger touch targets, one narrow column */
body.phone { background: var(--surface-2); font: 15px/1.5 var(--sans); min-height: 100vh; }
body.phone header { position: sticky; top: 0; z-index: 5; gap: 10px; padding: 10px 16px; padding-top: max(10px, env(safe-area-inset-top)); }
body.phone header .logo { height: 30px; }
body.phone header h1 { font: 700 17px/22px var(--display); }
body.phone header .sub { font-size: 11.5px; line-height: 15px; white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }
body.phone main { padding: 14px 12px calc(28px + env(safe-area-inset-bottom)); max-width: 640px; margin: 0 auto; gap: 12px; }
body.phone .card { padding: 14px 16px; }
body.phone .card h2 { font: 700 17px/22px var(--display); margin: 0 0 8px; }
body.phone .card h3 { font: 700 14px/18px var(--display); margin: 16px 0 6px; color: var(--text-secondary); }
body.phone .btn { min-height: 40px; padding: 8px 14px; font: 700 14px/20px var(--sans); -webkit-tap-highlight-color: transparent; }
body.phone .btn.icon { padding: 8px 10px; min-width: 40px; }
body.phone .badge { font-size: 12.5px; padding: 2px 10px; }
body.phone .notice { padding: 10px 12px; font-size: 14px; border-radius: 0 var(--r) var(--r) 0; }
body.phone .empty { padding: 12px 0; }
body.phone .field label { font-weight: 600; font-size: 13.5px; margin-bottom: 5px; }
body.phone .seg { display: flex; } body.phone .seg button { flex: 1; min-height: 42px; font: 600 14px var(--sans); }
body.phone .chk { gap: 10px; align-items: flex-start; margin-top: 12px; padding-top: 0; font-size: 14px; }
body.phone .btn.busy { opacity: .7; pointer-events: none; }
