/* ============================================================================
   GuardM8 v2 — design system.
   CoreM8's Mission-Control register (dark field + violet bloom, rounded cards,
   real shadows, violet chrome) applied to an ARC operator console. Two rules
   survive from the original direction because this is life-safety kit:

   1. The status ramp (--p1..--p4/--ok/--test) is the ONLY colour that may say
      anything about an alarm. Violet is chrome and primary actions, never state.
   2. Priority is always colour AND glyph AND position — never colour alone.

   No framework, no icon font, no CDN, nothing external. Class names here are a
   shared vocabulary with the other console pages: refine them, never rename.
   ========================================================================== */

/* ── Reset ────────────────────────────────────────────────────────────────── */

*, *::before, *::after { box-sizing: border-box; }
* { margin: 0; padding: 0; }

html {
  -webkit-text-size-adjust: 100%;
  /* Reserve the gutter so navigating from a long screen (audit) to a short one
     (help) does not shunt the whole layout sideways. */
  scrollbar-gutter: stable;
}

/* ── Scrollbars ────────────────────────────────────────────────────────────
   The browser default is an opaque grey slab, and on a graduated indigo field
   it reads as a hole punched through the surface. These are inset, translucent
   and violet, so a track belongs to the panel it sits in. Firefox gets the
   standard properties; the rest is WebKit/Blink. */

* { scrollbar-width: thin; scrollbar-color: var(--sb) transparent; }

::-webkit-scrollbar { width: 12px; height: 12px; }
::-webkit-scrollbar-track,
::-webkit-scrollbar-corner { background: transparent; }
::-webkit-scrollbar-thumb {
  background: var(--sb);
  border-radius: var(--r-pill);
  border: 3.5px solid transparent;   /* inset, via background-clip */
  background-clip: content-box;
  min-height: 38px;
}
::-webkit-scrollbar-thumb:hover,
::-webkit-scrollbar-thumb:active {
  background: var(--sb-hover);
  background-clip: content-box;
  border: 3px solid transparent;
}

/* The rail is chrome, not content. A permanent bar down the navigation is the
   most intrusive of the lot, so it only appears when the rail is in use. */
/* Hiding this bar was a mistake: the rail scrolls independently of the page, so
   a hidden bar means the last items are simply invisible and nothing on screen
   says otherwise. It is quiet, but it is there. */
.rail { scrollbar-color: var(--sb) transparent; }
.rail::-webkit-scrollbar-thumb {
  background: var(--sb); background-clip: content-box; border: 3px solid transparent;
}
.rail::-webkit-scrollbar { width: 10px; }

/* Horizontal bars under wide tables sit tighter so they do not eat a row of
   height, and a sideways scroll stays inside the table rather than dragging
   the page with it. */
.table-wrap { overscroll-behavior-x: contain; }
.table-wrap::-webkit-scrollbar { height: 10px; }

body {
  font-family: var(--f-text);
  font-size: var(--t-13);
  line-height: 1.5;
  letter-spacing: -0.01em;
  color: var(--ink);
  background: var(--canvas);
  background-image: var(--bg-grad);
  background-attachment: fixed;
  font-synthesis: none;
  -webkit-font-smoothing: antialiased;
  text-rendering: optimizeLegibility;
  /* Times and counts must not jitter as they tick. */
  font-variant-numeric: tabular-nums lining-nums;
  min-height: 100vh;
}

img, svg, video { display: block; max-width: 100%; }
button, input, select, textarea { font: inherit; color: inherit; }
a { color: inherit; text-decoration: none; }
table { border-collapse: collapse; width: 100%; }

/* An explicit display on a class must never resurrect a [hidden] element. */
[hidden] { display: none !important; }

::selection { background: var(--accent-soft); color: var(--ink); }
::-webkit-scrollbar { width: 10px; height: 10px; }
::-webkit-scrollbar-thumb {
  background: var(--hairline-strong);
  border-radius: 99px;
  border: 3px solid transparent;
  background-clip: content-box;
}
::-webkit-scrollbar-thumb:hover { background: var(--ink-faint); background-clip: content-box; }

:focus-visible {
  outline: 2px solid var(--accent-2);
  outline-offset: 1px;
  border-radius: 4px;
}

/* ── Type ─────────────────────────────────────────────────────────────────── */

.display {
  font-family: var(--f-display);
  font-variation-settings: "wdth" 112;   /* Archivo Expanded */
  font-weight: 600;
  letter-spacing: -0.01em;
}

/* A number treated as the most important thing on screen. */
.numeral {
  font-family: var(--f-display);
  font-variation-settings: "wdth" 118;
  font-weight: 700;
  font-size: var(--t-44);
  letter-spacing: -0.03em;
  line-height: 1;
}

.mono { font-family: var(--f-mono); font-size: 0.95em; letter-spacing: -0.01em; }

.label {
  font-size: var(--t-11);
  text-transform: uppercase;
  letter-spacing: 0.08em;
  font-weight: 600;
  color: var(--ink-faint);
}

.dim { color: var(--ink-dim); }
.faint { color: var(--ink-faint); }

h1, h2, h3, h4 {
  font-family: var(--f-display);
  font-variation-settings: "wdth" 108;
  font-weight: 600;
  letter-spacing: -0.02em;
  line-height: 1.2;
}
h1 { font-size: var(--t-22); }
h2 { font-size: var(--t-18); }
h3 { font-size: var(--t-15); }
h4 { font-size: var(--t-13); }

/* ── Shell: command rail + annunciator bar + data surface ─────────────────── */

.shell {
  display: grid;
  grid-template-columns: var(--rail-w) minmax(0, 1fr);
  grid-template-rows: var(--annunciator-h) minmax(0, 1fr);
  grid-template-areas: "brand annunciator" "rail main";
  min-height: 100vh;
  position: relative;
  z-index: 1;
}
.shell.is-collapsed { grid-template-columns: var(--rail-w-collapsed) minmax(0, 1fr); }

/* Brand block sits in the rail column, level with the annunciator. */
.brand {
  grid-area: brand;
  position: sticky;
  top: 0;
  z-index: 41;
  display: flex;
  align-items: center;
  gap: var(--sp-3);
  padding: 0 var(--sp-4);
  border-right: 1px solid var(--hairline);
  border-bottom: 1px solid var(--hairline);
  background: var(--surface);
}
/* The gradient mark — CoreM8 family resemblance. Violet chrome, not status. */
.brand-badge {
  width: 30px; height: 30px;
  border-radius: 9px;
  flex: none;
  display: grid;
  place-items: center;
  background: var(--accent-fill);
  color: #fff;
  font-family: var(--f-display);
  font-variation-settings: "wdth" 112;
  font-weight: 700;
  font-size: var(--t-13);
  box-shadow: 0 4px 16px -4px rgba(124, 108, 255, 0.7), inset 0 1px 0 rgba(255, 255, 255, 0.25);
}
.brand-mark {
  display: flex;
  align-items: center;
  gap: var(--sp-3);
  font-family: var(--f-display);
  font-variation-settings: "wdth" 112;
  font-weight: 700;
  font-size: var(--t-15);
  letter-spacing: -0.01em;
  white-space: nowrap;
}
.brand-mark .brand-mark-full { color: var(--ink-faint); font-weight: 600; }
.is-collapsed .brand-mark-full { display: none; }

/* ── Annunciator: a row of lamps read by position and colour together ─────── */

.annunciator {
  grid-area: annunciator;
  position: sticky;
  top: 0;
  z-index: 40;
  /* A grid item defaults to min-width:auto, so it refuses to shrink below its
     own content — the lamp cluster is ~430px and was pushing the whole page
     wide below 390px, giving the console a horizontal scrollbar on a phone.
     `overflow-x: auto` below cannot do its job without this. */
  min-width: 0;
  display: flex;
  align-items: center;
  gap: var(--sp-3);
  padding: 0 var(--sp-5);
  border-bottom: 1px solid var(--hairline);
  background: var(--surface); /* fallback where color-mix is unsupported */
  background: color-mix(in srgb, var(--canvas) 78%, transparent);
  backdrop-filter: blur(16px) saturate(140%);
  overflow-x: auto;
  scrollbar-width: none;
}
.annunciator::-webkit-scrollbar { display: none; }

/* The status lamps read as one instrument, not three loose words — a recessed
   panel with hairline dividers between the cells, the way a real annunciator
   groups its indicators. */
.lamp-cluster {
  display: inline-flex;
  align-items: stretch;
  gap: 0;
  align-self: center;
  height: 32px;
  padding: 0 var(--sp-1);
  border: 1px solid var(--hairline);
  border-radius: var(--r);
  background: color-mix(in srgb, var(--surface-2) 60%, transparent);
}
.lamp-cluster .lamp {
  padding: 0 var(--sp-3);
}
.lamp-cluster .lamp + .lamp {
  box-shadow: inset 1px 0 0 var(--hairline);
}

