/* Aerqor design system.
   One dark operations-console theme, shared by every page. */

:root {
  /* Tells the browser which way round its OWN chrome should be drawn: the
     dropdown a <select> opens, the calendar a date input opens, the spinners on
     a number input, checkbox and range fills, scrollbars, and the text caret.
     None of those are ours to style — they are rendered by the platform, and
     without this they are drawn light-on-light over a dark console.

     It was never declared, so all 436 native controls in the product (229
     <select>, 72 date, 66 number, 61 checkbox, 6 file, 2 range) carried light
     chrome: the date pickers on the Workbench timeframe, the person profile's
     start/end, and every filter dropdown. Visible as a pale calendar glyph
     sitting in a dark field. One line per theme fixes all of them, and it costs
     nothing because the browser was already drawing those widgets. */
  color-scheme: dark;

  --bg: #080b10;
  --bg-raise: #0c1018;
  --panel: #11161f;
  --panel-2: #151b25;
  --panel-hi: #1a212c;
  --line: #252e3d;
  --line-soft: #1b222d;

  --ink: #e8eefb;
  --ink-2: #b6c6de;
  --muted: #7d90ab;
  /* Lightened from #58687f, which measured 2.62:1 on --panel-hi — below even
     the 3:1 large-text floor, let alone 4.5:1. `--faint` carries timestamps,
     "entered by" lines and helper text: real content, not decoration, and it
     was failing WCAG on every raised surface in the product. #8191a8 clears
     4.5:1 on the lightest panel it appears on and stays in the same cool
     slate family, so nothing else in the palette has to move. */
  --faint: #8191a8;

  --accent: #2f9dff;
  --accent-hi: #5fc2ff;
  /* Ink for text sitting ON the accent fill. It flips with the theme because
     the accent does: dark ink measures 6.74:1 on the night accent (#2f9dff) and
     only 3.52:1 on the day one (#1566d1), which put every primary button in the
     product — "Save", "Run mission", "Create alert" — under the floor in day
     mode. White is 5.45:1 there and 2.84:1 at night, so neither ink works for
     both and the choice has to be a token rather than a constant. */
  --on-accent: #061019;
  --accent-dim: rgba(47, 157, 255, .15);
  --accent-line: rgba(47, 157, 255, .40);
  --cyan: #4dd8ff;
  --chrome: #c9d6e4;

  --ok: #3fbf87;
  --ok-dim: rgba(63, 191, 135, .14);
  --warn: #e0a83c;
  --warn-dim: rgba(224, 168, 60, .14);
  --danger: #f2695f;
  --danger-dim: rgba(242, 105, 95, .14);

  /* A status colour has two jobs, and they are not the same colour.
     As a FILL — a dot, a bar, a solid button — it wants to be vivid. As INK on
     its own 14% wash it has to be readable, and on the day theme the vivid
     version is not: #cf3f34 on the danger wash measured 3.94:1 and #b07414 on
     the warn wash 3.34:1, both under the 4.5 floor. Conflating the two is why
     every tinted badge in day mode was failing.
     On the night theme the wash is dark, so ink and fill legitimately coincide. */
  --ok-ink: var(--ok);
  --warn-ink: var(--warn);
  --danger-ink: var(--danger);

  /* The third job, and the one that was missing. A status colour is a FILL, an
     INK and a LINE, and twelve places across the stylesheet and seven pages
     wrote the line by hand as `rgba(242,105,95,.45)` and friends — the night
     hue, baked in, at five different alphas. On the day theme the hue is a
     different colour entirely, so every one of those borders was the wrong
     one. One alpha, two themes, one name. */
  --ok-line: rgba(63, 191, 135, .34);
  --warn-line: rgba(224, 168, 60, .34);
  --danger-line: rgba(242, 105, 95, .34);
  --violet: #a68bfa;
  --teal: #3fc7c7;

  /* ---------- data canvases ----------

     Every chart, graph and map draws on a DARK surface in both themes, and
     these four tokens are deliberately not overridden under
     [data-theme="light"]. The map already worked this way; the charts did not,
     and they were unreadable for it. `.viz` was `background: var(--bg)`, so in
     day mode it became #eef1f6 while the colours drawn into it stayed tuned
     for #070b12. Measured against the light canvas, every one of the eight
     series colours failed the 3:1 non-text minimum — 1.30:1 for the palest,
     2.88:1 for the best — while the grid lines, meant to be a whisper at
     1.19:1, came out at 14.65:1. The data vanished and the scaffolding
     shouted.

     Two ways to fix that: restate the whole palette per theme, or keep one
     canvas. One canvas wins, because a colour that encodes a category must not
     change meaning when somebody flips a theme — and the map had already
     settled the question. */
  /* Zebra striping. See `tbody tr:nth-child(even)`. */
  --row-alt: rgba(255, 255, 255, .022);
  --canvas: #070b12;
  --canvas-grid: #161f2c;        /* interior gridlines, a whisper */
  --canvas-line: #1e2937;        /* axis rules, one step up */
  --canvas-lbl: #58687f;         /* tick labels: 3.47:1 on the canvas */

  /* Selection, highlight and the drawing tools, ON a canvas. These exist
     because the map chrome used --accent and --accent-hi, which DO flip with
     the theme while the canvas underneath does not: in day mode --accent-hi
     becomes #0b52bd, and on #070b12 that measures 2.77:1 — under the 3:1
     floor. The visible symptom was polygon vertex handles and highlighted
     coordinate labels all but disappearing on the map in day mode. */
  --canvas-accent: #2f9dff;      /* 6.93:1 on the canvas */
  --canvas-accent-hi: #5fc2ff;   /* 9.99:1 */
  --canvas-accent-dim: rgba(47, 157, 255, .12);

  /* Status, on a canvas. Same hues as --ok/--warn/--danger but pinned to their
     night values, because the canvas does not change with the theme and these
     carry meaning a graph legend relies on. A "verified" edge must be the same
     green in both themes or the legend is lying to one of them. */
  --canvas-ok: #3fbf87;
  --canvas-warn: #e0a83c;
  --canvas-danger: #f2695f;

  /* ---------- categorical data colours ----------

     ONE palette. There were five, and they disagreed: series 1 was #4a9eff on
     the workbench and the dashboard but #2f9dff on the chart builder and the
     map, and positions 5 and 6 were swapped between them — so the same
     breakdown rendered on two pages gave the same category two different
     colours.

     Three constraints, all checked by test_data_colours.py rather than by eye:

       1. >= 3:1 against both the canvas and the map's lightest basemap fill
          (#121a26). The worst here is 6.05:1.
       2. No entry within dE 18 of --ok, --warn or --danger. Green, amber and
          red mean good, warning and bad everywhere else in the product; a
          palette that spends them on "Addresses" and "LPR Reads" — which is
          what the old one did — teaches people to read a meaning that is not
          there.
       3. No entry within dE 18 of the basemap's own chrome. The previous
          8th colour was #c9d6e4, which IS `.amap-place.city`: CAD calls for
          service, the busiest layer on an operational map, were drawn in
          exactly the colour of the city name labels underneath them.

     Mutually, the closest pair is dE 34.4 (azure/violet). */
  --series-1: #4aa8ff;
  --series-2: #a68bfa;
  --series-3: #2fb8a6;
  --series-4: #ffd24a;
  --series-5: #ff7ab8;
  --series-6: #b6e36a;
  --series-7: #4dd8ff;
  --series-8: #c98a5b;
  /* Added for Traffic Collisions and Premise Hazards. Chosen by measuring
     against the existing eight and the reserved status hues rather than by
     eye: magenta is dE 29.6 from its nearest peer, pale violet 22.4, and
     they are dE 41.1 apart. */
  --series-9: #e06be0;
  --series-10: #c0b0ff;

  --radius: 12px;
  --radius-sm: 8px;
  --radius-lg: 16px;
  /* Layered shadows read as depth rather than as a grey halo. */
  --shadow: 0 1px 2px rgba(0,0,0,.32), 0 8px 24px -8px rgba(0,0,0,.5);
  --shadow-lg: 0 2px 6px rgba(0,0,0,.3), 0 28px 72px -14px rgba(0,0,0,.66);
  --ring: 0 0 0 3px var(--accent-dim);

  --sans: "Inter", -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, Helvetica, Arial, sans-serif;
  --mono: "SF Mono", ui-monospace, SFMono-Regular, Menlo, Consolas, monospace;

  --sidebar-w: 232px;
  --topbar-h: 56px;
  --topbar-bg: rgba(8, 11, 16, .72);
  --kbd-bg: rgba(7, 11, 18, .78);
}

/* ---------- Day (light) theme ----------
   The default palette above is the night theme. A user toggles day mode from the
   top bar; the choice is stored and applied before first paint (see the inline
   boot snippet in each page head) so there is no flash. Only the design tokens
   are redefined here, so every component that reads them switches automatically. */