.lamp {
  display: inline-flex;
  align-items: center;
  gap: var(--sp-2);
  font-size: var(--t-11);
  text-transform: uppercase;
  letter-spacing: 0.07em;
  font-weight: 600;
  color: var(--ink-dim);
  white-space: nowrap;
}
.lamp-dot {
  width: 7px; height: 7px;
  border-radius: 50%;
  background: var(--ink-faint);
  flex: none;
}
/* Lamp colour is health/alarm state — the ramp, never the accent. */
.lamp[data-state="ok"] .lamp-dot { background: var(--ok); }
.lamp[data-state="warn"] .lamp-dot { background: var(--p2); }
.lamp[data-state="fail"] .lamp-dot { background: var(--p1); }
.lamp[data-state="ok"] { color: var(--ink); }

.annunciator-count {
  font-family: var(--f-display);
  font-variation-settings: "wdth" 115;
  font-weight: 700;
  font-size: var(--t-15);
}
.annunciator .spacer { margin-left: auto; flex: 1 1 auto; }

/* The right-hand cluster: clock, theme, account — the operator's own controls,
   held together and set apart from the estate-state lamps by a hairline. */
.annunciator-tools {
  display: inline-flex;
  align-items: center;
  gap: var(--sp-3);
  align-self: center;
}
.annunciator-tools .lamp--clock { color: var(--ink-dim); }
.tool-divider {
  width: 1px;
  height: 20px;
  background: var(--hairline-strong);
  flex: none;
}

/* Account chip: initials badge + name/role stacked, so identity reads at a
   glance without a stray status dot pretending the operator is a lamp. */
.account {
  display: inline-flex;
  align-items: center;
  gap: var(--sp-2);
  padding: 0 var(--sp-2) 0 0;
}
.account-badge {
  width: 26px; height: 26px;
  flex: none;
  display: grid;
  place-items: center;
  border-radius: var(--r-sm);
  background: var(--accent-soft);
  border: 1px solid var(--accent-line);
  color: var(--ink);
  font-family: var(--f-display);
  font-variation-settings: "wdth" 112;
  font-weight: 700;
  font-size: var(--t-13);
}
.account-id {
  display: flex;
  flex-direction: column;
  line-height: 1.15;
}
.account-name {
  font-size: var(--t-13);
  font-weight: 600;
  color: var(--ink);
  white-space: nowrap;
}
.account-role {
  font-size: var(--t-11);
  text-transform: uppercase;
  letter-spacing: 0.07em;
  font-weight: 600;
  color: var(--ink-faint);
}

/* ── Command rail ─────────────────────────────────────────────────────────── */

.rail {
  grid-area: rail;
  position: sticky;
  top: var(--annunciator-h);
  align-self: start;
  /* dvh, not vh: on a phone the browser chrome eats the difference and the last
     rail item sits under it permanently. */
  height: calc(100dvh - var(--annunciator-h));
  max-height: calc(100dvh - var(--annunciator-h));
  border-right: 1px solid var(--hairline);
  background: linear-gradient(180deg, var(--surface), var(--canvas));
  padding: var(--sp-3) var(--sp-3) var(--sp-4);
  overflow-y: auto;
  display: flex;
  flex-direction: column;
  gap: 3px;
}
.rail-group { padding: var(--sp-4) var(--sp-2) var(--sp-1); }
.is-collapsed .rail-group { padding-left: 0; text-align: center; }
.is-collapsed .rail-group span { display: none; }

.rail a {
  position: relative;
  display: flex;
  align-items: center;
  gap: 11px;
  padding: 8px 11px;
  border-radius: var(--r);
  color: var(--ink-dim);
  font-weight: 500;
  transition: background var(--fast) var(--ease), color var(--fast) var(--ease);
  white-space: nowrap;
}
.rail a svg { flex: none; opacity: 0.85; }
.rail a:hover { background: var(--hover); color: var(--ink); }
.rail a.is-active { color: var(--ink); background: var(--accent-soft); }
/* Gradient notch on the active item — chrome, so gradient is allowed. */
.rail a.is-active::before {
  content: "";
  position: absolute;
  left: -12px;
  top: 8px;
  bottom: 8px;
  width: 3px;
  border-radius: 0 3px 3px 0;
  background: var(--accent);
}
.is-collapsed .rail a { justify-content: center; padding-left: 0; padding-right: 0; }
.is-collapsed .rail a span { display: none; }

.rail-signout { margin-top: auto; padding-top: var(--sp-4); border-top: 1px solid var(--hairline); }
.rail-signout button {
  display: flex;
  align-items: center;
  gap: 11px;
  width: 100%;
  padding: 8px 11px;
  background: none;
  border: 0;
  border-radius: var(--r);
  color: var(--ink-faint);
  font-weight: 500;
  cursor: pointer;
  transition: background var(--fast) var(--ease), color var(--fast) var(--ease);
}
.rail-signout button:hover { background: var(--hover); color: var(--ink); }
.is-collapsed .rail-signout button { justify-content: center; padding-left: 0; padding-right: 0; }
.is-collapsed .rail-signout span { display: none; }

.rail-badge {
  margin-left: auto;
  font-family: var(--f-mono);
  font-size: var(--t-11);
  color: var(--ink-dim);
  background: var(--surface-3);
  border: 1px solid var(--hairline);
  border-radius: var(--r-pill);
  padding: 1px 7px;
}
.rail a.is-active .rail-badge { color: var(--ink); border-color: var(--accent-line); }
.is-collapsed .rail-badge { display: none; }

/* ── Main ─────────────────────────────────────────────────────────────────── */

.main {
  grid-area: main;
  min-width: 0;
  display: flex;
  flex-direction: column;
}
/* `.main` is a column flex, so every page wrapper inside it is a flex item —
   and a flex item's `min-width: auto` means it will not shrink below its own
   min-content. One wide child (the alarm page's call list) therefore pushed the
   whole page wider than the phone, with no element visibly overflowing to point
   at. Page wrappers shrink; their own children already handle their overflow. */
.main > * { min-width: 0; }

.view-head {
  display: flex;
  align-items: center;
  gap: var(--sp-4);
  padding: var(--sp-4) 28px;
  border-bottom: 1px solid var(--hairline);
  flex-wrap: wrap;
}
.view-head > :first-child { min-width: 0; }
/* A subtitle is a caption, not a paragraph. Capped so a long one wraps instead
   of stretching the header and shoving the buttons onto their own row. */
.view-head .sub { color: var(--ink-dim); font-size: var(--t-13); font-weight: 500;
                  max-width: 68ch; margin-top: 2px; }
.view-head .actions { margin-left: auto; display: flex; gap: var(--sp-2); align-items: center; flex-wrap: wrap; }
/* Inline controls in a header row size to their content, not to 100%. */
.view-head .actions .input, .view-head .actions .select { width: auto; min-width: 0; }

.view-body { padding: var(--sp-5) 28px 56px; }
.view-body.flush { padding: 0; }

/* ── Panels — CoreM8 cards: rounded, bordered, genuinely shadowed ─────────── */

.panel {
  background: var(--surface);
  border: 1px solid var(--hairline);
  border-radius: var(--r-lg);
  box-shadow: var(--shadow);
  overflow: hidden;
}
.panel-head {
  display: flex;
  align-items: center;
  gap: var(--sp-3);
  padding: var(--sp-3) var(--sp-4);
  border-bottom: 1px solid var(--hairline);
}
.panel-head .label { color: var(--ink-dim); }
.panel-head .actions { margin-left: auto; display: flex; gap: var(--sp-2); }
.panel-body { padding: var(--sp-4); }

.grid { display: grid; gap: var(--sp-4); }
.grid-2 { grid-template-columns: repeat(2, minmax(0, 1fr)); }
.grid-3 { grid-template-columns: repeat(3, minmax(0, 1fr)); }
.grid-4 { grid-template-columns: repeat(4, minmax(0, 1fr)); }
/* 62/38 — the event and its workflow, then context that sticks */
.split { display: grid; grid-template-columns: 62fr 38fr; gap: var(--sp-4); align-items: start; }
.split > aside { position: sticky; top: calc(var(--annunciator-h) + var(--sp-4)); }

/* ── KPI counters — a number, its meaning, and its trend ──────────────────── */

.counter {
  position: relative;
  overflow: hidden;
  padding: var(--sp-4);
  border: 1px solid var(--hairline);
  border-radius: var(--r-lg);
  background: var(--surface);
  box-shadow: var(--shadow);
  display: flex;
  flex-direction: column;
  gap: var(--sp-2);
  transition: border-color var(--fast) var(--ease);
}
.counter:hover { border-color: var(--hairline-strong); }
.counter .numeral { font-size: var(--t-32); }
.counter.has-spark { padding-bottom: 56px; }
/* Colour means "there is something here". A zero is not a warning, so it stays
   in ink — a red 0 next to a live 2097 says the opposite of what is true. */
.counter[data-priority="1"]:not([data-zero]) .numeral { color: var(--p1); }
.counter[data-priority="2"]:not([data-zero]) .numeral { color: var(--p2); }
.counter[data-priority="3"]:not([data-zero]) .numeral { color: var(--p3); }
.counter[data-tone="ok"]:not([data-zero]) .numeral { color: var(--ok); }
.counter[data-zero] .numeral { color: var(--ink-faint); }
.counter .meta {
  display: flex;
  align-items: center;
  gap: var(--sp-2);
  font-size: var(--t-11);
  color: var(--ink-faint);
  flex-wrap: wrap;
}

/* Delta vs yesterday. Deliberately NEUTRAL ink: on a life-safety board green/red
   belongs to alarm state, and "more signals than yesterday" is not an alarm. */
.delta { font-family: var(--f-mono); font-weight: 500; color: var(--ink-dim); white-space: nowrap; }

/* Server-rendered inline SVG sparkline — no chart library reaches an ARC
   network, and this way the trend renders with JavaScript disabled too. */
.counter .spark {
  position: absolute;
  left: 0; right: 0; bottom: 0;
  width: 100%;
  height: 40px;
  opacity: 0.9;
  pointer-events: none;
}

/* The received timestamp is a fixed-shape value. Left unstyled it took a share
   of the table's spare width from the site name, which is the column an
   operator actually reads. */
.th-received { width: 1%; white-space: nowrap; }

/* ── Data table ───────────────────────────────────────────────────────────── */

.table-wrap { overflow-x: auto; }

table.data { font-size: var(--t-13); }
table.data thead th {
  position: sticky;
  top: 0;
  z-index: 2;
  text-align: left;
  font-size: var(--t-11);
  text-transform: uppercase;
  letter-spacing: 0.06em;
  font-weight: 600;
  color: var(--ink-faint);
  background: var(--surface);
  padding: var(--sp-3) var(--sp-4);
  border-bottom: 1px solid var(--hairline-strong);
  white-space: nowrap;
}
table.data tbody td {
  padding: 0 var(--sp-4);
  height: var(--row-h);
  border-bottom: 1px solid var(--hairline);
  vertical-align: middle;
  white-space: nowrap;
}
table.data tbody td.wrap { white-space: normal; min-width: 20ch; }
/* Long free text in a dense table clips with an ellipsis; the full text rides
   on the title, and the detail page has all of it. Desktop only — the card
   form below 700px wraps it onto its own line instead. */