:root[data-theme="light"] {
  /* The other half of the pair. Day mode has to say so explicitly — the night
     value above is on `:root` and would otherwise still apply here, leaving
     dark dropdowns and a dark calendar on a white page. */
  color-scheme: light;
  /* Darker, not lighter: on a white panel a white stripe is no stripe. */
  --row-alt: rgba(16, 32, 56, .030);

  --bg: #f3f5f9;
  --bg-raise: #ffffff;
  --panel: #ffffff;
  --panel-2: #f6f8fb;
  --panel-hi: #eef2f8;
  --line: #d3dae5;
  --line-soft: #e3e8ef;

  --ink: #0f1826;
  --ink-2: #33425a;
  --muted: #566579;
  /* #7d8ba0 measured 3.46:1 on white and 3.05:1 on --bg — under the 4.5 floor
     on every light surface. The NIGHT theme's --faint was corrected for exactly
     this and the day theme was left behind, so timestamps, "entered by" lines
     and the rail's section labels were failing in day mode only. #5c6b85 clears
     4.5 on the darkest light surface (4.76 on --bg) and stays clearly lighter
     than --muted, so the two-step hierarchy between them survives. */
  --faint: #5c6b85;

  --accent: #1566d1;
  --accent-hi: #0b52bd;
  --on-accent: #ffffff;
  --accent-dim: rgba(21, 102, 209, .10);
  --accent-line: rgba(21, 102, 209, .34);
  /* Darkened 2026-10-25: #0a94b3 measured 3.17:1 on --panel-hi and #0f8a8a
     (teal, below) 3.72:1 — both under 4.5 for text. These now clear 4.5 on
     every light surface (4.73 and 4.83 worst case). */
  --cyan: #08758f;
  --chrome: #223046;

  --ok: #1a8f5f;
  --ok-dim: rgba(26, 143, 95, .12);
  --warn: #b07414;
  --warn-dim: rgba(176, 116, 20, .14);
  --danger: #cf3f34;
  --danger-dim: rgba(207, 63, 52, .12);

  /* Ink for a tinted chip, darker than the fill. Each clears 4.5:1 against its
     own wash composited over the LIGHTEST and the DARKEST light surface, so a
     badge reads the same on a panel, on the page, and on a raised row. */
  --ok-ink: #0f6341;
  --warn-ink: #87570d;
  --danger-ink: #ad2f24;
  --ok-line: rgba(26, 143, 95, .34);
  --warn-line: rgba(176, 116, 20, .34);
  --danger-line: rgba(207, 63, 52, .34);
  --violet: #6a4bcf;
  --teal: #0b7676;

  --shadow: 0 1px 2px rgba(20,30,50,.06), 0 8px 24px -10px rgba(20,30,50,.14);
  --shadow-lg: 0 2px 6px rgba(20,30,50,.08), 0 28px 72px -16px rgba(20,30,50,.24);
  --ring: 0 0 0 3px var(--accent-dim);
  --topbar-bg: rgba(255, 255, 255, .72);
  --kbd-bg: #e7ecf3;
}
/* Light-theme fixes for the few spots that hardcode dark-tuned colors. */
:root[data-theme="light"] .chip.ok { background: rgba(26,143,95,.12); border-color: #7fc6a5; color: var(--ok-ink); }
:root[data-theme="light"] .chip.warn { background: rgba(176,116,20,.14); border-color: #d6b077; color: var(--warn-ink); }
:root[data-theme="light"] .error-box { color: #a01810; }
/* `:not(.badge)` because a badge that happens to be a button is a TINTED chip,
   not a filled control: white on the badge's 14%-red wash measured 1.21:1 in day
   mode, which is the "5 integration issues" pill reading as an empty pink
   rectangle. The rule was written for the solid danger button and caught the
   chip by accident. */
:root[data-theme="light"] button.danger:not(.badge) { color: #fff; }
:root[data-theme="light"] .badge.critical { color: #fff; }
:root[data-theme="light"] ::-webkit-scrollbar-thumb { background: #c2ccda; }
:root[data-theme="light"] ::-webkit-scrollbar-thumb:hover { background: #a9b6c8; }
:root[data-theme="light"] .brand-logo { filter: none; }
/* The map is always a dark canvas so its vector features read the same in both
   themes; only the surrounding UI changes. That decision now lives in
   `--canvas`, which no theme overrides, and the charts follow the same rule —
   so this override has nothing left to say. See the data-canvas tokens above. */

* { box-sizing: border-box; }

html, body {
  margin: 0;
  padding: 0;
  height: 100%;
}

body {
  background: var(--bg);
  color: var(--ink);
  font-family: var(--sans);
  font-size: 14px;
  line-height: 1.5;
  -webkit-font-smoothing: antialiased;
}

a { color: var(--accent); text-decoration: none; }
a:hover { color: var(--accent-hi); text-decoration: underline; }

h1, h2, h3, h4 { margin: 0; font-weight: 600; letter-spacing: -.01em; }
h1 { font-size: 20px; }
h2 { font-size:18px; }
h3 { font-size: 14px; }
h4 { font-size: 13px; color: var(--ink-2); }

.mono { font-family: var(--mono); font-size: .92em; }
.muted { color: var(--muted); }
.faint { color: var(--faint); }
.small { font-size: 12px; }
.tnum { font-variant-numeric: tabular-nums; }
.hidden { display: none !important; }
.nowrap { white-space: nowrap; }
.grow { flex: 1 1 auto; }
.row { display: flex; align-items: center; gap: 8px; }
.row-wrap { display: flex; align-items: center; gap: 8px; flex-wrap: wrap; }
.col { display: flex; flex-direction: column; gap: 8px; }
.spread { display: flex; align-items: center; justify-content: space-between; gap: 12px; }

/* ---------- App shell ---------- */

.app { display: flex; min-height: 100vh; }

.app .sidebar {
  width: var(--sidebar-w);
  flex: 0 0 var(--sidebar-w);
  background: var(--bg-raise);
  border-right: 1px solid var(--line-soft);
  display: flex;
  flex-direction: column;
  position: sticky;
  top: 0;
  height: 100vh;
  overflow-y: auto;
}

.app .brand {
  display: flex; align-items: center; gap: 10px;
  padding: 5px 16px;                 /* tight, so the 45px mark still fits the 56px rule */
  border-bottom: 1px solid var(--line-soft);
  min-height: 56px;                  /* matches --topbar-h, so the rail's rule
                                        and the top bar's rule are one line */
}
.app .brand-mark-img { height: 45px; width: auto; display: block; flex: 0 0 auto; }
.app .brand-words { display: flex; flex-direction: column; line-height: 1.05; min-width: 0; }
.brand:hover { text-decoration: none; }
.brand-logo {
  display: block;
  width: 100%;
  max-width: 260px;
  height: auto;
  margin: 0 auto;
  /* The logo art is on black; a soft glow keeps it from looking pasted on. */
  filter: drop-shadow(0 0 14px rgba(47, 157, 255, .28));
}
/* 450px is wider than the 400px card on purpose; it is centred on the card and
   may overhang it, and never the viewport. */
.login-head .brand-logo { width: min(450px, calc(100vw - 32px)); max-width: none;
  margin: 0 0 6px 50%; transform: translateX(-50%); }
.app .brand-mark {
  width: 28px; height: 28px;
  flex: 0 0 28px;
  border-radius: 8px;
  background: linear-gradient(140deg, var(--accent) 0%, var(--violet) 100%);
  display: grid; place-items: center;
  color: #05080e; font-weight: 800; font-size: 14px;
  box-shadow: 0 0 0 1px rgba(255,255,255,.08) inset;
}
.app .brand-name { font-size: 15px; font-weight: 700; letter-spacing: .04em; color: var(--ink); }
.app .brand-sub { font-size: 10px; color: var(--faint); text-transform: uppercase; letter-spacing: .22em; }

.app .nav { padding: 10px 10px 18px; display: flex; flex-direction: column; gap: 2px; }
.app .nav-label {
  font-size: 11px; text-transform: uppercase; letter-spacing: .1em;
  color: var(--faint); padding: 14px 8px 6px;
}
.app .nav a {
  display: flex; align-items: center; gap: 10px;
  padding: 8px 10px; border-radius: var(--radius-sm);
  color: var(--ink-2); font-size: 13px; text-decoration: none;
  border: 1px solid transparent;
}
.app .nav a:hover { background: var(--panel); color: var(--ink); text-decoration: none; }
.app .nav a.active {
  background: var(--accent-dim);
  color: var(--accent-hi);
  border-color: var(--accent-line);
  font-weight: 550;
}
.app .nav .ico { width: 16px; text-align: center; opacity: .85; flex: 0 0 16px; }

.app .sidebar-foot {
  margin-top: auto; padding: 12px 16px;
  border-top: 1px solid var(--line-soft);
  font-size: 12px; color: var(--faint);
}

.app .main { flex: 1 1 auto; min-width: 0; display: flex; flex-direction: column; }

.app .topbar {
  height: var(--topbar-h);
  flex: 0 0 var(--topbar-h);
  border-bottom: 1px solid var(--line-soft);
  background: var(--topbar-bg);
  backdrop-filter: blur(10px);
  display: flex; align-items: center; gap: 12px;
  padding: 0 18px;
  position: sticky; top: 0; z-index: 40;
}

.app .content { padding: 18px; display: flex; flex-direction: column; gap: 14px; }

/* ---------- Panels ---------- */

.panel {
  background: var(--panel);
  border: 1px solid var(--line-soft);
  border-radius: var(--radius);
  overflow: hidden;
}
.panel-head {
  display: flex; align-items: center; justify-content: space-between; gap: 12px;
  padding: 11px 14px;
  border-bottom: 1px solid var(--line-soft);
  background: var(--panel-2);
}
.panel-head h3 { font-size: 13px; letter-spacing: .01em; }
.panel-body { padding: 14px; }
.panel-body.tight { padding: 0; }

.grid { display: grid; gap: 12px; }
.g2 { grid-template-columns: repeat(2, minmax(0, 1fr)); }
.g3 { grid-template-columns: repeat(3, minmax(0, 1fr)); }
.g4 { grid-template-columns: repeat(4, minmax(0, 1fr)); }
.g-main { grid-template-columns: minmax(0, 1fr) 340px; align-items: start; }

/* ---------- Stat tiles ---------- */

.stats { display: grid; grid-template-columns: repeat(auto-fit, minmax(150px, 1fr)); gap: 10px; }
.stat {
  background: var(--panel);
  border: 1px solid var(--line-soft);
  border-radius: var(--radius);
  /* 11px/12px, not 12px/14px: these tiles were 83px tall to show an 11px label
     and one digit, so on a 670px viewport four of them cost 100px of the fold
     before any content. The type is unchanged in size; only the leading and the
     padding came in. */
  padding: 11px 13px 12px;
}
.stat .k {
  font-size: 11px; text-transform: uppercase; letter-spacing: .08em;
  color: var(--muted); line-height: 1.3;
}
.stat .v {
  font-size: 24px; font-weight: 650; margin-top: 2px; line-height: 1.2;
  font-variant-numeric: tabular-nums; letter-spacing: -.02em;
}
.stat .v.sm { font-size: 15px; font-weight: 600; }
.stat .d { font-size: 12px; color: var(--faint); margin-top: 2px; line-height: 1.35; }

/* A count's tone.

   The operations console showed OPEN CASES 0, OPEN TASKS 0, PENDING REVIEWS 0,
   INTEGRATION ISSUES 5 — four tiles in identical ink. The only number on the
   strip that wanted attention looked exactly like the three that did not, so the
   strip did nothing a sentence would not have done better.

   Tone is set by the page from what the number MEANS, never inferred from the
   number itself: "5 integration issues" is bad and "5 open cases" is a Tuesday. */
.stat .v.tone-ok     { color: var(--ok-ink); }
.stat .v.tone-warn   { color: var(--warn-ink); }
.stat .v.tone-danger { color: var(--danger-ink); }
/* Zero is the quiet case in every tone — nothing is waiting, so nothing should
   pull the eye. Applied by the tile, so a page cannot forget it. */
.stat .v.is-zero { color: var(--faint); font-weight: 600; }
/* A zero is quiet even when its tile carries a tone: "Locked out 0" is good news. */
.stat.stat-link:has(.v.tone-danger:not(.is-zero)) { border-color: var(--danger-line); }
.stat.stat-link:has(.v.tone-warn:not(.is-zero))   { border-color: var(--warn-line); }

/* ---------- Forms ---------- */

label.f { display: block; font-size: 11px; text-transform: uppercase; letter-spacing: .07em; color: var(--muted); margin-bottom: 5px; }

input:not([type="checkbox"]):not([type="radio"]), select, textarea {
  width: 100%;
  font: inherit;
  color: var(--ink);
  background: var(--bg-raise);
  border: 1px solid var(--line);
  border-radius: var(--radius-sm);
  padding: 8px 10px;
  outline: none;
}
input::placeholder, textarea::placeholder {
  color: var(--faint);
  /* Placeholder text is a description of what the field wants, not content.
     It disappears the moment the field is focused (clicked into), so it never
     reads as a value the user has to clear before typing. */
  transition: color .12s ease;
}
input:focus::placeholder, textarea:focus::placeholder { color: transparent; }
input:focus, select:focus, textarea:focus {
  border-color: var(--accent-line);
  box-shadow: 0 0 0 3px var(--accent-dim);
}
input:read-only { background: var(--panel-2); color: var(--ink-2); }
select { appearance: none; background-image: linear-gradient(45deg, transparent 50%, var(--muted) 50%), linear-gradient(135deg, var(--muted) 50%, transparent 50%); background-position: right 14px center, right 9px center; background-size: 5px 5px, 5px 5px; background-repeat: no-repeat; padding-right: 28px; }

button, .btn {
  font: inherit; font-size: 13px; font-weight: 550;
  color: var(--on-accent);
  background: var(--accent);
  border: 1px solid transparent;
  border-radius: var(--radius-sm);
  padding: 8px 13px;
  cursor: pointer;
  white-space: nowrap;
  /* justify-content is only visible on a button that has been stretched —
     full width in a form or a modal footer. Fifty-seven of those carried
     style="justify-content:center" to compensate for its absence here. On a
     content-sized button it changes nothing. */
  display: inline-flex; align-items: center; justify-content: center; gap: 7px;
}
button:hover, .btn:hover { background: var(--accent-hi); text-decoration: none; }
button:active { transform: translateY(.5px); }
button:disabled { opacity: .42; cursor: not-allowed; transform: none; }
/* A disabled control that carries a reason says so: `cursor: help` invites the
   hover that reveals it, where `not-allowed` tells the reader only that they
   have already failed. Set by disableWithReason(). */
button:disabled.why { cursor: help; }

button.ghost, .btn.ghost {
  background: var(--panel-hi); color: var(--ink-2); border-color: var(--line);
}
button.ghost:hover, .btn.ghost:hover { background: var(--line-soft); color: var(--ink); }
button.quiet { background: transparent; color: var(--ink-2); border-color: var(--line); }
button.quiet:hover { background: var(--panel-hi); color: var(--ink); }
button.danger { background: var(--danger); color: #1a0605; }
button.danger:hover { background: #ff7f75; }
button.sm { padding: 5px 9px; font-size: 12px; }

.seg { display: inline-flex; background: var(--bg-raise); border: 1px solid var(--line); border-radius: var(--radius-sm); padding: 2px; gap: 2px; }
.seg button {
  background: transparent; color: var(--muted); border: 0;
  padding: 5px 11px; font-size: 13px; border-radius: 5px;
}
.seg button:hover { background: var(--panel-hi); color: var(--ink); }
.seg button:focus-visible { outline: 2px solid var(--accent); outline-offset: -2px; }
/* The active filter gets a RAISED CHIP, not an accent tint — deliberately
   unlike `.tabs` above, which uses the accent underline. One look per meaning.
   `aria-pressed` rides along because a filter is a toggle, not a tab; a tab
   bar uses `aria-selected`, and a screen reader is told which it is. */
.seg button.active,
.seg button[aria-pressed="true"] {
  background: var(--panel);
  color: var(--ink);
  box-shadow: 0 1px 2px rgba(0,0,0,.35), inset 0 1px 0 rgba(255,255,255,.06);
}

/* Tab bars — and the one rule that separates them from `.seg` above.
   `.tabs` CHANGES WHAT YOU ARE LOOKING AT. `.seg` NARROWS WHAT IS ALREADY SHOWN.

   They used to be the same control. An earlier pass gave `.tabs` "the same
   treatment as `.seg`, which is this app's existing answer to the same
   problem" — but it is not the same problem, and making them look alike is how
   `property.html` ended up with `#viewSeg` (five sub-views) and `#statusSeg`
   (four status filters) rendered identically, thirteen lines apart, one above
   the other. A reader cannot tell which one moves them and which one filters.
   They had also drifted apart by accident: a later consistency pass restyled
   `.seg button.active` only, so the two were already inconsistent AND still
   indistinguishable, which is the worst of both.

   So: tabs are an underline, filters are a pill group. The underline reads as
   a page-level boundary — the content below belongs to the tab above — and it
   is the convention a user arrives already knowing. The rule is testable, which
   is the point: if one option is "All", it is a filter and belongs in `.seg`.

   `background-image: none` is needed because buttons carry a gradient.
   `.active` is kept alongside `aria-selected` so pages can be converted one at
   a time without a flash of unstyled tabs in between. */
.tabs {
  display: flex; flex-wrap: wrap; gap: 2px;
  border-bottom: 1px solid var(--line);
  margin-bottom: 14px;
}
.tabs button {
  background: transparent; background-image: none; color: var(--muted);
  border: 0; border-bottom: 2px solid transparent; border-radius: 0;
  padding: 9px 14px; font-size: 13px;
  margin-bottom: -1px;   /* sit the underline ON the container's rule */
}
.tabs button:hover { background: transparent; color: var(--ink); border-bottom-color: var(--line); }
.tabs button:focus-visible { outline: 2px solid var(--accent); outline-offset: -2px; }
.tabs button.active,
.tabs button[aria-selected="true"] {
  background: transparent; color: var(--accent-hi);
  border-bottom-color: var(--accent); font-weight: 600;
}

/* Present to assistive tech, absent to everyone else. Used by `A.announce`.
   `display:none` and `visibility:hidden` would remove it from the accessibility
   tree too, which is the opposite of what this is for — hence the 1px clip. */
.sr-only {
  position: absolute; width: 1px; height: 1px;
  margin: -1px; padding: 0; overflow: hidden;
  clip: rect(0 0 0 0); clip-path: inset(50%); white-space: nowrap; border: 0;
}

/* A watcher row. Used by the Person Alerts console and by the alert panel on a
   person profile — one definition, because a row that looked different in the
   two places would read as two different kinds of thing. */
.wrow { display: flex; gap: 11px; align-items: baseline;
  padding: 10px 11px; border-bottom: 1px solid var(--line-soft); font-size: 13px; }
.wrow:last-child { border-bottom: 0; }
.wrow:nth-child(even) { background: var(--row-alt); }
.wrow .t { flex: 1; min-width: 0; }
.wrow .meta { color: var(--muted); font-size: 12px; margin-top: 3px; }

/* Charts. One component draws them all — see `drawChart` in aerqor.js. */
.chart-host { width: 100%; }
.chart {
  display: block; width: 100%;
  background: var(--canvas); border: 1px solid var(--line-soft);
  border-radius: var(--radius-sm);
}
.chart-empty {
  display: flex; align-items: center; justify-content: center;
  min-height: 160px; background: var(--canvas); border: 1px solid var(--line-soft);
  border-radius: var(--radius-sm); color: var(--canvas-lbl); font-size: 13px;
}
.chart-legend { display: flex; flex-wrap: wrap; gap: 12px; margin-top: 9px;
  font-size: 12px; color: var(--muted); }
.chart-legend span { display: inline-flex; align-items: center; gap: 6px; }
/* No colour literal here — the swatch is painted from the shared palette by
   the renderer, so a legend can never name a colour the chart did not draw. */
.chart-legend i { width: 10px; height: 10px; border-radius: 2px; display: inline-block; }

/* ---------- Chips / tags / badges ---------- */

.chips { display: flex; flex-wrap: wrap; gap: 6px; }
.chip {
  display: inline-flex; align-items: center; gap: 6px;
  background: var(--panel-hi);
  border: 1px solid var(--line);
  color: var(--ink-2);
  border-radius: 999px;
  padding: 4px 10px;
  font-size: 12px;
  cursor: pointer;
  font-family: inherit;
}
.chip:hover { border-color: var(--accent-line); color: var(--ink); background: var(--accent-dim); }
.chip .n { color: var(--faint); font-variant-numeric: tabular-nums; }
.chip.on { background: var(--accent-dim); border-color: var(--accent-line); color: var(--accent-hi); }
.chip.ok { background: var(--ok-dim); border-color: var(--ok-line); color: var(--ok-ink); }
.chip.warn { background: var(--warn-dim); border-color: var(--warn-line); color: var(--warn-ink); }
.chip .x { color: var(--faint); font-weight: 700; }

.badge {
  display: inline-flex; align-items: center; gap: 5px;
  font-size: 11px; font-weight: 600; letter-spacing: .03em;
  padding: 2px 8px; border-radius: 999px;
  background: var(--panel-hi); color: var(--ink-2); border: 1px solid var(--line);
}
.badge.ok { background: var(--ok-dim); color: var(--ok-ink); border-color: rgba(63,191,135,.3); }
.badge.warn { background: var(--warn-dim); color: var(--warn-ink); border-color: rgba(224,168,60,.3); }
.badge.danger { background: var(--danger-dim); color: var(--danger-ink); border-color: var(--danger-line); }
.badge.info { background: var(--accent-dim); color: var(--accent-hi); border-color: var(--accent-line); }

.dot { width: 7px; height: 7px; border-radius: 50%; background: var(--muted); flex: 0 0 7px; }
.dot.ok { background: var(--ok); box-shadow: 0 0 0 3px var(--ok-dim); }
.dot.warn { background: var(--warn); box-shadow: 0 0 0 3px var(--warn-dim); }
.dot.danger { background: var(--danger); box-shadow: 0 0 0 3px var(--danger-dim); }

/* ---------- Tables ---------- */

.table-wrap { overflow: auto; max-height: 60vh; }
table { width: 100%; border-collapse: separate; border-spacing: 0; font-size: 13px; }
thead th {
  position: sticky; top: 0; z-index: 2;
  background: var(--panel-2);
  color: var(--muted);
  font-size: 11px; font-weight: 600; text-transform: uppercase; letter-spacing: .07em;
  text-align: left; padding: 9px 12px;
  border-bottom: 1px solid var(--line);
  white-space: nowrap;
}
tbody td {
  padding: 9px 12px;
  border-bottom: 1px solid var(--line-soft);
  white-space: nowrap;
  max-width: 340px; overflow: hidden; text-overflow: ellipsis;
  font-variant-numeric: tabular-nums;
}
tbody tr { cursor: pointer; }
tbody tr:hover td { background: var(--panel-hi); }
tbody tr.sel td { background: var(--accent-dim); }
tbody tr:last-child td { border-bottom: 0; }
.group-head {
  padding: 9px 12px; background: var(--panel-2);
  border-top: 1px solid var(--line); border-bottom: 1px solid var(--line);
  font-size: 12px; font-weight: 600; color: var(--ink-2);
}

/* ---------- Detail / code ---------- */

pre.code {
  margin: 0;
  background: var(--bg);
  border: 1px solid var(--line-soft);
  border-radius: var(--radius-sm);
  padding: 11px;
  font-family: var(--mono); font-size: 12px; line-height: 1.6;
  color: var(--ink-2);
  white-space: pre-wrap; word-break: break-word;
  max-height: 340px; overflow: auto;
}

.kv { display: grid; grid-template-columns: minmax(90px, 38%) 1fr; gap: 1px; background: var(--line-soft); border: 1px solid var(--line-soft); border-radius: var(--radius-sm); overflow: hidden; }
.kv dt { background: var(--panel-2); padding: 7px 10px; font-size: 12px; color: var(--muted); margin: 0; overflow: hidden; text-overflow: ellipsis; }
.kv dd { background: var(--panel); padding: 7px 10px; font-size: 13px; margin: 0; overflow-wrap: anywhere; font-variant-numeric: tabular-nums; }

/* ---------- States ---------- */

.empty { padding: 34px 20px; text-align: center; color: var(--muted); }
.empty .big { font-size: 24px; opacity: .5; margin-bottom: 8px; }
.empty h4 { color: var(--ink-2); margin-bottom: 4px; }
.error-box {
  background: var(--danger-dim); border: 1px solid var(--danger-line);
  color: #ffb4ad; border-radius: var(--radius-sm); padding: 10px 12px; font-size: 13px;
}
.note {
  background: var(--accent-dim); border: 1px solid var(--accent-line);
  color: var(--ink-2); border-radius: var(--radius-sm); padding: 10px 12px; font-size: 13px;
}
.note.warn { background: var(--warn-dim); border-color: rgba(224,168,60,.3); }

.skeleton { background: linear-gradient(90deg, var(--panel-2) 25%, var(--panel-hi) 37%, var(--panel-2) 63%); background-size: 400% 100%; animation: sk 1.3s ease infinite; border-radius: 5px; height: 12px; }
@keyframes sk { 0% { background-position: 100% 50%; } 100% { background-position: 0 50%; } }

.spinner {
  width: 14px; height: 14px; border-radius: 50%;
  border: 2px solid var(--line); border-top-color: var(--accent);
  animation: spin .7s linear infinite; display: inline-block;
}
@keyframes spin { to { transform: rotate(360deg); } }

/* ---------- Toasts ---------- */

.toasts { position: fixed; right: 16px; bottom: 16px; z-index: 9999; display: flex; flex-direction: column; gap: 8px; max-width: 360px; }
.toast {
  background: var(--panel-hi); border: 1px solid var(--line);
  border-left: 3px solid var(--accent);
  border-radius: var(--radius-sm); padding: 10px 13px;
  box-shadow: var(--shadow-lg); font-size: 13px; color: var(--ink);
  animation: slidein .18s ease;
}
.toast.ok { border-left-color: var(--ok); }
.toast.err { border-left-color: var(--danger); }
.toast.warn { border-left-color: var(--warn); }
@keyframes slidein { from { opacity: 0; transform: translateX(14px); } to { opacity: 1; transform: none; } }

/* ---------- Login ---------- */

.login-wrap { min-height: 100vh; display: grid; place-items: center; padding: 24px; }
.login-card { width: 100%; max-width: 400px; }
.login-head { text-align: center; margin-bottom: 22px; }
.login-head .brand-mark { width: 44px; height: 44px; flex-basis: 44px; font-size: 20px; border-radius: 12px; margin: 0 auto 12px; }

/* ---------- Charts / map ---------- */

/* Every data canvas, one rule. Five pages each declared their own — .viz,
   .nexus, .gnet, .netviz, .net — identical but for the height, and all five
   wrote `background: var(--bg)`, which is what put the charts on a white
   surface in day mode. Height stays per page; the surface does not. */
.canvas, .viz { width: 100%; display: block; background: var(--canvas);
  border-radius: var(--radius-sm); border: 1px solid var(--line-soft); }
.viz { height: 380px; }
.viz-legend { display: flex; gap: 14px; flex-wrap: wrap; font-size: 12px; color: var(--muted); padding-top: 8px; }
.viz-legend i { width: 9px; height: 9px; border-radius: 2px; display: inline-block; margin-right: 5px; }

/* ---------- Drawer ---------- */

.drawer-mask { position: fixed; inset: 0; background: rgba(4, 7, 12, .58); z-index: 60; }
.drawer {
  position: fixed; right: 0; top: 0; bottom: 0; width: min(560px, 94vw);
  background: var(--panel); border-left: 1px solid var(--line);
  z-index: 61; display: flex; flex-direction: column;
  box-shadow: var(--shadow-lg);
  animation: drawerin .2s ease;
}
@keyframes drawerin { from { transform: translateX(24px); opacity: .4; } to { transform: none; opacity: 1; } }
.drawer-head { padding: 14px 16px; border-bottom: 1px solid var(--line-soft); background: var(--panel-2); }
.drawer-body { padding: 16px; overflow-y: auto; flex: 1; display: flex; flex-direction: column; gap: 14px; }
/* Flex items shrink by default. In a scrolling column that squashes tall
   content — a long key/value grid collapsed to a few pixels instead of
   extending the scroll. Drawer content should scroll, never compress. */
.drawer-body > * { flex-shrink: 0; }

/* ---------- Modal ---------- */
/* A shared modal, because the alternative is native prompt()/confirm(), which
   block the whole tab — they stop timers, stop fetches, and stop the automation
   extension from injecting script, so a page waiting on one looks hung rather
   than busy. Two pages had drifted back to the native calls. */

.modal-mask {
  position: fixed; inset: 0; background: rgba(4, 7, 12, .62);
  backdrop-filter: blur(2px);
  z-index: 70; display: grid; place-items: center; padding: 20px;
  animation: maskin .14s ease;
}
@keyframes maskin { from { opacity: 0; } to { opacity: 1; } }

.modal {
  width: min(560px, 96vw); max-height: 88vh;
  background: var(--panel); border: 1px solid var(--line);
  border-radius: var(--radius-lg); box-shadow: var(--shadow-lg);
  display: flex; flex-direction: column; overflow: hidden;
  animation: modalin .18s cubic-bezier(.2, .8, .3, 1);
}
.modal.sm { width: min(420px, 96vw); }
.modal.lg { width: min(820px, 96vw); }
@keyframes modalin { from { opacity: 0; transform: translateY(10px) scale(.985); } to { opacity: 1; transform: none; } }

.modal-head {
  padding: 14px 16px; border-bottom: 1px solid var(--line-soft);
  background: var(--panel-2); display: flex; align-items: flex-start; gap: 12px;
}
.modal-head h3 { font-size: 14px; margin: 0; letter-spacing: -.01em; }
.modal-head p { margin: 4px 0 0; font-size: 13px; color: var(--muted); }
.modal-head .x {
  margin-left: auto; background: transparent; border: 0; color: var(--muted);
  font-size:18px; line-height: 1; padding: 3px 6px; border-radius: var(--radius-sm); cursor: pointer;
}
.modal-head .x:hover { background: var(--panel-hi); color: var(--ink); }

.modal-body { padding: 16px; overflow-y: auto; display: flex; flex-direction: column; gap: 13px; }
/* Same reason as .drawer-body: a tall child in a scrolling flex column gets
   squashed instead of scrolling. */
.modal-body > * { flex-shrink: 0; }

.modal-foot {
  padding: 12px 16px; border-top: 1px solid var(--line-soft); background: var(--panel-2);
  display: flex; gap: 8px; justify-content: flex-end; align-items: center;
}
.modal-foot .spacer { margin-right: auto; }

/* Offline reference basemap layers (no external tiles). */
.amap-land { fill: #121a26; stroke: #223349; stroke-width: .8; }
.amap-water { fill: #0e1a2b; stroke: #16283f; stroke-width: .8; }
.amap-water-line { fill: none; stroke: #1b3350; stroke-width: 1.4; }
.amap-municipal { fill: #1a2536; fill-opacity: .55; stroke: #2c405e; stroke-width: .7; }
.amap-boundary { fill: none; stroke: #4a6a9c; stroke-width: 1.6; stroke-dasharray: 6 4; opacity: .85; }
.amap-road { fill: none; stroke-linecap: round; stroke-linejoin: round; }
.amap-road.interstate { stroke: #55709a; stroke-width: 2.2; }
.amap-road.us { stroke: #3d5578; stroke-width: 1.6; }
.amap-road.highway { stroke: #2b3c54; stroke-width: 1.2; }
.amap-place-dot { fill: #7d90ab; }
.amap-place { fill: #8fa2bd; font-size: 10px; font-family: var(--sans); pointer-events: none; paint-order: stroke; stroke: #070b12; stroke-width: 2.4px; }
.amap-place.city { fill: #c9d6e4; font-size: 11px; font-weight: 600; }

/* Inbox / unread badge on a nav item. */
.app .nav a { position: relative; }
.nav-badge {
  margin-left: auto; min-width: 18px; height: 18px; padding: 0 5px;
  background: var(--danger); color: #1a0605; border-radius: 9px;
  font-size: 11px; font-weight: 700; display: inline-flex; align-items: center; justify-content: center;
  font-variant-numeric: tabular-nums;
}

/* ---------- Universal top search bar ---------- */
.top-search {
  display: flex; align-items: center; gap: 8px; flex: 1;
  max-width: 480px; margin: 0 18px; padding: 0 10px; height: 34px;
  background: var(--bg-raise); border: 1px solid var(--line-soft); border-radius: 9px;
  cursor: text;
}

.top-search:focus-within { border-color: var(--accent-line); box-shadow: var(--ring); }
.top-search .ts-ico { color: var(--faint); font-size: 14px; flex: 0 0 auto; }
.top-search input {
  flex: 1; min-width: 0; height: 100%; border: 0; background: transparent; padding: 0;
  color: var(--ink); font-size: 13px;
}
.top-search input:focus { outline: none; box-shadow: none; }
.top-search .kbd { flex: 0 0 auto; }
@media (max-width: 760px) {
  .top-search { max-width: none; margin: 0 8px; }
  .top-search .kbd { display: none; }
}

/* ---------- Command palette ---------- */

.pal-list { max-height: 52vh; overflow-y: auto; display: flex; flex-direction: column; gap: 2px; margin: 0 -4px; padding: 0 4px; }
/* A heading per section — Recent, Go to, Actions, Records — so one ranked list
   does not make a page and a record look like the same kind of answer. Drawn
   only when the group changes, so a section with nothing in it never appears.
   `sticky` keeps the heading of a long section visible while it scrolls. */
.pal-group {
  position: sticky; top: 0; z-index: 1;
  padding: 10px 10px 4px; margin-top: 2px;
  font-size: 10px; font-weight: 600; text-transform: uppercase; letter-spacing: .09em;
  color: var(--faint); background: var(--panel);
}
.pal-group:first-child { margin-top: 0; padding-top: 4px; }
.pal-row {
  display: flex; align-items: center; gap: 10px; width: 100%; text-align: left;
  background: transparent; border: 1px solid transparent; color: var(--ink-2);
  padding: 8px 10px; border-radius: var(--radius-sm); font-size: 13px; cursor: pointer;
  background-image: none;
}
.pal-row:hover { background: transparent; }
.pal-row.on { background: var(--accent-dim); border-color: var(--accent-line); color: var(--ink); }
.pal-row .pal-ico { width: 18px; text-align: center; color: var(--muted); flex: 0 0 18px; }
.pal-row.on .pal-ico { color: var(--accent-hi); }
.pal-row .pal-label { flex: 1; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.pal-row .pal-hint {
  font-size: 11px; color: var(--faint); text-transform: uppercase; letter-spacing: .06em;
  flex: 0 0 auto; max-width: 42%; overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
}
.pal-row.on .pal-hint { color: var(--ink-2); }

.kbd {
  display: inline-block; font-family: var(--mono); font-size: 11px; line-height: 1;
  padding: 3px 5px; border: 1px solid var(--line); border-bottom-width: 2px;
  border-radius: 4px; background: var(--panel-2); color: var(--muted);
}

.pal-open {
  display: flex; align-items: center; gap: 9px; width: calc(100% - 20px);
  margin: 0 10px 10px; padding: 7px 9px; font-size: 13px; text-align: left;
  background: var(--bg-raise); background-image: none; border: 1px solid var(--line-soft);
  border-radius: var(--radius-sm); color: var(--muted); cursor: pointer;
}
.pal-open:hover { background: var(--panel-2); border-color: var(--accent-line); color: var(--ink-2); }
.pal-open .ico { color: var(--faint); font-size: 13px; }
.pal-open .kbd { margin-left: auto; }

/* ---------- Loading skeletons ---------- */

.sk-wrap { display: flex; flex-direction: column; gap: 10px; padding: 4px 0; }
.sk-row { display: flex; gap: 12px; align-items: center; }
.sk-row .skeleton { height: 11px; }
.sk-tiles { display: grid; grid-template-columns: repeat(auto-fit, minmax(160px, 1fr)); gap: 12px; }
.sk-tile {
  border: 1px solid var(--line-soft); border-radius: var(--radius);
  background: var(--panel-2); padding: 14px; display: flex; flex-direction: column;
}
.sk-tile .skeleton { display: block; }

/* ---------- Page header ---------- */
/* Title, one line of what the page is for, and the page's actions — so a
   console explains itself before the first panel, and the actions are in the
   same place on every page. */
.page-head { display: flex; align-items: flex-start; gap: 16px; flex-wrap: wrap; margin: 2px 2px 0; }
.page-head h1 { font-size: 20px; letter-spacing: -.02em; margin: 0 0 2px; }
.page-head p { margin: 0; color: var(--muted); font-size: 13px; max-width: 720px; }
.page-head .actions { margin-left: auto; display: flex; gap: 8px; align-items: center; flex-wrap: wrap; }

/* Shows and hides the page's own description — see `collapsePageProse`.
   It lives inside the <h1> so it sits on the title's baseline and moves with
   it when the heading wraps, rather than being positioned against the header
   box. Deliberately quiet: this is a disclosure, not an action. */
.page-head h1 .prose-toggle {
  background: none; border: 0; padding: 0 0 0 7px; margin: 0;
  color: var(--faint); cursor: pointer; vertical-align: middle; line-height: 1;
}
.page-head h1 .prose-toggle:hover { color: var(--ink-2); background: none; }
.page-head h1 .prose-toggle:focus-visible { outline: 2px solid var(--accent); outline-offset: 2px; border-radius: 3px; }

/* ---------- Images: mugshots, BOLO photographs ---------- */
.mug {
  width: 64px; height: 80px; object-fit: cover; flex: 0 0 64px;
  border-radius: 6px; background: var(--panel-hi); border: 1px solid var(--line-soft);
  display: block;
}
.mug.lg { width: 168px; height: 210px; flex-basis: 168px; border-radius: var(--radius); }
.mug.xl { width: 100%; max-width: 420px; height: auto; aspect-ratio: 4 / 5; flex-basis: auto; }
.mug.wide { width: 100%; height: auto; aspect-ratio: 4 / 3; max-height: 360px; flex-basis: auto; }
.mug.round { width: 40px; height: 40px; flex-basis: 40px; border-radius: 50%; }
/* No photograph: a silhouette in the same footprint, so a grid keeps its rhythm
   and "no photo" is legible as a fact rather than a broken image. */
.mug-empty {
  display: grid; place-items: center; color: var(--faint); font-size: 24px;
  background: repeating-linear-gradient(135deg, var(--panel-2) 0 6px, var(--panel-hi) 6px 7px);
}
.mug-empty.lg { font-size: 44px; }
img.img-loading { opacity: .35; }
/* A photograph that is on the record but missing from the media store.

   This used to be an <img> at 30% opacity with a dashed red outline — which,
   since a failed image has no intrinsic size, collapsed to nothing wherever its
   container had not been given a size of its own. On the booking grid the photo
   container went to 1x1 and the card lost its whole photo area, on exactly the
   records where a reader most needs to see that a photograph was expected.

   An <img> still paints its CSS background when its source fails, so the
   placeholder goes there: the same hatch `.mug-empty` uses, and the same
   silhouette, so "expected, not present" reads as a state rather than as a
   broken page. The element itself is left in place, which matters because
   callers hold a reference to it after requesting the load. The reason why it
   failed stays on `title`. */
img.img-failed {
  opacity: 1;
  border: 1px dashed var(--danger-line);
  background-image: url('data:image/svg+xml;utf8,<svg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24' fill='none' stroke='%238191a8' stroke-width='1.4' stroke-linecap='round' stroke-linejoin='round'><path d='M12 4.2a4 4 0 1 1 0 8 4 4 0 0 1 0-8z'/><path d='M4.8 21.2v-1.4a7.2 7.2 0 0 1 14.4 0v1.4'/></svg>'),
    repeating-linear-gradient(135deg, var(--panel-2) 0 6px, var(--panel-hi) 6px 7px);
  background-repeat: no-repeat, repeat;
  background-position: center, 0 0;
  background-size: 34% auto, auto;
}
.gallery { display: grid; grid-template-columns: repeat(auto-fill, minmax(112px, 1fr)); gap: 10px; }
.gallery figure { margin: 0; position: relative; }
.gallery figure img { width: 100%; height: auto; aspect-ratio: 4 / 5; object-fit: cover; border-radius: var(--radius-sm); border: 1px solid var(--line-soft); cursor: zoom-in; }
.gallery figcaption { font-size: 11px; color: var(--faint); margin-top: 4px; }
.gallery .x {
  position: absolute; top: 4px; right: 4px; width: 22px; height: 22px; padding: 0; line-height: 1;
  background: var(--kbd-bg); border: 1px solid var(--line); color: var(--ink-2); border-radius: 4px;
  font-size: 13px; display: grid; place-items: center; background-image: none;
}
.dropzone {
  border: 1px dashed var(--line); border-radius: var(--radius); padding: 14px; text-align: center;
  color: var(--muted); font-size: 13px; cursor: pointer; background: var(--bg-raise);
}
.dropzone:hover, .dropzone.over { border-color: var(--accent-line); background: var(--accent-dim); color: var(--ink-2); }
.dropzone input { display: none; }
.lightbox-img { max-width: 100%; max-height: 74vh; display: block; margin: 0 auto; border-radius: var(--radius-sm); }

/* ---------- Card grids (bookings, BOLOs) ---------- */
.card-grid { display: grid; grid-template-columns: repeat(auto-fill, minmax(252px, 1fr)); gap: 12px; }
.rec-card {
  display: flex; gap: 12px; padding: 12px; text-align: left; width: 100%;
  background: var(--panel-2); background-image: none; border: 1px solid var(--line-soft);
  border-radius: var(--radius); color: var(--ink-2); cursor: pointer; align-items: flex-start;
}
.rec-card:hover { background: var(--panel-hi); border-color: var(--accent-line); color: var(--ink); }
.rec-card.on { border-color: var(--accent); box-shadow: var(--ring); }
.rec-card .body { min-width: 0; flex: 1; display: flex; flex-direction: column; gap: 3px; }
.rec-card .name { font-weight: 600; font-size: 13px; color: var(--ink); white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }
.rec-card .meta { font-size: 12px; color: var(--muted); white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }
.rec-card .foot { margin-top: auto; display: flex; gap: 6px; align-items: center; flex-wrap: wrap; padding-top: 4px; }

.badge.critical { background: var(--danger); color: #1a0605; border-color: transparent; font-weight: 700; }
.badge.high { background: var(--warn-dim); color: var(--warn-ink); border-color: rgba(224,168,60,.4); }
.badge.expired { background: transparent; color: var(--danger-ink); border-color: var(--danger-line); }
.plate { font-family: var(--mono); font-weight: 700; letter-spacing: .06em; background: var(--bg); border: 1px solid var(--line); border-radius: 5px; padding: 2px 7px; display: inline-block; }

/* ---------- Map component ---------- */
.amap-host { position: relative; background: var(--canvas); border: 1px solid var(--line-soft); border-radius: var(--radius-sm); overflow: hidden; user-select: none; }
.amap { width: 100%; height: auto; display: block; cursor: grab; }
.amap-host.amap-panning .amap { cursor: grabbing; }
.amap-host[data-tool="rect"] .amap, .amap-host[data-tool="radius"] .amap { cursor: crosshair; }
.amap-host[data-tool="polygon"] .amap { cursor: cell; }
.amap-host[data-tool="point"] .amap { cursor: pointer; }
.amap-grid line { stroke: var(--canvas-grid); stroke-width: 1; }
.amap-lbl { fill: var(--canvas-lbl); font-size: 10px; font-family: var(--mono); pointer-events: none; }
.amap-lbl.bright { fill: var(--canvas-accent-hi); font-size: 11px; }
.amap-scale { stroke: #9fb1c8; stroke-width: 1.5; }
.amap-scale-bg { fill: rgba(7, 11, 18, .7); }
.amap-pt { transition: r .08s; }
.amap-pt.dim { opacity: .35; }
.amap-pt:hover { stroke: #fff; stroke-width: 1.2; }
.amap-hex { fill: var(--canvas-accent); stroke: #0b1220; stroke-width: .6; }
.amap-hexlbl { fill: #fff; font-size: 10px; font-weight: 600; pointer-events: none; }
.amap-shape { fill: var(--canvas-accent-dim); stroke: var(--canvas-accent); stroke-width: 1.6; pointer-events: none; }
.amap-shape.draft { stroke-dasharray: 5 4; fill: rgba(47, 157, 255, .08); }
.amap-vertex { fill: #0a0f18; stroke: var(--canvas-accent-hi); stroke-width: 1.4; }
.map-tools { display: flex; gap: 4px; align-items: center; flex-wrap: wrap; }
.map-tools .tool { padding: 5px 9px; font-size: 12px; background: var(--bg-raise); background-image: none; border: 1px solid var(--line-soft); color: var(--ink-2); }
.map-tools .tool:hover { background: var(--panel-hi); color: var(--ink); }
.map-tools .tool.on { background: var(--accent-dim); border-color: var(--accent-line); color: var(--accent-hi); }
.map-tools .sep { width: 1px; height: 18px; background: var(--line-soft); margin: 0 4px; }
.legend-sw { width: 10px; height: 10px; border-radius: 50%; display: inline-block; flex: 0 0 10px; }
.dow { display: flex; gap: 4px; }
.dow button { padding: 5px 0; flex: 1; font-size: 11px; background: var(--bg-raise); background-image: none; border: 1px solid var(--line-soft); color: var(--muted); }
.dow button.on { background: var(--accent-dim); border-color: var(--accent-line); color: var(--accent-hi); }
.check-list { display: flex; flex-direction: column; gap: 4px; max-height: 220px; overflow-y: auto; padding-right: 4px; }
.check-list label { display: flex; align-items: center; gap: 8px; font-size: 13px; padding: 4px 6px; border-radius: 5px; cursor: pointer; }
.check-list label:hover { background: var(--panel-2); }
.check-list label .cnt { margin-left: auto; color: var(--faint); font-size: 11px; font-variant-numeric: tabular-nums; }

/* ---------- Landing ----------
   Intentionally empty. `.landing`, `.hero`, `.cards` and `.card` lived here and
   were used by exactly one page — frontend/index.html — while this stylesheet
   is downloaded by forty. The landing page now carries its own presentation in
   a <style> block under an `lp-` namespace and reads only the design tokens
   above, so a palette change still reaches it and nothing else pays for it. */

/* ---------- Responsive ---------- */

@media (max-width: 1180px) {
  .g-main { grid-template-columns: minmax(0, 1fr); }
  .g4 { grid-template-columns: repeat(2, minmax(0, 1fr)); }
}
.nav-toggle { display: none; }
.nav-scrim { display: none; }

@media (max-width: 900px) {
  /* The sidebar slides over the content instead of vanishing. It keeps its own
     scroll, because the nav is longer than a phone screen. */
  .app .sidebar {
    position: fixed; top: 0; bottom: 0; left: 0; z-index: 80;
    overflow-y: auto; box-shadow: var(--shadow-lg);
    transform: translateX(-100%); transition: transform .2s cubic-bezier(.2, .8, .3, 1);
  }
  body.nav-open .app .sidebar { transform: none; }
  #sidebarHost { width: 0; flex: 0 0 0; }

  .nav-toggle {
    display: flex; flex-direction: column; justify-content: center; gap: 4px;
    width: 32px; height: 30px; padding: 0 7px; flex: 0 0 32px;
    background: var(--bg-raise); background-image: none; border: 1px solid var(--line-soft);
    border-radius: var(--radius-sm); cursor: pointer;
  }
  .nav-toggle span { display: block; height: 1.5px; background: var(--ink-2); border-radius: 1px; }
  .nav-toggle:hover { border-color: var(--accent-line); }

  .nav-scrim { display: block; position: fixed; inset: 0; z-index: 79; background: rgba(4, 7, 12, .55); }
  .nav-scrim[hidden] { display: none; }

  .g2, .g3, .g4 { grid-template-columns: minmax(0, 1fr); }
  .g-main { grid-template-columns: minmax(0, 1fr); }
  .content { padding: 12px; }
  /* The topbar is one non-wrapping row of buttons, which runs off the side of a
     phone. Letting it wrap needs the fixed flex-basis released as well, or the
     second row is drawn outside the bar. */
  .app .topbar {
    flex-wrap: wrap; gap: 8px;
    height: auto; min-height: var(--topbar-h); flex: 0 0 auto;
    padding: 8px 10px;
  }
  .app .topbar strong { font-size: 13px; }
}

::-webkit-scrollbar { width: 11px; height: 11px; }
::-webkit-scrollbar-track { background: var(--bg); }
::-webkit-scrollbar-thumb { background: var(--line); border-radius: 6px; border: 3px solid var(--bg); }
::-webkit-scrollbar-thumb:hover { background: #33455e; }


/* The brand lockup also appears outside the app shell (login card, landing). */
.brand-mark {
  width: 28px; height: 28px; flex: 0 0 28px; border-radius: 8px;
  background: linear-gradient(140deg, var(--accent) 0%, var(--violet) 100%);
  display: grid; place-items: center;
  color: #05080e; font-weight: 800; font-size: 14px;
  box-shadow: 0 0 0 1px rgba(255,255,255,.08) inset;
}
.login-head .brand-mark { margin-left: auto; margin-right: auto; }


/* Checkboxes and radios keep their intrinsic size and their label text. */
input[type="checkbox"], input[type="radio"] {
  width: auto;
  height: auto;
  margin: 0 6px 0 0;
  padding: 0;
  vertical-align: middle;
  accent-color: var(--accent);
  flex: 0 0 auto;
}
label:has(> input[type="checkbox"]), label:has(> input[type="radio"]) {
  display: inline-flex;
  align-items: center;
  white-space: nowrap;
}


/* ==========================================================================
   Polish pass. Small, systematic refinements applied once here rather than
   page by page: a hairline top edge on raised surfaces, consistent focus
   rings, calmer table rows, and motion that respects the user's setting.
   ========================================================================== */

/* Panels get a single hairline highlight along the top edge. It is what makes
   a flat rectangle read as a surface with a light source. */
.panel, .stat {
  box-shadow: inset 0 1px 0 rgba(255, 255, 255, .035), var(--shadow);
}
.panel-head {
  /* A hair of contrast so the header reads as attached to, not floating above. */
  background: linear-gradient(180deg, var(--panel-2), var(--panel));
}

/* One focus treatment everywhere, including keyboard focus on buttons and
   rows — previously only text inputs showed focus at all. */
:focus-visible {
  outline: none;
  box-shadow: var(--ring);
  border-radius: var(--radius-sm);
}
button:focus-visible, .chip:focus-visible, a:focus-visible { box-shadow: var(--ring); }

/* Buttons: a subtle gradient and a real pressed state. */
button, .btn {
  background-image: linear-gradient(180deg, rgba(255,255,255,.10), rgba(255,255,255,0));
  transition: background-color .12s ease, transform .06s ease, box-shadow .12s ease;
}
button:active, .btn:active { transform: translateY(1px); }
button.ghost, button.quiet { background-image: none; }

/* Tables: tighter rhythm, a hover that does not shout, and numerals that line
   up so a column of figures can be scanned. */
/* Alternating row tint — zebra striping.
   A 1px rule between rows is enough to separate two of them and not enough to
   follow one across eight columns, which is where a reader loses their line and
   reads a custodian against the wrong item. The tint is deliberately very
   slight: it has to be findable without becoming a second kind of highlight
   competing with hover and selection, which carry meaning.

   `--row-alt` rather than a literal so day mode can set its own: the same rgba
   that reads as a whisper on a dark panel is invisible on a white one. */
tbody tr:nth-child(even) td { background: var(--row-alt); }
/* Hover and selection still win — they mean something, the stripe does not. */
tbody tr { transition: background-color .1s ease; }
tbody tr:hover td, tbody tr:nth-child(even):hover td { background: rgba(47, 157, 255, .09); }
tbody tr.sel td { box-shadow: inset 2px 0 0 var(--accent); }
th { user-select: none; }
td.num, th.num { text-align: right; font-variant-numeric: tabular-nums; }

/* Badges sit on the text baseline instead of drifting above it. */
.badge { vertical-align: baseline; line-height: 1.5; }

/* Empty states get room to breathe. */
.empty { padding: 48px 24px; }
.empty .big { font-size: 30px; opacity: .35; margin-bottom: 12px; }

/* (The segmented-control active style used to be redefined here, overriding the
   accent tint declared next to `.seg` itself. Two definitions, 600 lines apart,
   and only the second had any effect. Folded into the one beside `.seg`.) */

/* The sidebar's active item gets a left rule — a clearer "you are here" than
   colour alone, and it survives being viewed in greyscale. */
.app .nav a.active { position: relative; }
.app .nav a.active:before {
  content: ""; position: absolute; left: -10px; top: 6px; bottom: 6px;
  width: 3px; border-radius: 0 2px 2px 0; background: var(--accent);
}

/* Scroll shadows on both axes: a table that continues past its container has to
   look like it continues. The vertical hint was already here; the horizontal one
   was not, so a table wider than its column was simply cut off mid-word with
   nothing to say the rest existed — the property list lost its whole location
   column that way.

   Four covers sit at the content edges (`local`, so they travel with the rows)
   and four shadows sit at the container edges (`scroll`, so they stay put). A
   cover parks over its shadow when that edge is reached, which is what makes the
   hint disappear exactly when there is nothing more in that direction. */
.table-wrap {
  background:
    /* covers */
    linear-gradient(var(--panel) 30%, rgba(20, 28, 43, 0)) center top,
    linear-gradient(rgba(20, 28, 43, 0), var(--panel) 70%) center bottom,
    linear-gradient(to right, var(--panel) 30%, rgba(20, 28, 43, 0)) left center,
    linear-gradient(to left, var(--panel) 30%, rgba(20, 28, 43, 0)) right center,
    /* shadows */
    radial-gradient(farthest-side at 50% 0, rgba(0, 0, 0, .38), rgba(0, 0, 0, 0)) center top,
    radial-gradient(farthest-side at 50% 100%, rgba(0, 0, 0, .38), rgba(0, 0, 0, 0)) center bottom,
    radial-gradient(farthest-side at 0 50%, rgba(0, 0, 0, .38), rgba(0, 0, 0, 0)) left center,
    radial-gradient(farthest-side at 100% 50%, rgba(0, 0, 0, .38), rgba(0, 0, 0, 0)) right center;
  background-repeat: no-repeat;
  background-size:
    100% 24px, 100% 24px, 22px 100%, 22px 100%,
    100% 10px, 100% 10px, 9px 100%, 9px 100%;
  background-attachment: local, local, local, local, scroll, scroll, scroll, scroll;
}

/* Page section heading, used above a group of panels. */
.section-title {
  display: flex; align-items: baseline; gap: 10px;
  font-size: 12px; text-transform: uppercase; letter-spacing: .1em;
  color: var(--muted); margin: 4px 2px -2px;
}
.section-title:after {
  content: ""; flex: 1; height: 1px; background: var(--line-soft);
}

/* Toolbar row that sits inside a panel body. */
.toolbar-row {
  display: flex; align-items: center; gap: 8px; flex-wrap: wrap;
  padding-bottom: 2px;
}

/* Dense list rows used in side rails. */
.rail-row {
  display: flex; align-items: center; justify-content: space-between; gap: 8px;
  padding: 8px 10px; border-radius: var(--radius-sm); font-size: 13px;
  color: var(--ink-2); border: 1px solid transparent; background: transparent;
  width: 100%; text-align: left; cursor: pointer;
}
.rail-row:hover { background: var(--panel-hi); color: var(--ink); }
.rail-row.on { background: var(--accent-dim); border-color: var(--accent-line); color: var(--accent-hi); font-weight: 600; }

/* Reduced motion, in one place.
   There were five of these blocks. This universal one carries `!important`, so
   it already silenced three of the other four — `.modal { animation: none }`,
   `.app .sidebar { transition: none }` and an `.app *` copy of this very rule
   were all dead code sitting a few hundred lines apart.

   The fourth was NOT dead, and it is the reason this block has a second rule.
   Killing a transition's DURATION still applies the transform; it just applies
   it instantly. So a hover lift honoured "reduce motion" by snapping into place
   rather than by not moving, which is the jolt the setting exists to prevent.
   Removing the transform is a different instruction from removing its timing. */
@media (prefers-reduced-motion: reduce) {
  *, *::before, *::after {
    animation-duration: .001ms !important;
    transition-duration: .001ms !important;
  }
  .stat-link:hover, .sec-tile:hover { transform: none; }
}


/* --- section tiles ---------------------------------------------------------
   A module with several consoles needs one place that says what those consoles
   are. Tabs would hide the subsets a page-width away; these say the whole shape
   of the module at a glance and read as the top-level choice they actually are. */
.sec-tiles {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(190px, 1fr));
  gap: 10px;
}
.sec-tile {
  display: flex; flex-direction: column; gap: 4px; align-items: flex-start;
  padding: 14px 16px; min-height: 76px;
  background: var(--panel); border: 1px solid var(--line-soft);
  border-radius: var(--radius); color: var(--ink-2);
  cursor: pointer; text-align: left; text-decoration: none;
  transition: border-color .12s ease, background .12s ease, color .12s ease;
}
.sec-tile:hover { background: var(--panel-hi); border-color: var(--accent-line); color: var(--ink); }
.sec-tile:focus-visible { outline: none; box-shadow: var(--ring); }
.sec-tile.on {
  background: var(--accent-dim); border-color: var(--accent-line); color: var(--accent-hi);
}
.sec-tile .ico { font-size: 20px; line-height: 1; opacity: .9; }
.sec-tile .t { font-size: 15px; font-weight: 600; letter-spacing: -.01em; }
.sec-tile .d { font-size: 12px; color: var(--faint); font-weight: 400; line-height: 1.35; }
.sec-tile.on .d { color: var(--accent-hi); opacity: .78; }

@media (max-width: 620px) {
  .sec-tiles { grid-template-columns: repeat(2, 1fr); }
  .sec-tile { min-height: 64px; padding: 11px 12px; }
  .sec-tile .d { display: none; }
}

/* ---------- rail rearrange ----------
   The controls sit above the nav rather than inside it, so entering rearrange
   mode does not reflow the list the user is about to drag. */
.app .nav-tools { display: flex; gap: 6px; padding: 0 10px 8px; }
.app .nav-edit {
  flex: 1; font-size: 11px; padding: 5px 8px; border-radius: var(--radius-sm);
  background: transparent; border: 1px solid var(--line-soft); color: var(--faint);
  cursor: pointer; text-align: center;
}
.app .nav-edit:hover { border-color: var(--accent-line); color: var(--ink-2); }
body.nav-editing .app .nav-edit { border-color: var(--accent-line); color: var(--accent-hi); }

/* In rearrange mode every entry reads as a handle: grab cursor, a visible
   boundary, and headings become draggable rather than inert dividers. */
body.nav-editing .app .nav a,
body.nav-editing .app .nav .nav-label {
  cursor: grab; border: 1px dashed var(--line); border-radius: var(--radius-sm);
  margin-bottom: 3px;
}
body.nav-editing .app .nav a:active,
body.nav-editing .app .nav .nav-label:active { cursor: grabbing; }
body.nav-editing .app .nav .nav-label { opacity: .85; }
.app .nav .dragging { opacity: .45; border-style: solid; border-color: var(--accent); }
body.nav-editing .app .nav a.active:before { display: none; }

/* ---------- rearrangeable panels ----------
   The grip is the handle, not the whole panel: a body full of selectable text
   and buttons is a terrible drag target, and dragging from anywhere would
   fight every click inside it. */
/* The grip is taken OUT of the head's flex flow, and this is not cosmetic.

   `.panel-head` is `justify-content: space-between`, and `wirePanelDrag`
   inserted the grip as the head's first child. That handed the grip the flex
   start slot, which pushed the <h3> to the middle of the head (three children)
   or hard against the right edge (two). Sixteen panel titles across the five
   consoles that arrange panels were right-aligned for that reason and no
   other — Workspace, Datasets, Breakdown, Category, Chain integrity, Awaiting
   my assignment and the rest. It read as a house style because it was
   consistent; it was a side effect of a button nobody can see.

   Absolute, not `order: -1` or a flex reshuffle: the grip must not consume a
   slot at all, so the head distributes exactly the children the page wrote.
   The left padding below reserves its lane permanently rather than on hover,
   so the title does not jump sideways when the pointer enters the panel. */
.app .panel-head { position: relative; }
.app .panel-head.has-grip { padding-left: 32px; }
.app .panel-grip {
  position: absolute; left: 6px; top: 50%; transform: translateY(-50%);
  background: none; border: 0; color: var(--faint); cursor: grab;
  /* 24x24, not 20x18. This is a drag handle — the control with the least margin
     for a missed press, since a near-miss lands on the panel behind it — and it
     was the smallest target in the product. 24 is the WCAG 2.5.8 minimum, and
     the icon inside stays 14px, so only the grabbable area grows. */
  display: inline-grid; place-items: center;
  width: 24px; height: 24px; padding: 0;
  line-height: 0;
  border-radius: var(--radius-sm); opacity: 0; transition: opacity .12s, color .12s;
}
.app .panel:hover .panel-grip,
.app .panel-grip:focus-visible { opacity: 1; }
.app .panel-grip:hover { color: var(--ink-2); }
.app .panel-grip:active { cursor: grabbing; }
.app .panel-grip:focus-visible { outline: 2px solid var(--accent); outline-offset: 1px; }
.app .panel-dragging { opacity: .5; outline: 1px solid var(--accent); }

/* ---------- facelift: consistency pass ----------
   These were each defined slightly differently on individual pages, which is
   how a product ends up with four spinners and three empty states. */
.app .empty { padding: 34px 20px; text-align: center; color: var(--muted); }
.app .empty .big { font-size: 30px; opacity: .5; margin-bottom: 8px; }
.app .empty h4 { margin: 0 0 4px; color: var(--ink-2); font-size: 14px; }
.app .empty .small { max-width: 44ch; margin: 0 auto; line-height: 1.55; }

/* A focus ring that is actually visible. Keyboard users were navigating on
   nothing but the browser default, which several of these dark surfaces
   swallow entirely. */
.app a:focus-visible,
.app button:focus-visible,
.app select:focus-visible,
.app input:focus-visible,
.app textarea:focus-visible,
.app summary:focus-visible {
  outline: 2px solid var(--accent);
  outline-offset: 1px;
  border-radius: var(--radius-sm);
}

/* Numbers line up under numbers. Tabular figures everywhere a quantity is
   shown, so columns of counts can be scanned rather than read. */
.app .stat .v,
.app td.num,
.app .mono { font-variant-numeric: tabular-nums; }

/* Tables: numbers right, text left — so digits stack vertically. */
.app table th.num, .app table td.num { text-align: right; }



/* ---------- readability pass ----------
   Dark backgrounds make text perceptually denser, so the same leading that
   reads fine on white reads as a wall here. These numbers come from the
   measured contrast audit and from current dark-mode guidance: leading 1.5–1.8,
   a slightly heavier body weight, and a bounded measure so a line of helper
   text does not run the full width of a 1900px monitor. */
.app .small,
.app .panel-body p,
.app .note,
.app .empty .small { line-height: 1.6; }

/* 45–75 characters is the readable range; prose blocks were unbounded and
   stretched to whatever the panel was. Tables and data rows are exempt — they
   are not prose and want the width. */
.app .note,
.app .panel-body > p,
.app .empty .small { max-width: 72ch; }

/* Slightly heavier than the light theme calls for. Thin weights thin further
   against a dark ground, and most of this product's text is small. */
:root:not([data-theme="light"]) .app .small,
:root:not([data-theme="light"]) .app .faint { font-weight: 420; }

/* Depth by surface lightness, not by shadow. A drop shadow on a near-black
   ground is invisible; a lighter surface is what actually reads as "in front". */
.app .modal { border: 1px solid var(--line); }
.app .drawer { border-left: 1px solid var(--line); }

/* Helper text under a control belongs to that control. Without this it floats
   equidistant between the field above and the field below, and reads as a label
   for the wrong one. */
.app label.f + .small.faint,
.app select + .small.faint,
.app input + .small.faint,
.app textarea + .small.faint { margin-top: 4px; display: block; }

/* A busy console can show three spinners at once. One definition, one size. */
.app .spinner { width: 14px; height: 14px; }

/* ---------- a count is a door ----------
   Tiles that navigate look and behave like the buttons they are: they take
   focus, they respond to Enter, and they say so on hover. A number that opens
   what it counts must not be indistinguishable from one that does nothing. */
/* Drawers sit outside .app, so without the second selector a clickable tile in a
   drawer kept the filled-button look and its tiles collided (court docket). */
.app button.stat, .drawer button.stat {
  font: inherit; text-align: left; width: 100%; cursor: pointer;
  border: 1px solid var(--line-soft); background: var(--panel);
  /* A stat tile is a SURFACE that happens to be clickable, so it has to opt out
     of everything `button` assumes about being a filled control.

     `color` is the one that mattered: `button` sets `#061019`, the dark ink for
     a bright accent fill, and this rule set a dark panel background without
     resetting it. Any tile value with no colour of its own — which is most of
     them — was drawn at a measured contrast of 1.12 against its own panel,
     where 3.0 is the floor for text this size. The label escaped it only because
     `.stat .k` names a colour. Tone and zero classes still override this. */
  color: var(--ink);
  /* And the sheen: `button` paints a top-down white gradient, which on a panel
     the size of a tile reads as a gradient background rather than as a control. */
  background-image: none;
  /* `display: block` is load-bearing, not tidying. A <button> lays its children
     out in an anonymous centring box, so the label and the value sat on one
     line — while the <div> version of the same tile, which is display:block,
     stacked them correctly. The two renderings of one component have to agree,
     and `.stat` on a div is block. */
  display: block;
  /* A button does not inherit the div's box metrics either. */
  margin: 0; border-radius: var(--radius); padding: 11px 13px 12px;
}
.app .stat-link:hover { border-color: var(--accent-line); background: var(--panel-hi); }
.app .stat-link:focus-visible { outline: 2px solid var(--accent); outline-offset: 1px; }
.app button.badge { font: inherit; cursor: pointer; border-width: 1px; }
.app button.badge:hover { filter: brightness(1.15); }

/* The open-record column. Narrow, right-aligned, and only visible on the row
   under the cursor — a permanent arrow on every row is visual noise on a table
   of two hundred. */
.app table th.go, .app table td.go { width: 28px; text-align: right; padding-right: 10px; }
.app table td.go a { opacity: 0; color: var(--muted); text-decoration: none; font-size: 15px; }
.app table tr:hover td.go a,
.app table td.go a:focus-visible { opacity: 1; color: var(--accent-hi); }

/* ---------- Icons ----------
   Drawn icons replace the fifty-four Unicode characters the product used to
   borrow from unrelated typefaces. Measured at one size, those glyphs varied
   13px–29px in ink height and 23%–92% in ink coverage, so a nav rail read as a
   row of mismatched weights rather than a set. These share one grid, one
   stroke, and `currentColor`.

   `vertical-align: -0.125em` puts a 16px icon's optical centre on the text's
   x-height centre. Without it an inline SVG sits on the baseline and rides
   high against its own label. */
/* Named `a-ico`, not `ai`: `.ai` already means "AI-generated content" here —
   person_profile.html gives such panels a violet left rule — so an icon class of
   that name drew a violet bar down the side of every icon on that page. */
.a-ico {
  display: inline-block;
  vertical-align: -.125em;
  flex: 0 0 auto;
  stroke: currentColor;
  fill: none;
}

/* `[hidden]` is a UA-sheet `display: none`, which any class rule outranks. The
   subject panel's body is `.panel-body { display: flex }`, so setting `hidden`
   on it did nothing at all. Every page in this product toggles visibility with
   `hidden`, so this belongs here rather than in one page. */
[hidden] { display: none !important; }

/* Icon slots: previously these sized a FONT. They now centre a fixed-size SVG,
   so the font-size declarations they carried are gone rather than merely
   overridden — a stale `font-size: 20px` on an SVG slot silently changes the
   line box it sits in. */
.app .nav .ico,
.pal-open .ico,
.sec-tile .ico,
.top-search .ts-ico,
.pal-row .pal-ico {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  line-height: 0;
}
.app .nav .ico { width: 16px; flex: 0 0 16px; opacity: .9; text-align: initial; }
.app .nav a.active .ico { opacity: 1; }
.pal-open .ico { color: var(--faint); }
.sec-tile .ico { color: var(--accent); opacity: .95; }
.sec-tile.on .ico { color: var(--accent-hi); opacity: 1; }
.top-search .ts-ico { color: var(--faint); }
.pal-row .pal-ico { width: 18px; flex: 0 0 18px; color: var(--muted); text-align: initial; }

/* A rail row is `space-between`, so its icon and its label have to travel as one
   flex child — otherwise the rail pushes them to opposite edges and the icon
   ends up labelling the count instead of the row. */
.rail-row .rr-main { display: inline-flex; align-items: center; gap: 9px; min-width: 0; }
.rail-row .rr-main .ico { color: var(--muted); }
.rail-row.on .rr-main .ico { color: var(--accent-hi); }

/* ---------- Horizontal scroll affordance ----------
   The same technique `.table-wrap` uses, on one axis, for the strips that scroll
   sideways: unit tabs, workspace tabs, wide panel bodies. `.unit-tabs` on the
   cases console measured 2285px of content in a 1233px rail — five of its nine
   units were off the right edge with nothing but a hairline scrollbar to say so,
   and "Sgt. Ellis," was cut mid-name at the boundary.

   Covers are `local` so they travel with the content; shadows are `scroll` so
   they stay at the container edge. A cover parks over its shadow when that end
   is reached, which is what makes the hint vanish exactly when there is nothing
   further in that direction. */
.scroll-x {
  overflow-x: auto;
  background:
    linear-gradient(to right, var(--panel) 30%, rgba(20, 28, 43, 0)) left center,
    linear-gradient(to left,  var(--panel) 30%, rgba(20, 28, 43, 0)) right center,
    radial-gradient(farthest-side at 0 50%,   rgba(0, 0, 0, .40), rgba(0, 0, 0, 0)) left center,
    radial-gradient(farthest-side at 100% 50%, rgba(0, 0, 0, .40), rgba(0, 0, 0, 0)) right center;
  background-repeat: no-repeat;
  background-size: 24px 100%, 24px 100%, 10px 100%, 10px 100%;
  background-attachment: local, local, scroll, scroll;
}
/* Thin, and only while the strip is actually being used. A permanent 15px
   scrollbar under a row of tabs reads as a broken layout. */
.scroll-x::-webkit-scrollbar { height: 6px; }
.scroll-x::-webkit-scrollbar-thumb { border-radius: 3px; }

/* ==========================================================================
   Console uplift — the front door's language, applied to the working pages.

   The landing page was redesigned first, and the temptation afterwards is to
   paste its decoration into thirty-eight consoles. Most of it must not travel:
   the fixed graticule and the accent bloom are noise behind a table of live
   records and cost a compositor layer on every scroll, and a gradient-clipped
   headline is right once, above a chrome lockup, and a gimmick on every panel
   title. What does travel is the register — raised surfaces with a light
   source, one monospaced label style for anything that names a thing rather
   than says something, an accent tile around an icon that leads, and a hover
   that reads as a control coming online rather than as a colour change.

   It lives here, once, because all thirty-eight consoles already share this
   stylesheet. Thirty-eight page edits would say the same thing thirty-eight
   times and start drifting the day after.
   ========================================================================== */

/* --- one label style ------------------------------------------------------
   `.kicker` names a thing: a section, a group, a field's identity. It is the
   same treatment the landing page uses for "01 — CONSOLES", and the same one
   the stat tiles have always used for their caption — which is why the tile
   caption is folded into it here rather than left as a near-miss. */
.kicker,
.stat .k {
  font-family: var(--mono);
  font-size: 11px;
  font-weight: 500;
  letter-spacing: .14em;
  text-transform: uppercase;
  color: var(--faint);
  line-height: 1.3;
}
.stat .k { color: var(--muted); }

/* The page header carries the rail group the page belongs to. Painted by
   pageKicker() in aerqor.js from the NAV tree, not written into each page, so
   moving a console between groups cannot leave its header lying. */
.page-head .kicker { display: block; margin-bottom: 5px; }

/* --- raised surfaces ------------------------------------------------------
   A flat fill reads as a hole; a vertical gradient reads as a panel under a
   light. The existing inset hairline stays — it is the top edge of the same
   effect — and the radius is unchanged, because `--radius-lg` on the smaller
   nested panels reads as bubbles rather than as surfaces. */
.panel,
.stat {
  background-image: linear-gradient(180deg, var(--panel) 0%, var(--panel-2) 100%);
}

/* --- controls shaped like cards -------------------------------------------
   A stat tile that navigates and a section tile are both links wearing a
   surface. They get the landing card's whole hover state: a two-pixel lift, an
   accent border, and a hairline that lights along the top edge. */
.stat-link,
.sec-tile {
  position: relative;
  overflow: hidden;
  transition: transform .16s ease, border-color .16s ease, box-shadow .16s ease;
}
.stat-link::before,
.sec-tile::before {
  content: "";
  position: absolute; top: 0; left: 0; right: 0; height: 1px;
  background: linear-gradient(90deg, transparent, var(--accent), transparent);
  opacity: 0;
  transition: opacity .16s ease;
}
.stat-link:hover,
.sec-tile:hover {
  transform: translateY(-2px);
  border-color: var(--accent-line);
  box-shadow: var(--shadow);
}
.stat-link:hover::before,
.sec-tile:hover::before { opacity: .8; }
/* A tile that is already the current section is not offering to be pressed. */
.sec-tile.on:hover { transform: none; }
.sec-tile.on::before { opacity: 0; }

/* --- an icon that leads gets a tile ---------------------------------------
   Only where the icon is the first thing in a card-shaped control. Rail and
   toolbar icons are labels beside text and stay bare. */
.sec-tile .ico {
  width: 30px; height: 30px;
  border-radius: 9px;
  background: var(--accent-dim);
  border: 1px solid var(--accent-line);
  color: var(--accent-hi);
  margin-bottom: 2px;
}
.sec-tile.on .ico {
  background: var(--accent);
  border-color: var(--accent);
  color: var(--on-accent);
}

/* Motion is decoration and goes first. */

/* ===== Chrome: account control, anchored menu, shortcut sheet, empty actions =====
   Everything in this block replaces something the consoles were each doing for
   themselves, or filling a hole they all had. */

/* ---------- account control ----------
   Sits at the end of the top bar where every product this audience already
   uses puts it. It replaces two loose buttons: the day/night toggle and the
   Sign out button that all thirty-eight consoles carried at equal weight
   beside their own primary action. */
.app .acct {
  display: flex; align-items: center; gap: 8px;
  margin-left: 4px; padding: 4px 8px 4px 4px;
  background: transparent; background-image: none;
  color: var(--ink-2);
  border: 1px solid transparent; border-radius: 9px;
  font-size: 13px; font-weight: 500; line-height: 1.15;
  max-width: 220px;
}
.app .acct:hover { background: var(--panel-hi); color: var(--ink); border-color: var(--line-soft); }
.app .acct[aria-expanded="true"] { background: var(--panel-hi); color: var(--ink); border-color: var(--accent-line); }
.acct-av {
  flex: 0 0 28px; width: 28px; height: 28px;
  display: grid; place-items: center;
  border-radius: 50%;
  background: var(--accent-dim); border: 1px solid var(--accent-line); color: var(--accent-hi);
  font-family: var(--mono); font-size: 11px; font-weight: 600; letter-spacing: .02em;
}
.acct-who { display: flex; flex-direction: column; align-items: flex-start; gap: 1px; min-width: 0; }
.acct-name { font-size: 13px; max-width: 140px; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.acct-role {
  font-family: var(--mono); font-size: 10px; letter-spacing: .08em; text-transform: uppercase;
  color: var(--faint); max-width: 140px; overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
}
.acct-chev { color: var(--faint); line-height: 0; }
/* Below tablet the bar has to hold a hamburger, a title, a search box and this.
   The avatar alone still says who is signed in, and the menu still says the
   rest of it. */
@media (max-width: 900px) {
  .app .acct { max-width: none; padding: 4px; }
  .acct-who, .acct-chev { display: none; }
}

/* ---------- the active assignment ----------
   Sits in the top bar on every page, beside the account control, because it has
   to be visible at the moment a lookup is run — that is when it decides what
   the audit row says. Quiet when unset so it does not nag; it carries the
   accent and a live dot once it is holding something, because at that point
   every lookup is being attributed to that case and the person should be able
   to see which one at a glance. */
.case-ctl {
  display: inline-flex; align-items: center; gap: 7px;
  background: transparent; background-image: none;
  border: 1px solid var(--line); border-radius: var(--radius-sm);
  color: var(--muted); padding: 5px 10px; font-size: 12px;
  max-width: 230px; white-space: nowrap;
}
.case-ctl:hover { background: var(--panel-hi); color: var(--ink); }
.case-ctl .cc-t { overflow: hidden; text-overflow: ellipsis; }
.case-ctl .cc-off { color: var(--faint); }
/* Aerqor Partner, beside the working-case control in the top bar. Shaped like
   its neighbour deliberately: it is a tool you pick up, not a mode you enter,
   and it should not compete with the case control for attention. */
/* The Partner box in the top bar. Sized so it reads as a field you can use
   rather than a search that competes with the global record search beside it:
   that one finds people, this one answers questions. */
.partner-box { position: relative; display: inline-flex; }
.partner-box input {
  width: 230px; padding: 5px 10px; font-size: 12px;
  background: transparent; border: 1px solid var(--line);
  border-radius: var(--radius-sm); color: var(--ink);
}
.partner-box input:focus { border-color: var(--accent-line); outline: none; width: 320px; }
.partner-box input::placeholder { color: var(--faint); }
/* The answer drops under the field, over the page. It is read where it was
   asked rather than in a panel somewhere else. */
.partner-pop {
  position: absolute; top: calc(100% + 6px); right: 0; z-index: 60;
  width: min(520px, 90vw); max-height: 60vh; overflow-y: auto;
  background: var(--panel); border: 1px solid var(--line);
  border-radius: var(--radius); box-shadow: var(--shadow-lg, 0 10px 34px rgba(0,0,0,.45));
}
.partner-pop .pp-row { padding: 9px 12px; border-bottom: 1px solid var(--line); font-size: 13px; }
.partner-pop .pp-row:last-of-type { border-bottom: 0; }
.partner-pop .pp-foot {
  padding: 8px 12px; font-size: 11px; line-height: 1.45;
  color: var(--warn-ink); background: var(--panel-hi);
  border-top: 1px solid var(--line);
}
@media (max-width: 900px) { .partner-box { display: none; } }

.partner-btn {
  display: inline-flex; align-items: center; gap: 6px;
  background: transparent; background-image: none;
  border: 1px solid var(--line); border-radius: var(--radius-sm);
  color: var(--muted); padding: 5px 10px; font-size: 12px; white-space: nowrap;
}
.partner-btn:hover { background: var(--panel-hi); color: var(--ink); }
.partner-btn .ico { display: inline-flex; }

/* A list that carries its own markers, used where the content is sentences
   rather than a table. */
ul.plain { margin: 0; padding: 0 0 0 16px; }
ul.plain li { margin: 0 0 6px; line-height: 1.45; }
ul.plain li:last-child { margin-bottom: 0; }

.case-ctl.on {
  border-color: var(--accent-line); background: var(--accent-dim); color: var(--accent-hi);
  font-weight: 600;
}
.case-ctl .dot {
  width: 7px; height: 7px; border-radius: 50%; flex: 0 0 7px;
  background: var(--accent);
}

/* ---------- anchored menu ---------- */
.menu {
  position: fixed; z-index: 1200;
  min-width: 212px; max-width: min(320px, calc(100vw - 16px));
  padding: 5px;
  background: var(--panel);
  border: 1px solid var(--line);
  border-radius: var(--radius);
  box-shadow: var(--shadow-lg);
}
.menu-head { padding: 8px 9px 9px; border-bottom: 1px solid var(--line-soft); margin-bottom: 5px; }
.menu-head .mh-t { font-size: 13px; font-weight: 600; color: var(--ink); }
.menu-head .mh-d {
  font-family: var(--mono); font-size: 11px; letter-spacing: .06em; text-transform: uppercase;
  color: var(--faint); margin-top: 3px;
}
.menu-sep { height: 1px; background: var(--line-soft); margin: 5px 0; }
.menu-item {
  display: flex; align-items: center; gap: 9px; width: 100%;
  padding: 7px 9px; text-align: left;
  background: transparent; background-image: none;
  color: var(--ink-2);
  border: 0; border-radius: var(--radius-sm);
  font-size: 13px; font-weight: 500; white-space: nowrap;
}
.menu-item:hover, .menu-item:focus-visible { background: var(--panel-hi); color: var(--ink); }
.menu-item .mi-ico { flex: 0 0 15px; line-height: 0; color: var(--faint); }
.menu-item:hover .mi-ico, .menu-item:focus-visible .mi-ico { color: var(--accent-hi); }
.menu-item .mi-t { flex: 1; overflow: hidden; text-overflow: ellipsis; }
.menu-item .kbd { flex: 0 0 auto; }
/* `.destructive`, deliberately NOT `.danger`.

   `button.danger` is the destructive COMMIT — "Delete", "Purge" — and it is a
   red fill: the shared rule sets the tone as a background, and the day palette
   adds `:root[data-theme="light"] button.danger:not(.badge) { color: #fff }`
   to put white ink on it. Both outrank a plain `.menu-item`, so naming this
   row `danger` drew it as a solid red bar in the night theme and as white text
   on white in the day theme. A menu row is a list item, not a commit button;
   it carries the tone as ink, and it needs a name of its own to say so. */
.menu-item.destructive { color: var(--danger-ink); }
.menu-item.destructive:hover, .menu-item.destructive:focus-visible { background: var(--danger-dim); color: var(--danger-ink); }
.menu-item.destructive:hover .mi-ico, .menu-item.destructive:focus-visible .mi-ico { color: var(--danger-ink); }

/* ---------- keyboard shortcut sheet ---------- */
.ks { display: flex; flex-direction: column; }
.ks-row {
  display: grid; grid-template-columns: 150px minmax(0, 1fr); gap: 14px; align-items: baseline;
  padding: 9px 0; border-bottom: 1px solid var(--line-soft);
}
.ks-row:last-child { border-bottom: 0; }
.ks-k { text-align: right; }
.ks-w { font-size: 13px; color: var(--ink-2); line-height: 1.5; }
@media (max-width: 560px) {
  .ks-row { grid-template-columns: 1fr; gap: 4px; }
  .ks-k { text-align: left; }
}

/* ---------- the action inside an empty state ----------
   An empty state is the whole page at the moment it renders. Giving it the
   action that resolves it is the difference between a dead end and a start. */
.app .empty-act { margin-top: 14px; }
.app .empty-act .btn, .app .empty-act button { text-decoration: none; }

/* ==========================================================================
   Facelift, 2026-10.

   A whole-product pass in the register current operational software has
   settled on: neutral surfaces with a faint cool cast instead of saturated
   navy, larger radii, hairline borders, glass only where something sits OVER
   content (the top bar, menus, toasts, the modal scrim), soft colour-tinted
   elevation on the one control that commits, and navigation that animates
   between consoles instead of blanking.

   Like the passes above, it lives here once rather than in forty-seven pages,
   and it changes no class name, no token name and no contrast pair the design
   tests measure. Everything below overrides; deleting this block restores the
   previous look exactly, apart from the token values at the top of the file.

   `color-mix()` is used for tints; each such line is preceded by a plain
   fallback so a browser without it gets the previous flat colour, not nothing.
   ========================================================================== */

/* ---------- type ----------
   Inter's alternates where Inter is installed (straight-legged l, open digits);
   on the system face these features are simply absent, which is harmless. */
body {
  font-feature-settings: "cv11", "ss01", "ss03";
  text-rendering: optimizeLegibility;
}
h1, h2, .page-head h1, .modal-head h3 { text-wrap: balance; }
.page-head p, .note, .empty .small, .modal-head p { text-wrap: pretty; }

::selection { background: rgba(47, 157, 255, .32); color: var(--ink); }

/* Thin scrollbars that take the theme's own colours. The standard properties
   win over the ::-webkit-scrollbar rules wherever both are supported, so this
   is the modern path and the older rules remain the fallback. */
html { scrollbar-color: var(--line) transparent; }
* { scrollbar-width: thin; }

/* ---------- page motion ----------
   Every console is its own document, so moving between them used to be a
   white-to-dark blank and a full repaint. A cross-document view transition
   keeps the rail and the top bar still and cross-fades only the page body.
   Browsers without it navigate exactly as before. */
@view-transition { navigation: auto; }
.app .sidebar { view-transition-name: aq-rail; }
.app .topbar  { view-transition-name: aq-topbar; }
::view-transition-old(root),
::view-transition-new(root) { animation-duration: .16s; animation-timing-function: ease-out; }
@media (prefers-reduced-motion: reduce) {
  @view-transition { navigation: none; }
}

/* ---------- canvas ----------
   One faint light source at the top of the working area, scrolling away with
   the page. Not fixed: a fixed bloom behind a live table costs a compositor
   layer on every scroll, which is why the landing page's was not carried over. */
.app .main {
  background:
    radial-gradient(1100px 380px at 28% -160px, rgba(47, 157, 255, .07), transparent 70%) no-repeat;
}
:root[data-theme="light"] .app .main {
  background:
    radial-gradient(1100px 380px at 28% -160px, rgba(21, 102, 209, .06), transparent 70%) no-repeat;
}
.app .content { padding: 22px 24px 28px; gap: 16px; }

/* ---------- rail ---------- */
.app .sidebar {
  background: linear-gradient(180deg, var(--bg-raise) 0%, var(--bg) 100%);
  border-right-color: var(--line-soft);
}
.app .brand { padding: 12px 18px; }
.app .brand-name { letter-spacing: .12em; font-weight: 700; }
.app .nav { padding: 6px 12px 20px; gap: 1px; }
.app .nav-label {
  font-size: 11px; font-weight: 600; letter-spacing: .08em;
  padding: 18px 10px 6px;
}
.app .nav a {
  padding: 7px 10px; border-radius: 9px; border: 0;
  font-weight: 500;
  transition: background-color .12s ease, color .12s ease;
}
.app .nav a .ico { color: var(--faint); transition: color .12s ease; }
.app .nav a:hover { background: var(--panel-hi); }
.app .nav a:hover .ico { color: var(--ink-2); }
/* The current page: a soft accent wash that fades out to the right, accent
   ink on the icon, and the existing left rule — now a lit pill. The box border
   it used to carry made the active row read as a focused input. */
.app .nav a.active {
  background: linear-gradient(90deg, var(--accent-dim), rgba(47, 157, 255, .04) 85%);
  color: var(--ink); font-weight: 600; border: 0;
}
:root[data-theme="light"] .app .nav a.active {
  background: linear-gradient(90deg, var(--accent-dim), rgba(21, 102, 209, .03) 85%);
}
.app .nav a.active .ico { color: var(--accent-hi); }
.app .nav a.active:before {
  left: -12px; top: 7px; bottom: 7px; width: 3px; border-radius: 0 3px 3px 0;
  box-shadow: 0 0 10px rgba(47, 157, 255, .55);
}
.app .nav-tools { padding: 0 12px 6px; }
.app .nav-edit { border-radius: 9px; }
.pal-open {
  width: calc(100% - 24px); margin: 2px 12px 10px; padding: 8px 10px;
  border-radius: 10px; background: var(--panel); border-color: var(--line-soft);
}
.app .sidebar-foot { padding: 12px 18px; }

/* ---------- top bar ----------
   Glass, because it is the one surface content scrolls under. Every control in
   it shares one height and one radius so the bar reads as a single instrument
   strip rather than five differently sized buttons. */
.app .topbar {
  background: var(--topbar-bg);
  backdrop-filter: saturate(170%) blur(16px);
  -webkit-backdrop-filter: saturate(170%) blur(16px);
  padding: 0 20px; gap: 10px;
}
.app .topbar > strong { font-size: 15px; font-weight: 650; letter-spacing: -.01em; }
.top-search {
  height: 36px; border-radius: 10px;
  background: var(--panel); border-color: var(--line-soft);
  transition: border-color .12s ease, box-shadow .12s ease, background-color .12s ease;
}
.top-search:hover { border-color: var(--line); }
.case-ctl, .partner-btn {
  height: 34px; padding: 0 12px; border-radius: 10px;
  border-color: var(--line-soft); background: var(--panel);
  color: var(--ink-2); font-weight: 500;
  transition: background-color .12s ease, border-color .12s ease, color .12s ease;
}
.case-ctl:hover, .partner-btn:hover { border-color: var(--line); background: var(--panel-hi); color: var(--ink); }
.case-ctl.on { border-color: var(--accent-line); background: var(--accent-dim); color: var(--accent-hi); }
.case-ctl.on .dot { box-shadow: 0 0 0 3px var(--accent-dim), 0 0 8px var(--accent); }
.partner-box input:not([type="checkbox"]):not([type="radio"]) {
  height: 34px; padding: 0 12px; border-radius: 10px;
  background: var(--panel); border-color: var(--line-soft);
}
.partner-box input:focus { box-shadow: var(--ring); }
.partner-pop, .menu {
  border-radius: var(--radius); border-color: var(--line);
  background: var(--panel);
  backdrop-filter: saturate(160%) blur(18px);
  -webkit-backdrop-filter: saturate(160%) blur(18px);
}
@supports (background: color-mix(in srgb, red 50%, blue)) {
  .partner-pop, .menu { background: color-mix(in srgb, var(--panel) 90%, transparent); }
}
.menu { padding: 6px; animation: aq-pop .14s cubic-bezier(.2, .8, .3, 1); transform-origin: top right; }
.menu-item { border-radius: 8px; padding: 8px 10px; }
.app .acct { border-radius: 10px; padding: 3px 9px 3px 3px; }
.acct-av {
  background: linear-gradient(140deg, var(--accent) 0%, var(--violet) 100%);
  border: 0; color: #fff; font-family: var(--sans); font-weight: 700;
  box-shadow: 0 0 0 2px var(--bg), 0 0 0 3px var(--accent-line);
}
@keyframes aq-pop { from { opacity: 0; transform: translateY(-4px) scale(.98); } to { opacity: 1; transform: none; } }

/* ---------- page header ---------- */
.page-head { margin: 4px 2px 2px; }
.page-head h1 { font-size: 24px; font-weight: 650; letter-spacing: -.025em; line-height: 1.2; margin-bottom: 4px; }
.page-head p { font-size: 13px; line-height: 1.6; }
/* The rail group above the title, in accent ink with a short lead rule. */
.page-head .kicker {
  display: flex; align-items: center; gap: 8px;
  color: var(--accent-hi); margin-bottom: 6px;
}
.page-head .kicker::before {
  content: ""; width: 14px; height: 2px; border-radius: 2px; background: var(--accent);
}

/* ---------- labels ----------
   The mono caption at .14em tracking read as typewriter spacing — "A S S I G N
   E D" — at the size it is used. Sans at a moderate track keeps the small-caps
   register and reads as one word. */
.kicker, .stat .k {
  font-family: var(--sans); font-size: 11px; font-weight: 600; letter-spacing: .08em;
}
label.f { font-size: 12px; font-weight: 500; text-transform: none; letter-spacing: 0; color: var(--ink-2); margin-bottom: 6px; }
.section-title { font-size: 11px; font-weight: 600; letter-spacing: .08em; color: var(--faint); margin-top: 8px; }

/* ---------- surfaces ---------- */
.panel {
  border-radius: var(--radius);
  background-image: linear-gradient(180deg, var(--panel-2) 0%, var(--panel) 120px);
  box-shadow: inset 0 1px 0 rgba(255, 255, 255, .04), var(--shadow);
}
:root[data-theme="light"] .panel { background-image: none; box-shadow: var(--shadow); }
/* The head is part of the panel, not a bar laid across it: no fill of its own,
   just the hairline under it. */
.panel-head { background: transparent; padding: 12px 14px; min-height: 48px; }
.panel-head h3 { font-size: 14px; font-weight: 600; letter-spacing: -.005em; }
.panel-body { padding: 16px; }
.grid { gap: 16px; }

/* ---------- stat tiles ----------
   A larger figure, more air, and a light in the top-right corner that takes
   the tile's tone — so an amber or red count is visible from across a room
   before its digits are read. */
.stats { gap: 12px; }
.stat {
  padding: 14px 16px 15px; border-radius: var(--radius);
  background-image:
    radial-gradient(120% 140% at 100% 0%, rgba(47, 157, 255, .08), transparent 55%),
    linear-gradient(180deg, var(--panel-2) 0%, var(--panel) 100%);
}
.stat:has(.v.tone-warn:not(.is-zero)) {
  background-image:
    radial-gradient(120% 140% at 100% 0%, rgba(224, 168, 60, .12), transparent 55%),
    linear-gradient(180deg, var(--panel-2) 0%, var(--panel) 100%);
}
.stat:has(.v.tone-danger:not(.is-zero)) {
  background-image:
    radial-gradient(120% 140% at 100% 0%, rgba(242, 105, 95, .13), transparent 55%),
    linear-gradient(180deg, var(--panel-2) 0%, var(--panel) 100%);
}
.stat:has(.v.tone-ok:not(.is-zero)) {
  background-image:
    radial-gradient(120% 140% at 100% 0%, rgba(63, 191, 135, .10), transparent 55%),
    linear-gradient(180deg, var(--panel-2) 0%, var(--panel) 100%);
}
.stat .v { font-size: 30px; font-weight: 650; letter-spacing: -.03em; margin-top: 6px; line-height: 1.1; }
.stat .v.sm { font-size: 18px; letter-spacing: -.01em; }
.stat .d { margin-top: 4px; }
.app button.stat, .drawer button.stat { padding: 14px 16px 15px; border-radius: var(--radius); }

/* ---------- buttons ----------
   Metrics are unchanged — the consoles with the fullest top bars already have
   no width to spare — so this is finish only: a larger radius, and elevation.

   Elevation belongs to the controls that commit, so the colour-tinted shadow
   goes to the default (filled) button by name, not to `button`, which every
   tab, row, chip, grip and tile in the product also is. `:where()` keeps it at
   zero specificity: any component rule that says what its own button looks
   like — `.tabs button`, `.seg button`, a page's toolbar — still wins. */
button, .btn { border-radius: 9px; }
button.sm, .btn.sm { border-radius: 8px; }
:where(button:not([class]), button.sm:not(.ghost):not(.quiet):not(.danger), button.primary, .btn:not(.ghost)) {
  box-shadow:
    inset 0 1px 0 rgba(255, 255, 255, .22),
    0 1px 2px rgba(0, 0, 0, .25),
    0 6px 16px -6px rgba(47, 157, 255, .55);
}
:where(:root[data-theme="light"]) :where(button:not([class]), button.sm:not(.ghost):not(.quiet):not(.danger), button.primary, .btn:not(.ghost)) {
  box-shadow: inset 0 1px 0 rgba(255, 255, 255, .2), 0 1px 2px rgba(15, 30, 60, .16), 0 6px 16px -8px rgba(21, 102, 209, .6);
}
:where(button:not([class]), button.primary):hover {
  box-shadow: inset 0 1px 0 rgba(255, 255, 255, .22), 0 1px 2px rgba(0, 0, 0, .25), 0 8px 22px -6px rgba(47, 157, 255, .7);
}
button.ghost, .btn.ghost {
  background: var(--panel-hi); color: var(--ink-2); border-color: var(--line);
  box-shadow: inset 0 1px 0 rgba(255, 255, 255, .04), 0 1px 2px rgba(0, 0, 0, .18);
}
button.ghost:hover, .btn.ghost:hover { background: var(--line-soft); color: var(--ink); border-color: var(--line); }
:root[data-theme="light"] button.ghost, :root[data-theme="light"] .btn.ghost {
  background: var(--panel); color: var(--ink-2); box-shadow: 0 1px 2px rgba(15, 30, 60, .06);
}
:root[data-theme="light"] button.ghost:hover, :root[data-theme="light"] .btn.ghost:hover { background: var(--panel-hi); color: var(--ink); }
button.danger:not(.badge) {
  background-image: linear-gradient(180deg, rgba(255, 255, 255, .14), rgba(255, 255, 255, 0) 60%);
  box-shadow: inset 0 1px 0 rgba(255, 255, 255, .2), 0 6px 16px -6px rgba(242, 105, 95, .5);
}
button:disabled { box-shadow: none; }

/* ---------- fields ---------- */
input:not([type="checkbox"]):not([type="radio"]), select, textarea {
  border-radius: 9px; padding: 8px 12px;
  transition: border-color .12s ease, box-shadow .12s ease, background-color .12s ease;
}
textarea { line-height: 1.55; }
select { padding-right: 30px; }
:where(input:not([type="checkbox"]):not([type="radio"]):not(:read-only), select, textarea):hover:not(:focus) { border-color: var(--muted); }
/* The search box draws the field; the input inside it must not draw a second
   one. The bare `input` rule outranked `.top-search input`, which is why a box
   sat inside the box. */
.top-search input:not([type="checkbox"]):not([type="radio"]) {
  min-height: 0; padding: 0; border: 0; border-radius: 0; background: transparent; box-shadow: none;
}

input:focus, select:focus, textarea:focus { border-color: var(--accent); box-shadow: 0 0 0 3px var(--accent-dim); }
/* Only after the person has interacted — `:invalid` alone paints every empty
   required field red before anyone has typed. */
input:user-invalid, select:user-invalid, textarea:user-invalid { border-color: var(--danger-line); }
input[type="range"] { accent-color: var(--accent); padding: 0; min-height: 0; border: 0; background: transparent; box-shadow: none; }

/* ---------- filters and tabs ---------- */
.seg { border-radius: 10px; padding: 3px; gap: 2px; background: var(--bg-raise); border-color: var(--line-soft); }
.seg button { border-radius: 7px; min-height: 28px; padding: 4px 12px; font-weight: 500; box-shadow: none; }
.seg button.active, .seg button[aria-pressed="true"] {
  background: var(--panel-hi); color: var(--ink);
  box-shadow: 0 1px 2px rgba(0, 0, 0, .35), inset 0 1px 0 rgba(255, 255, 255, .07);
}
:root[data-theme="light"] .seg button.active,
:root[data-theme="light"] .seg button[aria-pressed="true"] {
  background: var(--panel); color: var(--ink);
  box-shadow: 0 1px 2px rgba(15, 30, 60, .12), 0 0 0 1px var(--line-soft);
}
.tabs { border-bottom-color: var(--line-soft); gap: 4px; }
.tabs button { min-height: 0; padding: 10px 12px; font-weight: 500; box-shadow: none; transition: color .12s ease, border-color .12s ease; }
.tabs button.active, .tabs button[aria-selected="true"] { color: var(--ink); }

/* ---------- chips and badges ---------- */
.chip { padding: 4px 11px; transition: background-color .12s ease, border-color .12s ease, color .12s ease; }
.badge { padding: 2px 9px; letter-spacing: .02em; }
.app button.badge { min-height: 0; padding: 2px 9px; box-shadow: none; }
.dot.danger { box-shadow: 0 0 0 3px var(--danger-dim), 0 0 8px rgba(242, 105, 95, .45); }

/* ---------- tables ---------- */
thead th {
  background: var(--panel-2); color: var(--muted);
  font-size: 11px; letter-spacing: .06em; padding: 10px 14px;
  border-bottom-color: var(--line-soft);
}
tbody td { padding: 10px 14px; border-bottom-color: var(--line-soft); }
tbody tr:hover td, tbody tr:nth-child(even):hover td { background: rgba(47, 157, 255, .07); }
:root[data-theme="light"] tbody tr:hover td,
:root[data-theme="light"] tbody tr:nth-child(even):hover td { background: rgba(21, 102, 209, .05); }
.kv { border-radius: var(--radius-sm); }
.kv dt, .kv dd { padding: 8px 12px; }

/* ---------- cards ---------- */
.rec-card, .sec-tile { border-radius: var(--radius); }
.rec-card { padding: 13px; transition: background-color .14s ease, border-color .14s ease, transform .14s ease, box-shadow .14s ease; }
.rec-card:hover { transform: translateY(-1px); box-shadow: var(--shadow); }
.sec-tile { padding: 15px 16px; }
.sec-tile .ico { border-radius: 10px; }
.mug { border-radius: 8px; }

/* ---------- overlays ----------
   The scrim blurs what is behind it, so the dialog is the only sharp thing on
   screen. */
.modal-mask, .drawer-mask {
  background: rgba(3, 5, 9, .55);
  backdrop-filter: blur(6px) saturate(120%);
  -webkit-backdrop-filter: blur(6px) saturate(120%);
}
:root[data-theme="light"] .modal-mask, :root[data-theme="light"] .drawer-mask { background: rgba(15, 24, 38, .32); }
.modal { border-radius: 18px; border-color: var(--line); box-shadow: inset 0 1px 0 rgba(255, 255, 255, .05), var(--shadow-lg); }
.modal-head, .modal-foot, .drawer-head { background: transparent; }
.modal-head { padding: 18px 20px 14px; }
.modal-head h3 { font-size: 15px; font-weight: 650; }
.modal-body { padding: 18px 20px; }
.modal-foot { padding: 14px 20px; }
.drawer {
  border-radius: 18px 0 0 18px;
  box-shadow: inset 1px 0 0 rgba(255, 255, 255, .04), var(--shadow-lg);
  animation: drawerin .24s cubic-bezier(.2, .8, .3, 1);
}
.drawer-head { padding: 16px 20px; }
.drawer-body { padding: 18px 20px; }

.toast {
  border-radius: var(--radius); border-left-width: 1px; padding: 11px 14px 11px 16px;
  position: relative; overflow: hidden;
  background: var(--panel-hi);
  backdrop-filter: saturate(160%) blur(16px);
  -webkit-backdrop-filter: saturate(160%) blur(16px);
  animation: aq-toast .22s cubic-bezier(.2, .8, .3, 1);
}
@supports (background: color-mix(in srgb, red 50%, blue)) {
  .toast { background: color-mix(in srgb, var(--panel-hi) 88%, transparent); }
}
/* The tone moves from a hard left border, which a radius bends into a hook, to
   a straight inset bar. */
.toast::before { content: ""; position: absolute; left: 0; top: 0; bottom: 0; width: 3px; background: var(--accent); }
.toast.ok::before { background: var(--ok); }
.toast.err::before { background: var(--danger); }
.toast.warn::before { background: var(--warn); }
@keyframes aq-toast { from { opacity: 0; transform: translateY(8px) scale(.98); } to { opacity: 1; transform: none; } }

/* ---------- states ---------- */
.note, .error-box { border-radius: 10px; padding: 11px 14px; }
.skeleton { border-radius: 6px; }
.empty .big { opacity: .45; }

/* ---------- login ---------- */
.login-wrap {
  background:
    radial-gradient(700px 420px at 50% -10%, rgba(47, 157, 255, .14), transparent 70%),
    radial-gradient(600px 400px at 85% 110%, rgba(166, 139, 250, .08), transparent 70%);
}
.login-card .panel { border-radius: 18px; box-shadow: inset 0 1px 0 rgba(255, 255, 255, .05), var(--shadow-lg); }

@media (max-width: 900px) {
  .app .content { padding: 14px; }
  .page-head h1 { font-size: 20px; }
  .drawer { border-radius: 0; }
}

/* The rail's scroll position is carried from page to page (keepRailScroll in
   aerqor.js). Scroll anchoring has to be off for that to hold: the position is
   restored before the icons paint, and when they do the rows above the fold
   grow a few pixels and the browser "helpfully" scrolls to compensate — which
   is then saved, so the rail crept about 6px further down on every page. */
.app .sidebar { overflow-anchor: none; }

/* ---- legal footer (mounted by aerqor.js on every page) ------------------ */
/* Quiet by design: it is read once and then has to stay out of the way of the
   work above it. margin-top:auto pins it to the bottom of a short main column. */
.aq-legal {
  margin-top: auto; padding: 14px 18px 18px; border-top: 1px solid var(--line-soft);
  font-size: 11px; line-height: 1.6; color: var(--faint);
  display: flex; flex-direction: column; gap: 6px;
}
.aq-legal strong { color: var(--muted); font-weight: 600; }
.aq-legal-notice { max-width: 1100px; }
.aq-legal-row { display: flex; flex-wrap: wrap; gap: 6px 18px; align-items: center; margin-top: 2px; }
.aq-legal-row a { color: var(--muted); }
.aq-legal a.aq-support {
  color: var(--ink); font-weight: 600; padding: 3px 10px; border: 1px solid var(--line);
  border-radius: 999px; background: var(--panel);
}
.aq-legal a.aq-support:hover { border-color: var(--accent); text-decoration: none; }
.aq-legal-tm { font-size: 10px; opacity: .85; }
/* On the sign-in view the footer sits under the centred card. */
#loginView > .aq-legal { width: min(760px, 100%); margin-top: 18px; border-top: 0; text-align: center; }
#loginView > .aq-legal .aq-legal-row { justify-content: center; }
.sys-use-notice {
  font-size: 11px; line-height: 1.55; color: var(--muted);
  padding: 10px 12px; border: 1px solid var(--line); border-left: 3px solid var(--accent);
  border-radius: 8px; background: var(--panel);
}
.sys-use-notice strong { color: var(--ink); font-weight: 600; }
/* Authenticator QR: always dark-on-white so a phone camera reads it in either theme. */
.mfa-qr { display: flex; justify-content: center; margin: 4px 0 10px; }
.mfa-qr img { background: #fff; padding: 6px; border-radius: 8px; image-rendering: pixelated; }
.mfa-help ol { margin: 6px 0 10px; padding-left: 20px; font-size: 13px; line-height: 1.6; }
.mfa-help h4 { margin: 12px 0 4px; font-size: 13px; }
/* A small link riding on a field label (e.g. "Instructions" beside the code box). */
label.f .label-link { float: right; font-weight: 400; text-transform: none; letter-spacing: 0; font-size: 12px; }
/* Inside the app: one line, quieter still. */
.aq-legal-compact { padding: 10px 18px 12px; }
.aq-legal-compact .aq-legal-row { margin-top: 0; }
.aq-legal-compact a.aq-support { padding: 1px 9px; }

/* A ghost button that is a toggle shows when it is on. Six consoles marked the
   pressed state with .on or .active and nothing drew it. */
button.ghost.on, button.ghost.active, button.ghost[aria-pressed="true"] {
  background: var(--accent-dim); border-color: var(--accent-line); color: var(--accent-hi);
}

/* ==========================================================================
   Phone, tablet and accessibility pass, 2026-10-25.

   Measured on every served page at 390x844 and 768x1024 in the headless
   harness (driver.mjs `viewport W H`). None of the pages scrolled sideways —
   the earlier responsive work holds — so this block is about what the pages
   were like to USE at that size:

   - the hamburger was an empty box: the global `button` rule sets
     `align-items: center`, which shrinks the three bars of a column flexbox to
     zero width. The menu existed and could not be seen;
   - the closed slide-in rail was only translated off-screen, so every one of
     its links stayed in the Tab order — a keyboard or switch user on a tablet
     tabbed through forty invisible links before reaching the page;
   - the top bar wrapped to three rows and squeezed the global search to a 45px
     stub that could not show a single character;
   - most buttons measured 26-34px tall, under the 40px a finger needs;
   - the focus indicator outside `.app` (sign-in card, modals and menus, which
     are appended to <body>) was the 15%-alpha ring alone — on a dark panel
     that is barely there.

   Everything here overrides; nothing renames a class or a token.
   ========================================================================== */

html { -webkit-text-size-adjust: 100%; text-size-adjust: 100%; }

/* ---------- focus: one visible ring, everywhere ----------
   The `.app` rule above already draws a 2px outline; this extends it to the
   surfaces outside `.app` — the login card, modals, the account menu, toasts. */
:focus-visible {
  outline: 2px solid var(--accent);
  outline-offset: 2px;
}
/* Inside a scrolling list or a tab strip an outset ring is clipped by the
   container, so those keep their inset ring (declared beside each component). */
.tabs button:focus-visible, .seg button:focus-visible { outline-offset: -2px; }

/* ---------- the hamburger ---------- */
.nav-toggle { align-items: stretch; }

@media (max-width: 900px) {
  .nav-toggle {
    width: 40px; height: 40px; flex: 0 0 40px; padding: 0 10px; gap: 5px;
  }
  .nav-toggle span { height: 2px; }

  /* Closed rail: out of the Tab order and the accessibility tree, not merely
     out of sight. `visibility` waits for the slide-out to finish, so the
     closing animation still plays. */
  .app .sidebar {
    visibility: hidden;
    width: min(var(--sidebar-w, 232px), 86vw);
    transition: transform .2s cubic-bezier(.2, .8, .3, 1), visibility 0s linear .2s;
  }
  body.nav-open .app .sidebar {
    visibility: visible;
    transition: transform .2s cubic-bezier(.2, .8, .3, 1), visibility 0s;
  }
  /* The page behind an open menu should not scroll under the finger. */
  body.nav-open { overflow: hidden; }
}

/* ---------- top bar on a phone ----------
   Row one: menu, page title, then the controls that are always there
   (working case, Partner, account). Row two: the global search, full width,
   which is the thing a deputy on a phone is most likely to want. */
@media (max-width: 760px) {
  .app .topbar { row-gap: 8px; column-gap: 8px; padding: 8px 12px; }
  .app .topbar > strong {
    flex: 1 1 auto; min-width: 0;
    overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
  }
  .app .topbar > .grow { display: none; }
  .app .topbar .top-search {
    order: 99; flex: 1 1 100%; width: 100%; max-width: none; margin: 0;
    min-height: 40px;
  }
  .case-ctl { max-width: 38vw; }
  /* Partner keeps its icon and its name: the word is clipped visually but
     stays in the accessibility tree, so the button is still "Partner" to a
     screen reader and to voice control. */
  .partner-btn > span:not(.ico) {
    position: absolute; width: 1px; height: 1px; margin: -1px; padding: 0;
    overflow: hidden; clip: rect(0 0 0 0); clip-path: inset(50%);
    white-space: nowrap; border: 0;
  }
  .partner-btn { position: relative; padding: 0 11px; }
}

/* ---------- tap targets ----------
   WCAG 2.5.5 asks for 44px, 2.5.8 for 24px; 40px is the floor this product
   holds on a touch screen. Applied on narrow screens AND on any coarse
   pointer, so a 1024px tablet in landscape gets it too. Width is left alone
   (a min-width on every button would break the segmented controls); a short
   label still gets 40px of height, which is the axis a thumb misses on. */
@media (max-width: 900px), (pointer: coarse) {
  button, .btn, [role="button"], .chip, select,
  input:not([type="checkbox"]):not([type="radio"]):not([type="range"]):not([type="file"]):not([type="color"]) {
    min-height: 40px;
  }
  button.sm, .btn.sm { padding-top: 6px; padding-bottom: 6px; }
  .chip { display: inline-flex; align-items: center; }
  .seg button, .tabs button { min-height: 40px; }
  /* A badge that is a button ("5 integration issues") is still a control. */
  :root button.badge { min-height: 40px; display: inline-flex; align-items: center; }
  /* Small icon-only controls get a 40px square. */
  .modal-head .x, .gallery .x {
    min-width: 40px; min-height: 40px;
    display: inline-grid; place-items: center;
  }
  .gallery .x { width: 40px; height: 40px; top: 2px; right: 2px; }
  .app .acct { min-height: 40px; }
  .acct-av { width: 32px; height: 32px; flex-basis: 32px; }
  /* Checkboxes and radios: bigger box, and the whole label is the target. */
  input[type="checkbox"], input[type="radio"] { width: 20px; height: 20px; }
  .check-list label { min-height: 40px; }
  /* 16px stops iOS Safari zooming the page on every focus into a field,
     which is what makes a form unusable one-handed. */
  /* `1rem`, not a step on the type scale: it is the threshold iOS checks
     (computed size >= 16px) and nothing typographic. */
  input:not([type="checkbox"]):not([type="radio"]):not([type="range"]), select, textarea {
    font-size: 1rem;
  }
  .top-search input { font-size: 1rem; }
  /* Links in running text are fine; a link that is a control gets height. */
  .aq-legal-row a, .app .nav a { min-height: 40px; display: inline-flex; align-items: center; }
  .app .nav a { display: flex; }
}

/* ---------- tables ----------
   Every table lives in `.table-wrap`, which scrolls on both axes. On a phone
   its 60vh cap left a table about 330px tall inside a page that also
   scrolls — two nested scrollers fighting over one thumb. Let it grow and
   scroll sideways only. Momentum scrolling for older iOS. */
@media (max-width: 760px) {
  .table-wrap { max-height: none; -webkit-overflow-scrolling: touch; overscroll-behavior-x: contain; }
}

/* ---------- dialogs ----------
   `88vh` on a phone includes the area behind the browser's own toolbar, so the
   modal footer — where Save is — sat under it. `dvh` is the visible height;
   the vh line before it is the fallback.

   Nine pages (investigations, property, court, the four tactical consoles,
   operations, compliance) restate `.modal-mask` / `.modal` in their own
   <style>, which loads after this file: `width: min(620px, 96vw)` inside a
   mask with 20px padding is 414px wide on a 390px phone, so the dialog ran off
   the right edge. `:root` lifts these rules over the page copies without
   touching the pages. `max-width: 100%` resolves against the mask's padded
   grid area, so a dialog can never be wider than the space it sits in. */
:root .modal-mask .modal {
  max-width: 100%;
  max-height: 88vh; max-height: calc(100dvh - 40px);
}
@media (max-width: 560px) {
  :root .modal-mask { padding: 8px; place-items: end center; }
  :root .modal, :root .modal.sm, :root .modal.lg {
    width: 100%; max-width: 100%;
    max-height: calc(100vh - 16px); max-height: calc(100dvh - 16px);
    border-radius: var(--radius-lg) var(--radius-lg) var(--radius) var(--radius);
  }
  .modal-head, .modal-body, .modal-foot { padding-left: 14px; padding-right: 14px; }
  .modal-foot { flex-wrap: wrap; }
  .modal-foot > button { flex: 1 1 auto; }
  .modal-foot .spacer { flex: 1 1 100%; margin: 0 0 2px; }
  :root .drawer { width: 100vw; border-left: 0; }
}

/* ---------- the legal footer ----------
   11px with a 10px trademark line at 85% opacity is fine print on a desktop
   and unreadable on a phone; the opacity also took the trademark line under
   4.5:1. Full opacity everywhere, one size up on small screens. */
.aq-legal-tm { opacity: 1; }
@media (max-width: 760px) {
  .aq-legal { font-size: 12px; line-height: 1.6; padding: 14px 14px calc(18px + env(safe-area-inset-bottom, 0px)); }
  .aq-legal-tm { font-size: 11px; }
  .aq-legal-row { gap: 4px 14px; }
}

/* ---------- motion ---------- */
@media (prefers-reduced-motion: reduce) {
  .app .sidebar, .modal, .drawer { transition: none; animation: none; }
}