@media (min-width: 701px) {
  table.data tbody td.cell-clip { width: 100%; max-width: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
}
table.data tbody tr { transition: background var(--fast) var(--ease); }
table.data tbody tr:hover { background: var(--hover); }
table.data tbody tr.is-clickable { cursor: pointer; }
table.data tbody tr:last-child td { border-bottom: 0; }

/* Priority gutter — 3px of colour down the row's leading edge, always paired
   with a glyph so it never depends on colour alone. */
td.gutter {
  width: 0;
  padding: 0 !important;
  border-left: 3px solid transparent;
}
tr[data-priority="1"] td.gutter { border-left-color: var(--p1); }
tr[data-priority="2"] td.gutter { border-left-color: var(--p2); }
tr[data-priority="3"] td.gutter { border-left-color: var(--p3); }
tr[data-priority="4"] td.gutter { border-left-color: var(--p4); }
tr[data-status="closed"] td.gutter,
tr[data-status="cancelled"] td.gutter { border-left-color: var(--hairline-strong); }

/* The only looping animation in the product, and only on live P1. */
tr[data-priority="1"][data-live="1"] td.gutter { animation: breathe 1.4s var(--ease) infinite; }
@keyframes breathe { 0%, 100% { opacity: 1; } 50% { opacity: 0.45; } }
@media (prefers-reduced-motion: reduce) {
  tr[data-priority="1"][data-live="1"] td.gutter { animation: none; opacity: 1; }
}

/* Arrival should be noticed — that is a safety function, not decoration. */
tr.is-new { animation: enter var(--enter) var(--ease) both; }
@keyframes enter {
  from { opacity: 0; transform: translateY(-4px); }
  to { opacity: 1; transform: none; }
}

/* Keyboard focus on a row — a state you can find again at a glance. Violet is
   chrome: it marks where YOU are, and says nothing about the alarm. */
table.data tbody tr.is-kfocus {
  background: var(--accent-soft);
  outline: 1px solid var(--accent-line);
  outline-offset: -1px;
}

/* A queue that just stops, with no marker, reads as a page that failed to
   finish loading. This says the list ended on purpose. */
.rule-end {
  padding: var(--sp-3) var(--sp-4);
  border-top: 1px solid var(--hairline);
  color: var(--ink-faint);
  font-size: var(--t-11);
  text-transform: uppercase;
  letter-spacing: 0.08em;
}

/* ── Priority glyph ───────────────────────────────────────────────────────── */

.glyph {
  display: inline-flex;
  align-items: center;
  gap: 6px;
  font-size: var(--t-11);
  font-weight: 600;
  letter-spacing: 0.04em;
  white-space: nowrap;
}
.glyph::before { font-size: 9px; line-height: 1; }
.glyph[data-priority="1"] { color: var(--p1); }
.glyph[data-priority="1"]::before { content: "\25A0"; }   /* ■ */
.glyph[data-priority="2"] { color: var(--p2); }
.glyph[data-priority="2"]::before { content: "\25B2"; }   /* ▲ */
.glyph[data-priority="3"] { color: var(--p3); }
.glyph[data-priority="3"]::before { content: "\25C6"; }   /* ◆ */
.glyph[data-priority="4"] { color: var(--p4); }
.glyph[data-priority="4"]::before { content: "\25CF"; }   /* ● */

/* ── Tags — pill badges; tone = alarm state only ──────────────────────────── */

.tag {
  display: inline-flex;
  align-items: center;
  gap: 5px;
  padding: 2px 9px;
  border: 1px solid var(--hairline-strong);
  border-radius: var(--r-pill);
  font-size: var(--t-11);
  font-weight: 600;
  letter-spacing: 0.02em;
  color: var(--ink-dim);
  background: var(--hover);
  white-space: nowrap;
}
.tag[data-tone="p1"] { color: var(--p1); border-color: var(--p1); background: var(--p1-bed); }
.tag[data-tone="p2"] { color: var(--p2); border-color: var(--p2); background: var(--p2-bed); }
.tag[data-tone="p3"] { color: var(--p3); border-color: var(--p3); background: var(--p3-bed); }
.tag[data-tone="ok"] { color: var(--ok); border-color: var(--ok); background: var(--ok-bed); }
.tag[data-tone="test"] { color: var(--test); border-color: var(--test); background: var(--test-bed); }

/* ── Controls ─────────────────────────────────────────────────────────────── */

.btn {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  gap: var(--sp-2);
  height: 32px;
  padding: 0 var(--sp-3);
  background: var(--surface-2);
  border: 1px solid var(--hairline-strong);
  border-radius: var(--r);
  color: var(--ink);
  font-size: var(--t-13);
  font-weight: 600;
  cursor: pointer;
  white-space: nowrap;
  transition: background var(--fast) var(--ease), border-color var(--fast) var(--ease),
              transform var(--fast) var(--ease), filter var(--fast) var(--ease);
}
.btn:hover { background: var(--surface-3); border-color: var(--ink-faint); }
.btn:active { background: var(--surface-2); }
.btn[disabled] { opacity: 0.4; cursor: not-allowed; }
.btn.is-quiet { background: transparent; border-color: transparent; color: var(--ink-dim); }
.btn.is-quiet:hover { background: var(--hover); color: var(--ink); }
/* Primary = the gradient. Chrome only — an alarm action is primary because of
   its placement, not because it borrows a status colour. */
.btn.is-primary {
  background: var(--accent-fill);
  color: #fff;
  border: 0;
  box-shadow: 0 6px 18px -6px rgba(124, 108, 255, 0.7), inset 0 1px 0 rgba(255, 255, 255, 0.2);
}
.btn.is-primary:hover { filter: brightness(1.07); }
.btn.is-danger { color: var(--p1); border-color: var(--p1); background: var(--p1-bed); }
.btn.is-danger:hover { background: rgba(255, 59, 47, 0.2); }
.btn.is-lg { height: 40px; padding: 0 var(--sp-4); font-size: var(--t-15); }
.btn.is-block { width: 100%; }
.btn.is-sm { height: 24px; padding: 0 var(--sp-2); font-size: var(--t-11); border-radius: var(--r-sm); }

@media (prefers-reduced-motion: no-preference) {
  .btn:hover { transform: translateY(-1px); }
  .btn:active { transform: translateY(0); }
}

.key {
  display: inline-grid;
  place-items: center;
  min-width: 18px;
  height: 18px;
  padding: 0 5px;
  background: var(--surface-3);
  border: 1px solid var(--hairline-strong);
  border-radius: 5px;
  font-family: var(--f-mono);
  font-size: 10px;
  color: var(--ink-dim);
}

.field { display: flex; flex-direction: column; gap: 6px; }
.input, .select, .textarea {
  width: 100%;
  min-height: 34px;
  padding: 6px var(--sp-3);
  background: var(--surface-2);
  border: 1px solid var(--hairline-strong);
  border-radius: var(--r);
  color: var(--ink);
  transition: border-color var(--fast) var(--ease), box-shadow var(--fast) var(--ease);
}
.input::placeholder, .textarea::placeholder { color: var(--ink-faint); }
.input:focus, .select:focus, .textarea:focus {
  outline: none;
  border-color: var(--accent-line);
  box-shadow: 0 0 0 3px var(--accent-soft);
  background: var(--surface);
}
.textarea { min-height: 72px; resize: vertical; line-height: 1.55; }
.select {
  appearance: none;
  padding-right: var(--sp-5);
  background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='10' height='6' fill='none' stroke='%23a6a6b8' stroke-width='1.5'%3E%3Cpath d='M1 1l4 4 4-4'/%3E%3C/svg%3E");
  background-repeat: no-repeat;
  background-position: right 10px center;
}

.check { display: inline-flex; align-items: center; gap: var(--sp-2); cursor: pointer; }
.check input { accent-color: var(--accent); width: 14px; height: 14px; }

/* ── Segmented scope control ──────────────────────────────────────────────── */

.seg {
  display: inline-flex;
  align-items: center;
  gap: 2px;
  padding: 3px;
  background: var(--surface-2);
  border: 1px solid var(--hairline);
  border-radius: calc(var(--r) + 1px);
  /* The scopes are one control and must not wrap into two ragged rows, so on a
     narrow phone the control scrolls sideways within itself rather than
     widening the page. Without this the queue had a horizontal scrollbar at
     320px — the whole console shifting under the thumb. */
  max-width: 100%;
  overflow-x: auto;
  scrollbar-width: none;
}
.seg::-webkit-scrollbar { display: none; }
.seg a, .seg button {
  display: inline-flex;
  align-items: center;
  gap: 6px;
  padding: 5px 12px;
  border-radius: calc(var(--r) - 2px);
  color: var(--ink-dim);
  font-weight: 600;
  font-size: var(--t-13);
  white-space: nowrap;
  transition: background var(--fast) var(--ease), color var(--fast) var(--ease);
}
.seg button { background: transparent; border: 0; font-family: inherit; cursor: pointer; }
.seg a:hover, .seg button:hover { color: var(--ink); }
.seg a.is-on, .seg button.is-on { background: var(--surface-3); color: var(--ink); box-shadow: var(--shadow); }
.seg .n, .chip .n {
  font-family: var(--f-mono);
  font-size: var(--t-11);
  color: var(--ink-faint);
}
.seg a.is-on .n, .chip.is-on .n { color: var(--ink-dim); }

/* ── Priority filter chips — glyph carries the priority, count says what you
      are filtering into ──────────────────────────────────────────────────── */

.chip {
  display: inline-flex;
  align-items: center;
  gap: 6px;
  height: 30px;
  padding: 0 11px;
  border: 1px solid var(--hairline-strong);
  border-radius: var(--r);
  background: var(--surface-2);
  color: var(--ink-dim);
  font-weight: 600;
  white-space: nowrap;
  transition: background var(--fast) var(--ease), border-color var(--fast) var(--ease);
}
.chip:hover { background: var(--surface-3); }
.chip.is-on { background: var(--surface-3); border-color: var(--ink-faint); color: var(--ink); }

/* ── Definition rows ──────────────────────────────────────────────────────── */

.defs { display: grid; grid-template-columns: minmax(min(90px, 100%), auto) minmax(0, 1fr); gap: var(--sp-2) var(--sp-4); }
.defs dt { color: var(--ink-faint); font-size: var(--t-11); text-transform: uppercase; letter-spacing: 0.08em; font-weight: 600; padding-top: 2px; }
.defs dd { min-width: 0; overflow-wrap: anywhere; }

/* ── Flash messages ───────────────────────────────────────────────────────── */

/* Notifications: a toast stack in the top-right of the work surface. Fixed, so
   a confirmation never shoves the page down under the operator's cursor;
   dismissable; cleared by console.js after a beat. Tone is a left rule, never
   a filled block — the status ramp stays reserved for alarm state. */
.flashes {
  position: fixed; bottom: var(--sp-4); right: var(--sp-4);
  z-index: 90; display: flex; flex-direction: column; gap: var(--sp-2);
  width: min(420px, calc(100vw - 2 * var(--sp-4))); pointer-events: none;
}
.flash {
  pointer-events: auto;
  display: flex; align-items: flex-start; gap: var(--sp-3);
  padding: var(--sp-3) var(--sp-4);
  border: 1px solid var(--hairline-strong);
  border-left-width: 3px;
  border-radius: var(--r);
  background: var(--panel);
  box-shadow: var(--shadow-lg);
  font-weight: 500; font-size: var(--t-13); line-height: 1.4;
}
.flash[data-tone="success"], .flash[data-tone="ok"] { border-left-color: var(--ok); }
.flash[data-tone="danger"] { border-left-color: var(--p1); }
.flash[data-tone="warning"], .flash[data-tone="warn"] { border-left-color: var(--p2); }
.flash[data-tone="info"] { border-left-color: var(--accent); }
.toast-x {
  margin-left: auto; flex: none; width: 24px; height: 24px; border-radius: var(--r-sm);
  border: 0; background: transparent; color: var(--ink-faint); font-size: 18px; line-height: 1;
  cursor: pointer;
}
.toast-x:hover { color: var(--ink); background: var(--hover); }
@media (max-width: 900px) {
  .flashes { top: auto; bottom: calc(80px + env(safe-area-inset-bottom, 0px)); right: var(--sp-3); left: var(--sp-3); width: auto; }
}
@media (prefers-reduced-motion: no-preference) {
  .flash { animation: toast-in .18s var(--ease, ease) both; }
  @keyframes toast-in { from { opacity: 0; transform: translateY(4px); } to { opacity: 1; transform: none; } }
}

/* ── Empty states — state the fact, give the time, say nothing cheerful ───── */

.empty {
  padding: var(--sp-7) var(--sp-5);
  text-align: center;
  color: var(--ink-dim);
}
.empty .display { font-size: var(--t-22); color: var(--ink); margin-bottom: var(--sp-2); }
.empty .empty-actions {
  margin-top: var(--sp-4);
  display: flex;
  gap: var(--sp-2);
  justify-content: center;
  flex-wrap: wrap;
}

/* ── Timeline ─────────────────────────────────────────────────────────────── */

.timeline { display: flex; flex-direction: column; }
.timeline li { display: grid; grid-template-columns: 74px minmax(0, 1fr) auto; gap: var(--sp-4); padding: var(--sp-3) 0; border-bottom: 1px solid var(--hairline); list-style: none; align-items: start; }
/* The delete control is the third column. Without it the form was a block-level
   third grid item with nowhere to go, so it wrapped onto the next row and sat
   under the timestamp — bottom left of the entry it was meant to remove. */
/* A small square control that is an icon and nothing else. Shared vocabulary:
   it lives here rather than in dashboard.css because the alarm page uses it for
   the activity delete and every dialog close, and does not load that file. */
.icon-btn {
  background: transparent;
  border: 1px solid transparent;
  color: var(--ink-dim);
  border-radius: var(--r-sm);
  width: 28px; height: 28px;
  display: inline-flex; align-items: center; justify-content: center;
  font-size: var(--t-13); line-height: 1;
}
.icon-btn:hover:not(:disabled) { background: var(--hover); color: var(--ink); border-color: var(--hairline-strong); }
.icon-btn:focus-visible { outline: 2px solid var(--accent); outline-offset: 1px; }
.icon-btn:disabled { opacity: .3; cursor: not-allowed; }
.icon-btn.is-danger:hover:not(:disabled) { color: var(--p1); border-color: var(--p1); }

.timeline-del { grid-column: 3; }
.timeline-del .icon-btn { opacity: 0; }
.timeline li:hover .timeline-del .icon-btn,
.timeline-del .icon-btn:focus-visible { opacity: 1; }
/* Touch has no hover, so on a tablet the control would never appear at all. */
@media (hover: none) { .timeline-del .icon-btn { opacity: .55; } }
.timeline li:last-child { border-bottom: 0; }
.timeline time { font-family: var(--f-mono); font-size: var(--t-11); color: var(--ink-faint); padding-top: 2px; }

/* ── Priority header band: full-bleed status colour on the alarm page ─────── */

.band {
  display: flex;
  align-items: center;
  gap: var(--sp-4);
  padding: var(--sp-4) 28px;
  border-bottom: 1px solid var(--hairline-strong);
  background: var(--band-bg, var(--surface));
  flex-wrap: wrap;
}
/* The band is a calm neutral strip. Only the genuinely urgent priorities keep a
   coloured bottom edge; medium and low get a plain hairline — no yellow line —
   with priority still carried by the prio chip's colour + glyph. */
.band[data-priority="1"] { border-bottom: 2px solid var(--p1); }
.band[data-priority="2"] { border-bottom: 2px solid var(--p2); }
.band[data-priority="3"] { border-bottom-color: var(--hairline-strong); }
.band[data-priority="4"] { border-bottom-color: var(--hairline-strong); }
.band .actions { margin-left: auto; display: flex; gap: var(--sp-2); align-items: center; }

/* ── Camera ───────────────────────────────────────────────────────────────── */

.cam {
  position: relative;
  background: var(--surface-2);
  border: 1px solid var(--hairline);
  border-radius: var(--r);
  aspect-ratio: 16 / 9;
  display: grid;
  place-items: center;
  overflow: hidden;
}
.cam img { width: 100%; height: 100%; object-fit: cover; }
.cam .stamp {
  position: absolute;
  left: 6px; bottom: 6px;
  font-family: var(--f-mono);
  font-size: var(--t-11);
  /* This label sits on video, not on the page, so it stays light-on-dark in
     both themes — inheriting --ink would make it black text on a black scrim
     the moment the console is switched to light. */
  color: var(--scrim-ink);
  background: var(--scrim);
  border-radius: 5px;
  padding: 1px 6px;
}

/* ── Theme toggle ──────────────────────────────────────────────────────────
   The button offers the theme you would switch TO, so in dark it shows a sun.
   Driven purely by CSS off the same signals the palette uses, which means it
   is already correct at first paint — including for someone whose OS is set to
   light and who has never touched the toggle. */
.theme-toggle { padding: 0 var(--sp-2); }
.theme-toggle .t-light { display: none; }
.theme-toggle .t-dark { display: inline-flex; }

:root[data-theme="light"] .theme-toggle .t-dark { display: none; }
:root[data-theme="light"] .theme-toggle .t-light { display: inline-flex; }

@media (prefers-color-scheme: light) {
  :root:not([data-theme="dark"]) .theme-toggle .t-dark { display: none; }
  :root:not([data-theme="dark"]) .theme-toggle .t-light { display: inline-flex; }
}

/* ── Bars (reports, busiest sites) ────────────────────────────────────────── */

.bars { display: flex; flex-direction: column; gap: var(--sp-2); }
.bar-row { display: grid; grid-template-columns: minmax(min(90px, 100%), 26%) 1fr auto; gap: var(--sp-3); align-items: center; }
.bar-track { height: 6px; background: var(--surface-3); border-radius: var(--r-pill); overflow: hidden; min-width: 40px; }
.bar-fill { height: 100%; border-radius: var(--r-pill); background: var(--ink-faint); }
.bar-fill[data-priority="1"] { background: var(--p1); }
.bar-fill[data-priority="2"] { background: var(--p2); }
.bar-fill[data-priority="3"] { background: var(--p3); }
.bar-fill[data-priority="4"] { background: var(--p4); }
.bar-row .n { font-family: var(--f-mono); font-size: var(--t-11); color: var(--ink-dim); }

/* ══════════════════════════════════════════════════════════════════════════
   Queue workspace — the screen an operator stares at for twelve hours. The
   estate is usually near-empty (that is the job going well), so the queue is
   one card in a working dashboard, not a table marooned in a void.
   ════════════════════════════════════════════════════════════════════════ */

.queue-layout {
  display: grid;
  grid-template-columns: 1.6fr 1fr;
  gap: var(--sp-4);
  align-items: start;
}
.queue-side { display: flex; flex-direction: column; gap: var(--sp-4); min-width: 0; }

.queue-tools {
  display: flex;
  align-items: center;
  gap: var(--sp-3);
  padding: var(--sp-3) var(--sp-4);
  border-bottom: 1px solid var(--hairline);
  flex-wrap: wrap;
}
.queue-tools .input { flex: 1 1 200px; max-width: 360px; }
.queue-tools .count {
  margin-left: auto;
  font-family: var(--f-mono);
  font-size: var(--t-11);
  color: var(--ink-faint);
  white-space: nowrap;
}

/* Quick acknowledge from the row — quiet until the row is looked at. */
.qa { display: inline-flex; vertical-align: middle; margin-left: var(--sp-2); }
table.data .qa .btn { background: transparent; border-color: var(--hairline-strong); color: var(--ink-dim); }
table.data tr:hover .qa .btn, table.data tr.is-kfocus .qa .btn { background: var(--surface-3); color: var(--ink); }
table.data .qa .btn:hover { border-color: var(--ink-faint); color: var(--ink-bright); }

/* Age — how long it has waited, scannable without reading. Display face and
   ink weight, not hue: saturation stays reserved for priority. */
td.c-age { font-family: var(--f-mono); color: var(--ink-dim); }
tr[data-live="1"] td.c-age {
  font-family: var(--f-display);
  font-variation-settings: "wdth" 112;
  font-weight: 600;
  font-size: var(--t-15);
  color: var(--ink);
}
tr[data-live="1"][data-age-tier="2"] td.c-age { color: var(--ink-bright); }
tr[data-live="1"][data-age-tier="3"] td.c-age { color: var(--ink-bright); font-weight: 700; }
/* Age tiers brighten and embolden the age; the word "overdue" itself is the
   escalation tag (escalation.py), rendered from the same threshold the queue
   scope and the dashboard tile use — one definition, not two. */

/* Compact side lists — recently closed, overdue supervision */
.side-list { list-style: none; display: flex; flex-direction: column; }
.side-list > li { border-top: 1px solid var(--hairline); }
.side-list > li:first-child { border-top: 0; }
.side-list a, .side-list .side-row {
  display: grid;
  grid-template-columns: 46px minmax(0, 1fr);
  gap: var(--sp-3);
  padding: var(--sp-2) var(--sp-2);
  border-radius: var(--r);
  transition: background var(--fast) var(--ease);
}
.side-list a:hover { background: var(--hover); }
.side-list time { font-family: var(--f-mono); font-size: var(--t-11); color: var(--ink-faint); padding-top: 3px; }
.side-main { display: flex; flex-direction: column; gap: 1px; min-width: 0; }
.side-main > * { overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.side-main strong { font-weight: 600; font-size: var(--t-13); }

/* Keyboard reference — a spec sheet, not a tooltip */
.kbd-list {
  display: grid;
  grid-template-columns: auto minmax(0, 1fr);
  gap: var(--sp-2) var(--sp-3);
  align-items: center;
  color: var(--ink-dim);
  font-size: var(--t-13);
}
.kbd-list .keys { display: inline-flex; gap: 3px; justify-content: flex-end; }

/* ── Utilities ────────────────────────────────────────────────────────────── */

.row { display: flex; align-items: center; gap: var(--sp-3); }
.row.wrap { flex-wrap: wrap; }
.col { display: flex; flex-direction: column; gap: var(--sp-3); }
.push { margin-left: auto; }
.sep { color: var(--ink-faint); }
.nowrap { white-space: nowrap; }
.truncate { overflow: hidden; text-overflow: ellipsis; white-space: nowrap; max-width: 40ch; }
.mt-2 { margin-top: var(--sp-2); }
.mt-3 { margin-top: var(--sp-3); }
.mt-4 { margin-top: var(--sp-4); }
.mt-5 { margin-top: var(--sp-5); }
.sr-only {
  position: absolute; width: 1px; height: 1px; padding: 0; margin: -1px;
  overflow: hidden; clip: rect(0, 0, 0, 0); white-space: nowrap; border: 0;
}

/* ══════════════════════════════════════════════════════════════════════════
   Wall mode — the unattended ops-room screen, read from across the room.
   Four priority lanes; a clear board reads as deliberate, not broken.
   ════════════════════════════════════════════════════════════════════════ */

.wallboard { min-height: 100vh; display: flex; flex-direction: column; position: relative; z-index: 1; }

.wall-top {
  display: flex;
  align-items: center;
  gap: var(--sp-4);
  padding: var(--sp-4) 28px;
  border-bottom: 1px solid var(--hairline);
  flex-wrap: wrap;
}
.wall-top .wall-clock {
  font-family: var(--f-mono);
  font-size: var(--t-22);
  color: var(--ink);
  letter-spacing: 0.02em;
}

.wall-lanes {
  flex: 1;
  display: grid;
  grid-template-columns: repeat(4, minmax(0, 1fr));
  gap: var(--sp-4);
  padding: var(--sp-5) 28px 40px;
  align-items: start;
}
.wall-lane { display: flex; flex-direction: column; gap: var(--sp-3); min-width: 0; }
.lane-head {
  display: flex;
  align-items: baseline;
  gap: var(--sp-3);
  padding-bottom: var(--sp-2);
  border-bottom: 2px solid var(--hairline-strong);
}
.lane-head .lane-count {
  margin-left: auto;
  font-family: var(--f-display);
  font-variation-settings: "wdth" 118;
  font-weight: 700;
  font-size: var(--t-26);
  letter-spacing: -0.02em;
  color: var(--ink-faint);
}
/* Lane identity: position (fixed order) + glyph + colour, never colour alone. */
.lane-head[data-priority="1"] { border-bottom-color: var(--p1); }
.lane-head[data-priority="2"] { border-bottom-color: var(--p2); }
.lane-head[data-priority="3"] { border-bottom-color: var(--p3); }
.lane-head[data-priority="4"] { border-bottom-color: var(--p4); }
.lane-head[data-priority="1"] .lane-count:not([data-zero]) { color: var(--p1); }
.lane-head[data-priority="2"] .lane-count:not([data-zero]) { color: var(--p2); }
.lane-head[data-priority="3"] .lane-count:not([data-zero]) { color: var(--p3); }
.lane-head[data-priority="4"] .lane-count:not([data-zero]) { color: var(--ink); }
.lane-clear {
  padding: var(--sp-5) 0;
  text-align: center;
  font-size: var(--t-11);
  text-transform: uppercase;
  letter-spacing: 0.1em;
  font-weight: 600;
  color: var(--ink-faint);
}

.wall { display: grid; gap: var(--sp-4); grid-template-columns: repeat(auto-fill, minmax(min(320px, 100%), 1fr)); align-content: start; }
/* Inside a lane the cards simply stack. */
.wall-lane .wall { grid-template-columns: minmax(0, 1fr); }

.wall-card {
  border: 1px solid var(--hairline-strong);
  border-left-width: 4px;
  border-radius: var(--r-lg);
  padding: var(--sp-4) var(--sp-4) var(--sp-4) var(--sp-4);
  background: var(--surface);
  box-shadow: var(--shadow);
  display: flex;
  flex-direction: column;
  gap: var(--sp-2);
  transition: border-color var(--fast) var(--ease);
}
.wall-card:hover { border-color: var(--ink-faint); }
.wall-card[data-priority="1"] { border-left-color: var(--p1); background: var(--p1-bed); }
.wall-card[data-priority="2"] { border-left-color: var(--p2); background: var(--p2-bed); }
.wall-card[data-priority="3"] { border-left-color: var(--p3); background: var(--p3-bed); }
.wall-card[data-priority="4"] { border-left-color: var(--p4); }
.wall-card .site {
  font-family: var(--f-display);
  font-variation-settings: "wdth" 112;
  font-weight: 700;
  font-size: var(--t-22);
  line-height: 1.15;
  overflow-wrap: anywhere;
}
.wall-card .age {
  font-family: var(--f-display);
  font-variation-settings: "wdth" 118;
  font-weight: 700;
  font-size: var(--t-32);
  letter-spacing: -0.03em;
  line-height: 1;
}
/* The board's one instrument-panel moment: a P1 age is unmissable. */
.wall-card[data-priority="1"] .age { font-size: var(--t-44); }

/* A clear board is most of the job. It must read as reassuring and deliberate,
   never as a page that failed to load. */
.wall-empty {
  flex: 1;
  display: grid;
  place-content: center;
  text-align: center;
  gap: var(--sp-3);
  padding: var(--sp-7);
  color: var(--ink-dim);
}
.wall-empty .display { font-size: var(--t-44); color: var(--ink); }
.wall-empty .mono { font-size: var(--t-15); }

/* ── Login — the CoreM8 doorway: blurred orbs, one card ───────────────────── */

.auth {
  min-height: 100vh;
  display: grid;
  place-items: center;
  padding: var(--sp-5);
  position: relative;
  z-index: 1;
  overflow: hidden;
}
.auth .orb { position: absolute; border-radius: 50%; filter: blur(80px); opacity: 0.45; pointer-events: none; }
.auth .orb.a { width: 460px; height: 460px; background: #7c6cff; top: -160px; right: -120px; }
.auth .orb.b { width: 380px; height: 380px; background: #ff6cab; bottom: -160px; left: -100px; opacity: 0.3; }
.auth-card {
  width: min(400px, 100%);
  border: 1px solid var(--hairline-strong);
  background: var(--surface);
  border-radius: 22px;
  padding: var(--sp-6);
  box-shadow: var(--shadow-lg);
  position: relative;
  z-index: 2;
}
.auth-note {
  margin-top: var(--sp-6);
  padding-top: var(--sp-4);
  border-top: 1px solid var(--hairline);
  font-size: var(--t-11);
  line-height: 1.6;
  color: var(--ink-faint);
}

/* ── Responsive: the rail becomes a drawer; nothing is ever cut off ───────── */

/* List-row form: the same table markup, reflowed as readable rows. The queue
   card carries it at every width (`.is-list`) because a seven-column table
   cannot fit a half-width card without clipping the State column — and any
   `.data--cards` table takes the same form under 700px. Age leads the row's
   right edge; the exact received timestamp is on the alarm page. */
table.is-list, table.is-list tbody { display: block; width: 100%; }
table.is-list thead { display: none; }
/* The row has a fixed shape, not an order of flex items. Reflowed with
   `flex-wrap` the cells landed wherever the content pushed them, so a long
   description shoved the state onto its own line and two rows never looked
   alike. A grid means every row reads in the same places:

       ▌ ◆  SITE NAME  code                      zone      2m 04s
             FIRE  E110  sprinkler flow, zone 4            pending [Ack]

   Site first because it is what an operator recognises; event and its detail on
   the second line; age hard right where the eye returns to it. */
table.is-list tbody tr {
  display: grid;
  grid-template-columns: 3px auto minmax(0, 1fr) auto auto;
  grid-template-areas:
    "gut pri site  zone  age"
    "gut pri event event state";
  align-items: baseline;
  column-gap: var(--sp-3);
  row-gap: 3px;
  padding: var(--sp-3) var(--sp-4) var(--sp-3) 0;
  border-bottom: 1px solid var(--hairline);
}
table.is-list tbody tr:last-child { border-bottom: 0; }
table.is-list tbody tr:hover { background: var(--hover); }
table.is-list tbody td {
  display: block; height: auto; padding: 0; border: 0;
  white-space: normal; min-width: 0;
}
table.is-list td.gutter {
  grid-area: gut; align-self: stretch;
  width: 3px; border: 0; border-radius: 0 2px 2px 0;
}
table.is-list td.c-pri   { grid-area: pri; }
table.is-list td.c-site  { grid-area: site; font-size: var(--t-15); }
table.is-list td.c-site strong { font-weight: 600; }
table.is-list td.c-zone  { grid-area: zone; font-size: var(--t-11); color: var(--ink-faint);
                           text-align: right; white-space: nowrap; }
table.is-list td.c-age   { grid-area: age; text-align: right; white-space: nowrap;
                           font-variant-numeric: tabular-nums; }
table.is-list td.c-event { grid-area: event; overflow-wrap: anywhere; }
table.is-list td.c-state { grid-area: state; text-align: right; white-space: nowrap; }
table.is-list td.c-received { display: none; }

/* The event line: type and code carry the weight, the description is context. */
table.is-list .event-head { display: inline-flex; gap: var(--sp-2); align-items: baseline; }
table.is-list .event-head b { font-weight: 500; }
table.is-list .event-desc {
  display: inline; margin-left: var(--sp-2); color: var(--ink-faint);
}

/* Age is the thing you triage on, so it gets the size — and only while the
   alarm is still open. A closed row does not need a big clock. */
.queue-layout tr[data-live="1"] td.c-age .age-rel { font-size: var(--t-18); color: var(--ink); }
table.is-list td.c-age { display: block; }
table.is-list .age-abs { display: block; }
table.is-list .state-by { display: block; }

/* Quick-ack sits under the state chip rather than beside it, so the chip stays
   the thing you read and the button is where your hand already is. */
table.is-list .qa { margin-top: 4px; }

@media (max-width: 640px) {
  table.is-list tbody tr {
    grid-template-columns: 3px auto minmax(0, 1fr) auto;
    grid-template-areas:
      "gut pri site  age"
      "gut pri event event"
      "gut pri zone  state";
  }
  table.is-list td.c-zone { text-align: left; }
}

.rail-toggle { display: none; }

@media (max-width: 1160px) {
  .queue-layout { grid-template-columns: minmax(0, 1fr); }
  .wall-lanes { grid-template-columns: repeat(2, minmax(0, 1fr)); }
}

@media (max-width: 900px) {
  .shell {
    grid-template-columns: minmax(0, 1fr);
    grid-template-areas: "brand" "annunciator" "main";
    grid-template-rows: var(--annunciator-h) auto minmax(0, 1fr);
  }
  .brand { border-right: 0; justify-content: space-between; position: sticky; top: 0; }
  .annunciator { position: static; }
  .rail-toggle {
    display: inline-flex; align-items: center; justify-content: center;
    width: 34px; height: 34px; border: 1px solid var(--hairline-strong);
    border-radius: var(--r); background: var(--surface-2); cursor: pointer;
  }
  .rail {
    position: fixed;
    inset: 0 auto 0 0;
    width: min(280px, 85vw);
    height: 100vh;
    top: 0;
    z-index: 60;
    padding-top: var(--sp-5);
    transform: translateX(-102%);
    transition: transform var(--fast) var(--ease);
    border-right: 1px solid var(--hairline-strong);
    background: var(--surface);
    box-shadow: var(--shadow-lg);
  }
  .rail.is-open { transform: none; }
  .annunciator { gap: var(--sp-4); padding: 0 var(--sp-4); }
  /* The clock and the signed-in name are the first things a phone can live
     without; the lamps that carry alarm state stay. Scroll catches the rest. */
  .annunciator { overflow-x: auto; }
  .split, .grid-2, .grid-3 { grid-template-columns: minmax(0, 1fr); }
  .grid-4 { grid-template-columns: repeat(2, minmax(0, 1fr)); }
  .split > aside { position: static; }
  .view-body { padding: var(--sp-4); }
  .view-head { padding: var(--sp-4); }
  .band { padding: var(--sp-4); }
  /* The header actions wrap onto their own line rather than being clipped. */
  .view-head .actions, .band .actions { margin-left: 0; width: 100%; flex-wrap: wrap; }
  .wall-lanes { grid-template-columns: minmax(0, 1fr); padding: var(--sp-4); }
  .wall-top { padding: var(--sp-4); }
}

@media (max-width: 700px) {
  .annunciator .lamp--clock, .annunciator .tool-divider,
  .annunciator .account-id { display: none; }
  .grid-4 { grid-template-columns: minmax(0, 1fr); }
  .queue-tools .input { max-width: none; flex: 1 1 100%; }
  .queue-tools .count { margin-left: 0; }

  /* Rows become readable cards. Same markup, no duplication: the cells
     reflow, the gutter becomes the card's leading edge, nothing is clipped. */
  table.data--cards, table.data--cards tbody { display: block; width: 100%; }
  table.data--cards thead { display: none; }
  table.data--cards tbody tr {
    display: flex;
    flex-wrap: wrap;
    align-items: baseline;
    column-gap: var(--sp-3);
    row-gap: 2px;
    position: relative;
    padding: var(--sp-3) var(--sp-4);
    padding-left: calc(var(--sp-4) + 3px);
    border-bottom: 1px solid var(--hairline);
  }
  table.data--cards tbody tr:last-child { border-bottom: 0; }
  table.data--cards tbody td {
    display: block;
    height: auto;
    width: auto; max-width: none;   /* desktop column hints (width: 1%) would crush a flex item */
    padding: 0;
    border: 0;
    white-space: normal;
    min-width: 0;
  }
  /* Desktop column hints (width: 1%) are set on higher-specificity selectors;
     in the card form every cell sizes to its content. */
  table.data.data--cards tbody td, table.data.data--cards tbody td[class] { width: auto; max-width: none; }
  table.data--cards td.gutter {
    position: absolute;
    top: 0; bottom: 0; left: 0;
    width: 3px;
    border-left-width: 3px;
  }
  /* Queue card: priority · age · state on the first line, then the substance. */
  table.data--cards td.c-pri { order: 1; }
  table.data--cards td.c-age { order: 2; margin-left: auto; }
  table.data--cards td.c-state { order: 3; }
  table.data--cards td.c-site { order: 4; flex: 1 0 100%; }
  table.data--cards td.c-event { order: 5; flex: 1 0 100%; overflow-wrap: anywhere; }
  table.data--cards td.c-zone { order: 6; font-size: var(--t-11); }
  table.data--cards td.c-received { order: 7; font-size: var(--t-11); }
  /* Signals card: time · code first, then site, then what it said. */
  table.data--cards td.c-time { order: 1; }
  table.data--cards td.c-code { order: 2; margin-left: auto; }
  table.data--cards td.c-type { order: 5; }
  table.data--cards td.c-desc { order: 6; flex: 1 0 100%; overflow-wrap: anywhere; font-size: var(--t-11); color: var(--ink-faint); }
  table.data--cards td.c-state { flex: none; white-space: nowrap; }
}


/* Each option renders its own sample at its own real size — you choose by
   reading it, not by reading the word "Large". */


/* ══ WALL STATS ═══════════════════════════════════════════════════════════
   Sized for the far side of the room, not for a desk. The numerals use the
   display face at a width the type scale does not otherwise reach, and the
   labels stay small — from ten feet you read the number and the colour, and the
   word is there for whoever walks up to it. */

.wall-stats {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(min(150px, 100%), 1fr));
  gap: var(--sp-3);
  padding: var(--sp-5) var(--sp-5) var(--sp-4);
}
.wstat {
  display: flex; flex-direction: column; gap: 2px;
  padding: var(--sp-3) var(--sp-4);
  border: 1px solid var(--hairline);
  border-radius: var(--r-lg);
  background: var(--panel);
  box-shadow: var(--shadow);
}
.wstat-n {
  font-family: var(--f-display);
  font-size: clamp(2rem, 4.2vw, 3.4rem);
  font-weight: 700;
  letter-spacing: -0.035em;
  line-height: 1;
  font-variant-numeric: tabular-nums;
  color: var(--ink);
}
.wstat-l {
  font-family: var(--f-mono);
  font-size: var(--t-11);
  letter-spacing: 0.12em;
  text-transform: uppercase;
  color: var(--ink-faint);
}
/* Colour only when there is something to say. A red 0 next to a live 40 says
   the opposite of what is true. */
.wstat.is-hot { border-color: var(--p1); background: var(--p1-bed); }
.wstat.is-hot .wstat-n { color: var(--p1); }
.wstat.is-warn .wstat-n { color: var(--p2); }
.wstat.is-test { border-color: var(--test); }
.wstat.is-test .wstat-n { color: var(--test); }

/* ── Health band ──────────────────────────────────────────────────────────
   The one thing a wall of alarm cards cannot tell you: whether the quiet is
   real. Three watchdogs and the device checks, in a row, always visible. */
.wall-health {
  display: flex; align-items: center; gap: var(--sp-4); flex-wrap: wrap;
  margin: 0 var(--sp-5) var(--sp-4);
  padding: var(--sp-3) var(--sp-4);
  border: 1px solid var(--hairline);
  border-radius: var(--r-lg);
  background: var(--surface);
}
.wh-item { display: flex; align-items: baseline; gap: var(--sp-2); font-size: var(--t-15); }
.wh-dot {
  width: 10px; height: 10px; border-radius: 50%; flex: none;
  background: var(--ok); box-shadow: 0 0 12px var(--ok);
  align-self: center;
}
.wh-item.is-fault .wh-dot { background: var(--p1); box-shadow: 0 0 14px var(--p1); }
.wh-item.is-unknown .wh-dot { background: var(--p4); box-shadow: none; }
.wh-name { color: var(--ink-dim); }
.wh-val { font-family: var(--f-mono); font-weight: 500; color: var(--ok); }
.wh-item.is-fault .wh-val { color: var(--p1); }
.wh-item.is-unknown .wh-val { color: var(--ink-faint); }
.wh-code { font-family: var(--f-mono); font-size: var(--t-11); color: var(--ink-faint); }

@media (max-width: 900px) {
  .wall-stats { padding: var(--sp-4); }
  .wall-health { margin: 0 var(--sp-4) var(--sp-3); }
}

/* ── Queue rows ───────────────────────────────────────────────────────────
   The queue renders as cards at every width (.is-list), not as a table — so
   column widths do not apply and setting them squashes flex items into a mess.
   These rules describe the CARD.

   Age reads relative, with the absolute time beside it for quoting. The
   description is context under the event, not the headline it had become. */

/* Only meaningful if the table is ever rendered as a real table. */
table:not(.is-list) .th-age,
table:not(.is-list) .th-state,
table:not(.is-list) td.c-age,
table:not(.is-list) td.c-state,
table:not(.is-list) td.c-zone,
table:not(.is-list) td.c-pri { width: 1%; white-space: nowrap; }

.age-rel { font-family: var(--f-mono); }
.age-abs { font-size: var(--t-11); color: var(--ink-faint); }
/* On a card the two sit side by side; stacking them made the row taller than
   its neighbours and broke the rhythm down the list. */
table.is-list td.c-age { display: flex; align-items: baseline; gap: var(--sp-2); }

.event-head { display: flex; align-items: center; gap: var(--sp-2); flex-wrap: wrap; }
.event-desc { display: block; margin-top: 1px; font-size: var(--t-11); color: var(--ink-faint); }
table:not(.is-list) .event-desc {
  overflow: hidden; text-overflow: ellipsis; white-space: nowrap; max-width: 46ch;
}
.state-by { display: block; font-size: var(--t-11); color: var(--ink-faint); margin-top: 1px; }
table.is-list td.c-state { display: flex; align-items: baseline; gap: var(--sp-2); }
table.is-list .state-by { display: inline; margin-top: 0; }

/* ── Filling the counter ──────────────────────────────────────────────────
   The grid stretches every tile to the tallest. The label / numeral / meta
   block anchors at the top of every tile, so the numerals sit on one line
   across the row; whatever a tile carries beneath (split bars, the oldest
   waiting, a sparkline) takes the slack at the bottom. */
.counter { justify-content: flex-start; }
.counter .mini-split, .counter .kpi-aside { margin-top: auto; }

/* The split bars are a second block of information, not a continuation of the
   meta line — they need the separation to read as one. */
.mini-split {
  list-style: none; display: flex; flex-direction: column; gap: var(--sp-2);
  margin-top: var(--sp-4); padding-top: var(--sp-3);
  border-top: 1px solid var(--hairline);
}
.mini-split li { display: grid; grid-template-columns: 30px minmax(0, 1fr) 24px;
                 align-items: center; gap: var(--sp-3); }
.ms-lab .glyph { font-size: var(--t-11); }
.ms-track { height: 6px; border-radius: var(--r-pill); background: var(--hover); overflow: hidden; }
.ms-fill { display: block; height: 100%; border-radius: var(--r-pill); background: var(--p4); }
.mini-split .p1 .ms-fill { background: var(--p1); }
.mini-split .p2 .ms-fill { background: var(--p2); }
.mini-split .p3 .ms-fill { background: var(--p3); }
.mini-split .is-zero .ms-n { color: var(--ink-faint); }
.ms-n { font-size: var(--t-11); color: var(--ink-dim); text-align: right; }

.kpi-aside { margin-top: var(--sp-3); padding-top: var(--sp-3);
             border-top: 1px solid var(--hairline); display: flex;
             align-items: baseline; gap: var(--sp-2); }
.kpi-aside-v { font-size: var(--t-15); color: var(--ink); font-variant-numeric: tabular-nums; }

/* Hooks used by the queue markup. */
.queue-table { width: 100%; }
.ua-assign, .ua-block { display: flex; flex-direction: column; gap: var(--sp-1); }

/* ── Map links ────────────────────────────────────────────────────────────
   An address is a place, so it behaves like one. The arrow marks that it
   leaves the console — an operator following it mid-alarm gets a new tab, not
   a lost alarm. */
.maplink { color: inherit; text-decoration: none; border-bottom: 1px dotted var(--hairline-strong); }
.maplink:hover { color: var(--accent); border-bottom-color: var(--accent); }
.maplink:focus-visible { outline: 2px solid var(--accent); outline-offset: 2px; }
.maplink-i { font-size: 0.85em; margin-left: 3px; opacity: .6; }
.maplink:hover .maplink-i { opacity: 1; }

/* ── Login: Microsoft Entra sign-in (shown only when configured + readable) ── */
.sso-split { display: flex; align-items: center; gap: var(--sp-3); margin: var(--sp-4) 0 var(--sp-3); color: var(--ink-faint); font-size: var(--t-11); text-transform: uppercase; letter-spacing: .08em; }
.sso-split::before, .sso-split::after { content: ""; flex: 1; border-top: 1px solid var(--hairline); }
.sso-btn { display: inline-flex; align-items: center; justify-content: center; gap: var(--sp-2); }


/* ── Dialogs ──────────────────────────────────────────────────────────────
   A dialog is one action with room to read it: a 560px card, a plain header,
   and a vertical rhythm the CSS owns — the markup carries no spacing classes,
   so every dialog breathes the same way. Controls are 44px+ because the hand
   on them belongs to an operator under pressure, possibly on glass. */
.dlg { position: fixed; inset: 0; z-index: 80; display: grid; place-items: center; padding: var(--sp-4); }
.dlg-backdrop { position: absolute; inset: 0; background: rgba(8,4,24,.72);
  -webkit-backdrop-filter: blur(8px); backdrop-filter: blur(8px); }
.dlg-box {
  position: relative; width: 100%; max-width: 560px; max-height: 90dvh; overflow: auto;
  background: var(--panel); border: 1px solid var(--hairline-strong);
  border-radius: var(--r-lg); box-shadow: var(--shadow-lg);
}
.dlg-head { display: flex; gap: var(--sp-3); align-items: flex-start;
  padding: var(--sp-5) var(--sp-5) var(--sp-4); }
.dlg-head h3 { font-size: var(--t-20, 1.25rem); font-weight: 650; letter-spacing: -.02em; }
.dlg-head p { margin-top: 4px; color: var(--ink-dim); font-size: var(--t-13); max-width: 44ch; }
.dlg-head .icon-btn { margin-left: auto; flex: none; }
.dlg-body { padding: 0 var(--sp-5) var(--sp-5); }
body.has-modal { overflow: hidden; }

/* The rhythm: a label opens a field group; whatever follows sits close under
   it; the next label brings the next group after a full beat. */
.dlg-body .field-label { margin: var(--sp-5) 0 var(--sp-2); }
.dlg-body > .field-label:first-child,
.dlg-body form > .field-label:first-child { margin-top: 0; }
.dlg-body .input, .dlg-body .select, .dlg-body textarea.input {
  width: 100%; min-height: 44px; font-size: var(--t-15); padding: 0 var(--sp-3);
}
.dlg-body textarea.input { padding: var(--sp-2) var(--sp-3); min-height: 0; line-height: 1.5; }
.dlg-body .check { display: flex; align-items: flex-start; gap: var(--sp-2);
  padding: var(--sp-2) 0; font-size: var(--t-15); }
.dlg-body .check input { margin-top: 4px; }
.dlg-body .btn.is-primary.is-block { margin-top: var(--sp-5); min-height: 48px; font-size: var(--t-15); }
.dlg-body > a.btn.is-primary.is-block:first-child { margin-top: 0; min-height: 52px; }
.dlg-body .faint { margin-top: var(--sp-3); }

.btn.is-block { display: flex; width: 100%; justify-content: center; }

/* Outcomes: seven honest answers, none clipped. The pills grow to their text
   and wrap to two lines rather than spilling out of their border. */
.outcome-grid { display: grid; grid-template-columns: repeat(auto-fit, minmax(min(150px, 100%), 1fr));
  gap: var(--sp-2); }
.outcome-btn { min-height: 46px; height: auto; padding: var(--sp-2) var(--sp-3);
  justify-content: center; text-align: center; white-space: normal; line-height: 1.25;
  font-size: var(--t-14, 0.875rem); }
.outcome-btn:hover { border-color: var(--accent); background: var(--accent-soft); }
.faint.sm { font-size: var(--t-11); }

/* No JavaScript: dialogs are ordinary sections at the foot of the page. */
html:not(.has-dialogs) .dlg { position: static; display: block; padding: 0; margin-bottom: var(--sp-4); }
html:not(.has-dialogs) .dlg[hidden] { display: block; }
html:not(.has-dialogs) .dlg-backdrop { display: none; }
html:not(.has-dialogs) .dlg-box { max-width: none; max-height: none; }
html:not(.has-dialogs) .dialogs { padding: 0 var(--sp-5) var(--sp-6); }

/* ── Dialogs on a phone: a bottom sheet, not a floating card ───────────── */
@media (max-width: 640px) {
  html.has-dialogs .dlg { place-items: end stretch; padding: 0; }
  html.has-dialogs .dlg-box {
    max-width: none; max-height: 92dvh;
    border-radius: var(--r-lg) var(--r-lg) 0 0;
    border-bottom: 0;
    padding-bottom: env(safe-area-inset-bottom, 0px);
  }
}


/* ── Menu: a button that opens a small list of choices ─────────────────── */
.menu { position: relative; }
.menu > summary { list-style: none; cursor: pointer; user-select: none; }
.menu > summary::-webkit-details-marker { display: none; }
.menu[open] > summary { background: var(--accent-fill-hover, var(--accent)); }
.menu-pop {
  position: absolute; right: 0; top: calc(100% + 6px); z-index: 60; min-width: 240px;
  background: var(--panel); border: 1px solid var(--hairline-strong);
  border-radius: var(--r-lg); box-shadow: var(--shadow-lg); padding: 6px; display: flex; flex-direction: column;
}
.menu-pop a { display: flex; flex-direction: column; gap: 1px; padding: 8px 10px;
  border-radius: var(--r-sm); color: inherit; text-decoration: none; }
.menu-pop a:hover { background: var(--hover); }
.menu-pop a b { font-size: var(--t-13); }
.menu-pop a span { font-size: var(--t-11); color: var(--ink-faint); }

/* ── Command palette ──────────────────────────────────────────────────────
   Ctrl+K. Sits above everything, centred near the top so the results grow
   downward into the eye's path rather than pushing the input around. Uses the
   same surface/line tokens as the rest of the console so it inherits both
   themes without a second palette of its own. */
.kbd-hint { margin-left: 6px; font-size: var(--t-11); color: var(--ink-faint); }
@media (max-width: 900px) { .kbd-hint { display: none; } }

.palette { position: fixed; inset: 0; z-index: 90; display: grid;
  align-items: start; justify-items: center; padding: 10vh 16px 16px; }
.palette-scrim { position: absolute; inset: 0; background: rgba(6, 2, 24, 0.55);
  backdrop-filter: blur(2px); }
.palette-card { position: relative; width: min(640px, 100%);
  background: var(--surface-2); border: 1px solid var(--line-2);
  border-radius: var(--r-lg, 14px); box-shadow: var(--shadow-lg); overflow: hidden; }
.palette-input { width: 100%; padding: 16px 18px; border: 0; outline: 0;
  background: transparent; color: var(--ink); font: inherit;
  font-size: var(--t-15, 1rem); border-bottom: 1px solid var(--line); }
.palette-input::placeholder { color: var(--ink-faint); }

.palette-results { list-style: none; margin: 0; padding: 6px;
  max-height: min(52vh, 420px); overflow-y: auto; }
.palette-item { display: flex; gap: 10px; align-items: baseline; padding: 9px 12px;
  border-radius: var(--r-sm); cursor: pointer; }
.palette-item.is-active { background: var(--hover); }
.palette-kind { flex: none; min-width: 6ch; font-size: var(--t-11);
  color: var(--ink-faint); text-transform: uppercase; letter-spacing: .04em; }
.palette-text { display: flex; flex-direction: column; gap: 1px; min-width: 0; }
.palette-text b { font-size: var(--t-13); }
.palette-sub { font-size: var(--t-11); color: var(--ink-faint);
  overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }

.palette-foot { display: flex; gap: 14px; padding: 8px 14px;
  border-top: 1px solid var(--line); font-size: var(--t-11); }

/* The palette is a jump, not an animation. Nothing here depends on motion. */
