/* Battery LCM — soft-light industrial theme.
   Dense, high-contrast, utilitarian. Built for operators staring at it all day.
   Canvas is #f6f7f9, cards are white, one energy accent (amber).

   Two rules future sessions keep breaking:
   1. Amber splits into FILL vs INK. --accent (#ffb000) is a FILL only: button
      backgrounds, dots, chart areas. It measures 1.83:1 on white, so it must
      NEVER be used for text or thin strokes — use --accent-ink for those.
   2. The operator wallboard (bottom of this file) is a TV surface. It was
      converted to light deliberately; alarm states there use solid tints and
      dark ink, not thin coloured outlines, so they survive across a room. */

:root {
  color-scheme: light;

  /* ---- surfaces ---- */
  --bg:          #f6f7f9;   /* app canvas */
  --bg-2:        #eef1f5;   /* recessed fill: disabled fields, seg tracks */
  --panel:       #ffffff;   /* cards, sidebar, login card */
  --panel-2:     #f3f4f6;   /* nested surface inside a white card */

  /* ---- borders ---- */
  --line:        #e5e7eb;   /* hairline: card edge, table rows, dividers */
  --line-2:      #d1d5db;   /* visible edge: inputs, tags, metric tiles */
  --line-strong: #8a94a1;   /* 3.07:1 — focused/invalid/dashed drawable edges */

  /* ---- text ---- */
  --ink:       #111827;
  --ink-dim:   #4b5563;
  --ink-mute:  #5f6b7a;     /* clears white, canvas AND panel-2 at >= 4.5:1 */

  /* ---- brand amber: FILL vs INK. Never interchange. ---- */
  --accent:        #ffb000;
  --accent-ink:    #8a5a00;
  --accent-border: #d97706;
  --accent-soft:   #fff4d6;
  --on-accent:     #1a1205;

  --accent-2: #0b6bcb;      /* links */

  /* ---- status: fill, ink, tint plate, tint border ---- */
  --ok:        #10b981;  --ok-ink:   #047857;  --ok-bg:   #ecfdf5;  --ok-line:   #a7f3d0;
  --warn:      #f59e0b;  --warn-ink: #92400e;  --warn-bg: #fffbeb;  --warn-line: #fde68a;
  --crit:      #ef4444;  --crit-ink: #b42318;  --crit-bg: #fef2f2;  --crit-line: #fca5a5;
  --info-bg:   #f3f4f6;  --info-line: #d1d5db;

  /* ---- sidebar ---- */
  --sidebar-w:          248px;
  --sidebar-bg:         #ffffff;
  --sidebar-line:       #e5e7eb;
  --sidebar-ink:        #4b5563;
  --sidebar-ink-active: #111827;
  --sidebar-active-bg:  #fff4d6;

  --radius:    10px;
  --radius-sm: 6px;

  /* ---- layout system: --ctl-h is THE control height. input/select/button all
     compute to 39px, so anything in a control row is pinned to it and
     `align-items:end` lines up baselines instead of just box bottoms. */
  --ctl-h:        39px;
  --measure:      600px;   /* single-column form measure */
  --measure-wide: 880px;   /* .form-grid measure */
  --prose:        74ch;    /* readable measure for description paragraphs */
  --gutter:       16px;    /* == .card padding; page headers align to it */
  --shadow:    0 1px 2px rgba(16,24,40,.04), 0 1px 3px rgba(16,24,40,.06);
  --shadow-lg: 0 10px 30px rgba(16,24,40,.08), 0 2px 6px rgba(16,24,40,.05);

  --mono: ui-monospace, "SF Mono", "JetBrains Mono", "Cascadia Mono", Menlo, Consolas, monospace;
  --sans: ui-sans-serif, system-ui, -apple-system, "Segoe UI", Roboto, Helvetica, Arial, sans-serif;
}

* { box-sizing: border-box; }

html, body {
  margin: 0;
  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-2); text-decoration: none; }
a:hover { text-decoration: underline; }
a:focus-visible, .btn-link:focus-visible, .nav-item:focus-visible, button:focus-visible {
  outline: 2px solid var(--accent-border); outline-offset: 2px;
}

h1 { font-size: 20px; margin: 0 0 4px; letter-spacing: -0.01em; }
h2 { font-size: 14px; margin: 0 0 12px; text-transform: uppercase; letter-spacing: 0.06em; color: var(--ink-dim); font-weight: 600; }

/* ---- Sidebar (replaces the old horizontal top bar) ---- */
.sidebar {
  position: fixed; inset: 0 auto 0 0; width: var(--sidebar-w);
  display: flex; flex-direction: column;
  padding: 18px 14px 14px;
  background: var(--sidebar-bg);
  border-right: 1px solid var(--sidebar-line);
  overflow: hidden; z-index: 20;
}
.sidebar .brand {
  display: block; padding: 2px 10px 16px; margin-bottom: 10px;
  border-bottom: 1px solid var(--sidebar-line);
}
.sidebar .brand-link { display: block; }
.sidebar .wordmark { display: block; width: 100%; max-width: 200px; height: auto; }
.sidebar .brand-app {
  display: block; margin-top: 8px; font-size: 11px; font-weight: 600;
  letter-spacing: .08em; text-transform: uppercase; color: var(--ink-mute);
}

.mainnav { display: flex; flex-direction: column; gap: 2px;
           flex: 1 1 auto; min-height: 0; overflow-y: auto; }
.nav-group + .nav-group { margin-top: 14px; }
.nav-group-label {
  padding: 6px 10px 4px; font-size: 11px; font-weight: 600;
  text-transform: uppercase; letter-spacing: .07em; color: var(--ink-mute);
}
.nav-item {
  display: block; padding: 7px 10px; border-radius: 8px;
  color: var(--sidebar-ink); font-weight: 500; text-decoration: none;
  transition: background .12s, color .12s;
}
.nav-item:hover { background: var(--panel-2); color: var(--sidebar-ink-active); text-decoration: none; }
.nav-item.is-active {
  background: var(--sidebar-active-bg); color: var(--accent-ink);
  font-weight: 600; box-shadow: inset 3px 0 0 var(--accent);
}

.user {
  display: flex; flex-direction: column; align-items: flex-start; gap: 6px;
  margin-top: auto; padding: 14px 10px 0; border-top: 1px solid var(--sidebar-line);
}
.user-name { font-weight: 600; font-size: 13px; }
.user-role { color: var(--ink-mute); font-size: 12px; padding: 2px 8px; border: 1px solid var(--line); border-radius: 999px; }
.logout { margin: 0; }

/* ---- Mobile bar (hidden on desktop; hosts the hamburger) ---- */
.topbar {
  display: none; align-items: center; gap: 12px; height: 52px; padding: 0 16px;
  background: var(--panel); border-bottom: 1px solid var(--line);
  position: sticky; top: 0; z-index: 10;
}
.topbar-title { font-weight: 600; }
.nav-toggle {
  display: inline-flex; align-items: center; justify-content: center;
  width: 36px; height: 36px; padding: 0; font-size: 16px;
  background: var(--panel-2); color: var(--ink);
  border: 1px solid var(--line-2); border-radius: 8px; cursor: pointer;
}
.skip-link { position: absolute; left: -9999px; }
.skip-link:focus {
  left: 8px; top: 8px; z-index: 50; background: var(--panel);
  border: 1px solid var(--line-2); padding: 8px 12px; border-radius: 8px;
}

/* Brand chip — kept because the wallboard headers still use it. */
.brand { display: flex; align-items: center; gap: 10px; font-weight: 700; letter-spacing: -0.01em; }
.brand-mark { width: 14px; height: 14px; border-radius: 3px; background: var(--accent); }

/* ---- Layout ---- */
.container {
  max-width: 1320px; margin: 0 auto; padding: 24px 28px;
  display: flex; flex-direction: column; gap: 16px;
}
/* The sidebar offset lives on the body so the content column centres in what
   is left of the viewport instead of hugging the sidebar. */
body.app      { padding-left: var(--sidebar-w); }
body.app-bare { padding-left: 0; }
.app-bare .container { margin-left: auto; }

/* Intrinsic grids collapse when a track no longer fits, so the old 820px
   breakpoint (which ignored the 248px sidebar) is gone. */
.grid-2 { display: grid; grid-template-columns: repeat(auto-fit, minmax(380px, 1fr)); gap: 16px; }
.grid-4 { display: grid; grid-template-columns: repeat(auto-fit, minmax(190px, 1fr)); gap: 16px; }
.grid-2 > *, .grid-4 > * { min-width: 0; }

@media (max-width: 900px) {
  .topbar { display: flex; }
  .sidebar { transform: translateX(-100%); transition: transform .18s ease; }
  .sidebar.open { transform: none; box-shadow: 0 10px 40px rgba(16,24,40,.18); }
  body.app { padding-left: 0; }
  .container { margin-left: 0; padding: 16px; }
}

.card {
  background: var(--panel);
  border: 1px solid var(--line);
  border-radius: var(--radius);
  padding: var(--gutter);
  box-shadow: var(--shadow);
  overflow-x: auto;               /* wide tables scroll inside the card at EVERY width */
}
.card > *:first-child { margin-top: 0; }
.card > *:last-child  { margin-bottom: 0; }

/* align-items:center centres the MARGIN box, so h1's 4px and h2's 12px bottom
   margin pushed every card title above the control beside it. */
.card-head {
  display: flex; align-items: center; justify-content: space-between;
  gap: 12px; flex-wrap: wrap; margin-bottom: 12px;
}
.card-head > h1, .card-head > h2, .card-head > h3 { margin: 0; }
.card-head .inline-form, .card-head form { margin-top: 0; }
.card-head > .muted { font-size: 12px; text-align: right; max-width: 38ch; }

.btn-link {
  background: none; border: none; color: var(--accent-2); cursor: pointer;
  font: inherit; font-weight: 400; padding: 0; text-decoration: none; min-height: 0;
}
.btn-link:hover { text-decoration: underline; filter: none; }

/* ---- Stat tiles ---- */
.stat { display: flex; flex-direction: column; gap: 2px; }
.stat-n { font-family: var(--mono); font-size: 30px; font-weight: 700; line-height: 1.1; }
.stat-n.ok { color: var(--ok-ink); }
.stat-n.crit { color: var(--crit-ink); }
.stat-l { color: var(--ink-mute); font-size: 12px; text-transform: uppercase; letter-spacing: 0.05em; }

/* ---- Pills (state breakdown) ---- */
.pills { display: flex; flex-wrap: wrap; gap: 8px; }
.pill { display: flex; align-items: baseline; gap: 8px; background: var(--panel-2); border: 1px solid var(--line-2); border-radius: 999px; padding: 6px 14px; }
.pill-n { font-family: var(--mono); font-weight: 700; color: var(--accent-ink); }
.pill-l { color: var(--ink-dim); font-size: 13px; }

/* ---- Tables ---- */
table.data { width: 100%; border-collapse: collapse; font-size: 13px; }
table.data th {
  text-align: left; padding: 8px 10px; color: var(--ink-mute);
  font-weight: 600; text-transform: uppercase; letter-spacing: 0.04em; font-size: 11px;
  border-bottom: 1px solid var(--line-2); white-space: nowrap;
}
table.data td { padding: 9px 10px; border-bottom: 1px solid var(--line); }
table.data tbody tr:nth-child(even) { background: #fafbfc; }
table.data tbody tr:hover, table.data tbody tr:nth-child(even):hover { background: #eff6ff; }

/* Column 1 is an identifier column by convention. Opt out with class="sans". */
table.data td:first-child { font-family: var(--mono); white-space: nowrap; }
table.data td.sans { font-family: var(--sans); white-space: normal; }

/* Outer edges align with the card gutter, so the heading above the table and
   the table's first column share a left edge. */
table.data th:first-child, table.data td:first-child { padding-left: 0; }
table.data th:last-child,  table.data td:last-child  { padding-right: 0; }

/* An empty state is prose, never an identifier. */
table.data td[colspan] {
  font-family: var(--sans); white-space: normal; text-align: center;
  color: var(--ink-mute); padding: 22px 10px;
}

/* Controls in cells must not carry the label-stack top margin. */
table.data input, table.data select, table.data textarea { margin-top: 0; vertical-align: middle; }
table.data .inline-form { margin-top: 0; }
table.data td .btn-sm, table.data td button { min-height: 0; }

/* A totals row is a total, not a column header. */
table.data tfoot th {
  font-size: 13px; text-transform: none; letter-spacing: 0; font-weight: 700;
  color: var(--ink); border-top: 2px solid var(--line-2); border-bottom: none; padding-top: 10px;
}

/* ---- Badges / status ---- */
.badge { display: inline-block; font-family: var(--mono); font-size: 11px; font-weight: 700; padding: 2px 7px; border-radius: 4px; letter-spacing: 0.03em; line-height: 1.45; white-space: nowrap; }
.badge.crit { background: var(--crit-bg); color: var(--crit-ink); border: 1px solid var(--crit-line); }
.badge.warn { background: var(--warn-bg); color: var(--warn-ink); border: 1px solid var(--warn-line); }
.badge.ok   { background: var(--ok-bg);   color: var(--ok-ink);   border: 1px solid var(--ok-line); }
.badge.info { background: var(--info-bg); color: var(--ink-dim);  border: 1px solid var(--info-line); }

.dot { display: inline-block; width: 8px; height: 8px; border-radius: 50%; margin-right: 8px; vertical-align: -1px; }
.dot-on  { background: var(--ok);       box-shadow: 0 0 0 2px rgba(16,185,129,.18); }
.dot-off { background: var(--ink-mute); box-shadow: 0 0 0 2px rgba(95,107,122,.12); }

.tag { display: inline-block; font-size: 12px; padding: 2px 8px; border: 1px solid var(--line-2); border-radius: 4px; color: var(--ink-dim); white-space: nowrap; }
.tag-int { color: var(--warn-ink); background: var(--warn-bg); border-color: var(--warn-line); }

/* ---- Metric tiles ---- */
.metrics { display: grid; grid-template-columns: repeat(auto-fill, minmax(150px, 200px)); justify-content: start; gap: 10px; }
.metric { background: var(--panel-2); border: 1px solid var(--line-2); border-radius: var(--radius); padding: 12px; }
.metric-v { font-family: var(--mono); font-size: 22px; font-weight: 700; }
.metric-u { font-size: 13px; color: var(--ink-mute); font-weight: 500; }
.metric-k { color: var(--ink-mute); font-size: 12px; margin-top: 2px; }

/* ---- Unit header ---- */
.unit-head { display: flex; align-items: flex-start; justify-content: space-between; gap: 16px; flex-wrap: wrap; }
.unit-head h1 { font-family: var(--mono); margin: 0 0 4px; }
.sub { color: var(--ink-dim); font-size: 13px; }
.ver { display: flex; align-items: center; flex-wrap: wrap; gap: 8px; }
.ver .tag { font-family: var(--mono); }

/* ---- Forms ---------------------------------------------------------------
   RULE: layout margin never lives on the bare `label` element. Stacked forms
   add rhythm explicitly (.stack > label); flex/grid rows get zero. The old
   `label { margin-bottom: 10px }` leaked into .form-grid / .user-edit /
   .set-row, and because those use `align-items:end` it bottom-aligned a
   phantom margin box — measured as a 10-13px offset between an input and the
   button beside it. That was the single root cause of the reported mess. */
label { display: block; margin: 0; color: var(--ink-dim); font-size: 13px; }

input, select, textarea {
  display: block; width: 100%; margin-top: 4px;
  background: #ffffff; color: var(--ink);
  border: 1px solid var(--line-2); border-radius: var(--radius);
  padding: 8px 10px; font: inherit;
}
input::placeholder, textarea::placeholder { color: var(--ink-mute); }
input:hover, select:hover, textarea:hover { border-color: var(--line-strong); }
input:disabled, select:disabled, textarea:disabled { background: var(--bg-2); color: var(--ink-mute); }
input:focus, select:focus, textarea:focus { outline: none; border-color: var(--accent-border); box-shadow: 0 0 0 3px rgba(255,176,0,0.35); }
textarea { min-height: 96px; resize: vertical; }

/* ---- Buttons. Every visual lives on a CLASS as well as the element, so
   <a class="btn-sm"> and <button class="btn-sm"> are the same object. The base
   button carries a transparent 1px border so bordered variants match height. */
button, .btn {
  display: inline-flex; align-items: center; justify-content: center; gap: 6px;
  background: var(--accent); color: var(--on-accent);
  border: 1px solid transparent; border-radius: var(--radius);
  padding: 8px 16px; line-height: 21px; font: inherit; font-weight: 600;
  cursor: pointer; text-decoration: none; white-space: nowrap;
  transition: filter .12s;
}
button:hover, .btn:hover { filter: brightness(1.08); text-decoration: none; }
.btn-sm {
  display: inline-flex; align-items: center; justify-content: center; gap: 6px;
  padding: 5px 10px; font-size: 13px; font-weight: 600; line-height: 18px;
  background: #ffffff; color: var(--ink);
  border: 1px solid var(--line-2); border-radius: var(--radius);
  cursor: pointer; text-decoration: none; white-space: nowrap;
}
.btn-sm:hover { border-color: var(--accent-border); background: var(--bg-2); text-decoration: none; filter: none; }
a.btn-sm:hover, a.btn:hover { text-decoration: none; }
.btn-primary { background: var(--accent); color: var(--on-accent); border-color: transparent; }
.btn-primary:hover { background: var(--accent); filter: brightness(1.08); }
.btn-secondary { background: var(--panel); border: 1px solid var(--line-2); color: var(--ink); }

/* ---- Control rows. A nested filter bar is a RECESSED surface, not a second
   white card inside a white card. */
.filters {
  display: flex; gap: 12px; align-items: end; flex-wrap: wrap;
  margin: 0 0 12px;
  background: var(--panel-2); border: 1px solid var(--line);
  border-radius: var(--radius-sm); padding: 10px 12px;
}
.inline-form { display: flex; gap: 12px; align-items: end; flex-wrap: wrap; margin-top: 12px; }
.filters label, .inline-form label {
  display: flex; flex-direction: column; gap: 4px; margin: 0;
  font-size: 11px; font-weight: 600; text-transform: uppercase; letter-spacing: .04em;
  color: var(--ink-mute);
}
.filters input, .filters select, .inline-form input, .inline-form select { margin-top: 0; }
.filters input:not([type=checkbox]):not([type=radio]):not([type=file]) { width: 240px; }
.filters select { width: auto; min-width: 190px; }
.inline-form input:not([type=checkbox]):not([type=radio]):not([type=file]),
.inline-form select { width: auto; min-width: 190px; }

/* One height for everything in a control row, so `align-items:end` lines up
   text baselines instead of just box bottoms. */
.filters     input:not([type=checkbox]):not([type=radio]), .filters     select, .filters     button, .filters     .btn-sm,
.inline-form input:not([type=checkbox]):not([type=radio]), .inline-form select, .inline-form button, .inline-form .btn-sm,
.rec-actions input:not([type=checkbox]), .rec-actions select, .rec-actions button, .rec-actions .btn-sm,
.photo-upload input, .photo-upload select, .photo-upload button,
.form-actions button, .form-actions .btn-sm,
.ua-form input, .ua-form button {
  min-height: var(--ctl-h);
}
.filters .btn-link, .inline-form .btn-link { align-self: center; }
/* Filtering is not a page's primary action — demote its submit. */
.filters button[type=submit] { background: #ffffff; color: var(--ink); border: 1px solid var(--line-2); }
.filters button[type=submit]:hover { border-color: var(--accent-border); background: var(--bg-2); filter: none; }

/* ---- Checkboxes are ALWAYS a row, in every container. */
.chk { display: flex; align-items: center; gap: 8px; }
label.chk, .stack > label.chk, .form-grid label.chk, .set-grid label.chk,
.filters label.chk, .inline-form label.chk, .user-edit label.chk {
  display: flex; flex-direction: row; align-items: center; gap: 8px;
  margin: 0; font-size: 13px; font-weight: 400;
  text-transform: none; letter-spacing: 0; color: var(--ink-dim);
  min-height: var(--ctl-h);
}
label.chk > input[type=checkbox], label.chk > input[type=radio],
.chk input[type=checkbox], .chk input[type=radio] {
  display: inline-block; width: auto; min-width: 0; min-height: 0;
  margin: 0; flex: none; align-self: center;
}

/* .row is a KEY/VALUE line, never a form layout. Capped so a two-word pair is
   not justified across 1100px of card. */
.row { display: flex; align-items: center; justify-content: space-between; gap: 12px; margin-top: 8px; flex-wrap: wrap; max-width: 720px; }

/* ---- Tickets ---- */
.ticket-title { text-transform: none; letter-spacing: 0; font-size: 16px; color: var(--ink); margin: 8px 0 6px; }
.desc { color: var(--ink-dim); white-space: pre-wrap; }
.comment { border-left: 3px solid var(--line-2); padding: 8px 12px; margin-bottom: 10px; background: var(--panel-2); border-radius: 0 var(--radius) var(--radius) 0; }
.comment.internal { border-left-color: var(--warn); background: var(--warn-bg); }
.comment-meta { font-size: 12px; color: var(--ink-mute); margin-bottom: 4px; }
.comment-body { font-size: 13px; }
.comment-form { margin-top: 14px; }

/* ---- Route planner ---- */
.route-summary { display: flex; gap: 32px; flex-wrap: wrap; padding: 12px 0 14px; color: var(--ink-dim); font-size: 13px; border-top: 1px solid var(--line); border-bottom: none; margin: 12px 0 0; }
.route-summary strong { font-family: var(--mono); color: var(--ink); }
.ok { color: var(--ok-ink); } .crit { color: var(--crit-ink); } .warn { color: var(--warn-ink); }
.muted { color: var(--ink-mute); }

/* ---- Alerts ---- */
.alert { padding: 10px 14px; border-radius: var(--radius); font-size: 13px; border: 1px solid; }
.alert-error { background: var(--crit-bg); color: var(--crit-ink); border-color: var(--crit-line); border-left: 3px solid var(--crit); }
.alert-warn  { background: var(--warn-bg); color: var(--warn-ink); border-color: var(--warn-line); border-left: 3px solid var(--warn); }
.alert-ok    { background: var(--ok-bg);   color: var(--ok-ink);   border-color: var(--ok-line);   border-left: 3px solid var(--ok); }

/* ---- Login ---- */
.login-body { display: flex; align-items: center; justify-content: center; min-height: 100vh; background: radial-gradient(1200px 600px at 50% -10%, #ffffff, var(--bg)); }
.login-card { width: 360px; background: var(--panel); border: 1px solid var(--line); border-radius: 12px; padding: 28px; box-shadow: var(--shadow-lg); }
.login-card .brand { display: block; margin-bottom: 20px; font-size: 16px; }
.login-card .wordmark { display: block; width: 100%; max-width: 240px; height: auto; }
.login-card .brand-app { display: block; margin-top: 8px; font-size: 11px; font-weight: 600; letter-spacing: .08em; text-transform: uppercase; color: var(--ink-mute); }
.login-card button { width: 100%; margin-top: 6px; }
.login-card .muted { margin: 0 0 14px; }

/* ---- Control-plan protocol form ------------------------------------------ */
.protocol { margin-top: 4px; }
.field { padding: 12px 0; border-bottom: 1px solid var(--line); }
.field:last-of-type { border-bottom: none; }
.field > label { font-size: 14px; color: var(--ink); margin-bottom: 6px; }
.field small { display: block; margin-top: 4px; }
.req { color: var(--crit-ink); }
.num-input { display: flex; align-items: stretch; gap: 0; max-width: 320px; }
.num-input input { border-top-right-radius: 0; border-bottom-right-radius: 0; }
.suffix {
  display: inline-flex; align-items: center; padding: 0 12px; margin-top: 4px;
  background: var(--panel-2); border: 1px solid var(--line-2); border-left: 1px solid var(--line-2);
  border-radius: 0 var(--radius) var(--radius) 0; color: var(--ink-dim);
  font-family: var(--mono); font-size: 13px;
}
.seg { display: flex; gap: 8px; flex-wrap: wrap; margin-top: 4px; }
.seg-opt {
  display: inline-flex; align-items: center; gap: 8px; margin: 0; cursor: pointer;
  padding: 8px 14px; background: #ffffff; border: 1px solid var(--line-2);
  border-radius: var(--radius); color: var(--ink); font-size: 14px;
}
.seg-opt input { width: auto; margin: 0; }
.seg-opt:hover { border-color: var(--accent-border); background: var(--accent-soft); }
.seg-opt:has(input:checked) { border-color: var(--accent-border); background: var(--accent-soft); box-shadow: inset 0 0 0 1px var(--accent-border); }
.protocol-actions { display: flex; align-items: center; gap: 14px; margin-top: 18px; }

/* ---- Upgrade runbook ------------------------------------------------------ */
.runbook .sub { font-family: var(--mono); }
.runbook-steps { margin: 14px 0 0; padding-left: 22px; }
.runbook-steps > li { margin-bottom: 16px; line-height: 1.5; }
.step-instr { color: var(--ink); }
.step-exp { color: var(--ink-dim); font-size: 13px; margin-top: 4px; }
.step-warn { margin-top: 8px; }

/* ---- Control-plan authoring ---------------------------------------------- */
/* ---- Stacked forms: a single-column form has ONE measure. A 1100px-wide
   "Title" field was the most visible symptom of having none. */
.stack { max-width: var(--measure); }
.stack > label, .stack > .field, .stack > fieldset, .stack > .field-row,
.stack > .form-grid { display: block; margin-bottom: 12px; }
.stack > label.chk { display: flex; margin-bottom: 12px; }
/* These two must re-assert their own display: the blanket `display:block` above
   otherwise flattens a .field-row / .form-grid into a single column, so 2-up
   rows like postal code + country stack instead of sitting side by side. */
.stack > .form-grid { display: grid; }
.stack > .field-row { display: grid; }
.stack input[type=number] { max-width: 200px; }
.rulebox {
  border: 1px solid var(--line); background: var(--panel-2);
  border-radius: var(--radius); padding: 12px 14px 2px; margin: 4px 0 12px;
}
.rulebox legend { color: var(--ink-mute); font-size: 11px; text-transform: uppercase; letter-spacing: .06em; padding: 0 6px; }
.rulebox label { margin: 0 0 12px; }
.rulebox > *:last-child { margin-bottom: 12px; }
.inline-confirm { display: inline; }
.btn-link.danger { color: var(--crit-ink); }
.ref-photo { display: flex; align-items: center; gap: 8px; margin-top: 6px; }
.ref-thumb { max-width: 160px; max-height: 120px; border: 1px solid var(--line-2); border-radius: var(--radius); display: block; background: var(--bg-2); }

/* ---- SLA, recalls, timeline, work timer ---------------------------------- */
.recall-banner { border: 1px solid var(--crit-line); background: var(--crit-bg); box-shadow: inset 4px 0 0 var(--crit); }
.recall-banner h2 { color: var(--crit-ink); }
.recall-list { list-style: none; padding: 0; margin: 6px 0 0; }
.recall-list > li { display: flex; align-items: center; gap: 10px; padding: 6px 0; border-bottom: 1px solid var(--line); flex-wrap: wrap; }
.recall-list > li:last-child { border-bottom: none; }

.timeline { list-style: none; margin: 0; padding: 0; }
.tl-item { display: flex; gap: 12px; padding: 8px 0 8px 14px; border-left: 2px solid var(--line-2); position: relative; }
.tl-item::before { content: ""; position: absolute; left: -5px; top: 14px; width: 8px; height: 8px; border-radius: 50%; background: var(--accent-ink); box-shadow: 0 0 0 2px var(--panel); }
.tl-state::before { background: var(--ink-mute); }
.tl-exec::before  { background: var(--accent-ink); }
.tl-ticket::before{ background: var(--warn); }
.tl-when { color: var(--ink-mute); font-family: var(--mono); font-size: 12px; min-width: 124px; flex-shrink: 0; }
.tl-body { color: var(--ink-dim); }
.tl-next { margin-top: 14px; padding-top: 10px; border-top: 1px solid var(--line); }
.tl-next h3 { margin: 0 0 6px; font-size: 13px; color: var(--ink-mute); text-transform: uppercase; letter-spacing: .04em; }
.tl-next ul { margin: 0; padding-left: 18px; }

.work-card .work-total { font-family: var(--mono); font-size: 18px; color: var(--accent-ink); }
.work-card .inline-form { gap: 6px; }

/* ---- Fleet route map + legend ------------------------------------------- */
.routemap { width: 100%; height: auto; display: block; border-radius: 8px; margin: 6px 0; background: var(--panel); border: 1px solid var(--line); }
.legend { display: flex; flex-wrap: wrap; gap: 14px; margin: 8px 0; color: var(--ink-dim); font-size: 13px; }
.legend-item { display: inline-flex; align-items: center; gap: 6px; }
.legend-dot { width: 11px; height: 11px; border-radius: 50%; display: inline-block; }
.legend-sq { width: 11px; height: 11px; display: inline-block; background: var(--ink-mute); border: 1px solid rgba(17,24,39,.18); }

/* ---- Telemetry chart ---------------------------------------------------- */
.tchart { width: 100%; height: auto; display: block; }
.tchart-grid { stroke: var(--line); stroke-width: 1; }
.tchart-line { fill: none; stroke: var(--accent-ink); stroke-width: 2; }
.tchart-area { fill: var(--accent); opacity: 0.16; }
.tchart-axis { fill: var(--ink-mute); font-size: 11px; font-family: var(--mono, monospace); }

/* Shift plan + on-call */
table.data tbody tr.row-active, table.data tbody tr:nth-child(even).row-active { background: #eff6ff; box-shadow: inset 3px 0 0 var(--accent-2); }
.row-active { background: #eff6ff; }
.form-grid {
  display: grid; grid-template-columns: repeat(auto-fill, minmax(210px, 1fr));
  gap: 12px 16px; align-items: end; max-width: var(--measure-wide);
}
.form-grid label {
  display: flex; flex-direction: column; gap: 4px; margin: 0;
  font-size: 11px; font-weight: 600; text-transform: uppercase; letter-spacing: .04em;
  color: var(--ink-mute);
}
.form-grid input, .form-grid select, .form-grid textarea { width: 100%; margin-top: 0; min-height: var(--ctl-h); }
.form-grid input[type=checkbox], .form-grid input[type=radio] { width: auto; min-height: 0; }
.form-grid input[type=number], .form-grid input[type=time], .form-grid input[type=date] { max-width: 200px; }
/* `.wide` is emitted 5x in index.php and had NO rule anywhere in this file. */
.form-grid .wide, .form-grid > table, .form-grid > p,
.form-grid > .form-actions, .form-grid > .actions { grid-column: 1 / -1; }
.form-grid > table { display: block; overflow-x: auto; }
.form-grid > p { margin: 0; max-width: var(--prose); }
@media (max-width: 640px) { .form-grid { grid-template-columns: 1fr; } }

/* Users & policies editors */
.row-actions { display: flex; gap: 8px; flex-wrap: wrap; }
.user-list { display: flex; flex-direction: column; gap: 10px; }

/* Was a flex-wrap row of ~12 arbitrary-width controls that wrapped raggedly,
   with Save and two checkboxes on three different baselines (measured 13px). */
.user-edit {
  display: grid; grid-template-columns: repeat(auto-fill, minmax(150px, 1fr));
  align-items: end; gap: 10px 12px;
  padding: 12px; border: 1px solid var(--line-2); border-radius: 8px; background: var(--panel-2);
}
.user-edit .ue-id {
  grid-column: 1 / -1; display: flex; flex-direction: row; align-items: baseline;
  gap: 8px; min-width: 0; padding-bottom: 2px; border-bottom: 1px solid var(--line);
}
.user-edit label {
  display: flex; flex-direction: column; gap: 4px; margin: 0;
  font-size: 11px; font-weight: 600; text-transform: uppercase; letter-spacing: .04em;
  color: var(--ink-mute);
}
.user-edit input, .user-edit select { width: 100%; margin-top: 0; font-size: 13px; min-height: var(--ctl-h); }
.user-edit label.chk, .user-edit .ue-active {
  flex-direction: row; align-items: center; gap: 8px; min-height: var(--ctl-h);
  text-transform: none; letter-spacing: 0; font-size: 13px; font-weight: 400; color: var(--ink-dim);
}
.user-edit .ue-active input, .user-edit label.chk input { width: auto; min-height: 0; margin: 0; flex: none; }
.user-edit button[type=submit] { grid-column: 1 / -1; justify-self: start; min-height: var(--ctl-h); }

/* Leaflet route map */
.leaflet-map { height: 460px; border-radius: 8px; overflow: hidden; border: 1px solid var(--line-2); }
.leaflet-map .stop-num { background: transparent; border: 0; box-shadow: none; color: #fff; font-weight: 700; font-size: 11px; text-shadow: 0 0 2px rgba(0,0,0,.65); }

/* Timesheet / trip recorder */
.recorder { border: 1px solid var(--line); box-shadow: var(--shadow), inset 3px 0 0 var(--accent); }
.rec-state { font-size: 15px; margin-bottom: 10px; }
.rec-clocks { display: flex; gap: 28px; margin: 12px 0; }
.rec-clocks > div { display: flex; flex-direction: column; gap: 2px; }
.rec-big { font-size: 26px; font-variant-numeric: tabular-nums; font-weight: 700; }
.rec-actions { display: flex; flex-wrap: wrap; gap: 12px; align-items: end; margin-top: 12px; }
.rec-actions label {
  display: flex; flex-direction: column; gap: 4px; margin: 0;
  font-size: 11px; font-weight: 600; text-transform: uppercase; letter-spacing: .04em;
  color: var(--ink-mute);
}
.rec-actions input, .rec-actions select { margin-top: 0; }
.rec-actions input[type=number] { width: 140px; }
.rec-clocks .muted { font-size: 12px; text-transform: uppercase; letter-spacing: .05em; color: var(--ink-mute); }
.rec-cancel { margin-top: 12px; }
.btn-secondary { background: var(--panel); border: 1px solid var(--line-2); color: var(--ink); }

/* Settings + user management (added with the admin GUI) */
/* ---- Page header. Padded to the card gutter so an <h1> outside a card and
   an <h1> inside one share the same left edge. */
/* GRID, not flex: with `flex-basis:100%` the description's max-width clamps its
   hypothetical size, so it never forces a wrap and sits beside the <h1>.
   A grid row is deterministic — title left, optional action right, lede below. */
.page-head {
  display: grid; grid-template-columns: 1fr auto;
  align-items: baseline; gap: 4px 16px; margin: 0; padding: 0 var(--gutter);
}
.page-head > h1 { margin: 0; grid-column: 1; }
.page-head > a, .page-head > .btn-sm, .page-head > .btn, .page-head > form,
.page-head > .row-actions { grid-column: 2; justify-self: end; align-self: center; }
.page-head > p, .page-head .lede, .page-head .page-sub {
  grid-column: 1 / -1; margin: 4px 0 0; color: var(--ink-mute); max-width: var(--prose);
}

.set-grid { display: grid; grid-template-columns: repeat(auto-fill, minmax(260px, 1fr)); gap: 12px 16px; align-items: end; }
.set-row  { display: flex; flex-direction: column; gap: 4px; margin: 0; font-size: 13px; }
.set-row > span { color: var(--ink-mute); font-size: 11px; font-weight: 600; text-transform: uppercase; letter-spacing: .04em; }
.set-row input, .set-row select { width: 100%; margin-top: 0; min-height: var(--ctl-h); }
/* Checkbox rows are NOT label+input pairs — own full-width row so they stop
   aligning to the wrong edge of the auto-fit grid. */
.set-grid > .set-bool, .set-grid > .form-actions { grid-column: 1 / -1; }
.set-bool { display: flex; align-items: center; gap: 8px; min-height: var(--ctl-h); margin: 4px 0 0; }

.user-actions { display: flex; flex-wrap: wrap; gap: 12px; align-items: center; padding: 10px 0 16px; border-bottom: 1px solid var(--line); margin-bottom: 10px; }
.ua-form { display: flex; align-items: center; gap: 8px; }
.ua-form input { width: 260px; margin-top: 0; }
button.primary { background: var(--accent); color: var(--on-accent); border-color: transparent; }
button.btn-sm.danger { background: var(--crit-bg); color: var(--crit-ink); border: 1px solid var(--crit-line); }
.cycle-big { font-size: 2rem; font-weight: 700; line-height: 1.1; }
.cycle-big .metric-u { font-size: .9rem; font-weight: 500; color: var(--ink-mute); }
/* Ticket service-photo gallery */
.photo-grid { display: grid; grid-template-columns: repeat(auto-fill, minmax(140px, 1fr)); gap: .6rem; margin-bottom: .8rem; }
.photo-tile { position: relative; margin: 0; }
.photo-tile img { width: 100%; height: 120px; object-fit: cover; border-radius: 6px; border: 1px solid var(--line-2); background: var(--bg-2); display: block; }
.photo-tile .ph-cap { display: block; font-size: .75rem; color: var(--ink-mute); margin-top: .2rem; }
.photo-tile .ph-del { position: absolute; top: 4px; right: 4px; margin: 0; }
.photo-tile .ph-del button { background: rgba(17,24,39,.72); color: #fff; border: none; border-radius: 50%; width: 22px; height: 22px; line-height: 1; cursor: pointer; }
.photo-upload {
  display: flex; flex-wrap: wrap; gap: 10px; align-items: center;
  margin-top: 14px; padding-top: 14px; border-top: 1px solid var(--line);
}
.photo-upload input, .photo-upload select { width: auto; margin-top: 0; }
.photo-upload input[type=file] { max-width: 340px; }
.photo-upload input[type=text] { width: 240px; }
/* Signatures + protocol media + lifecycle record */
.sig-block { margin: 1rem 0 .5rem; }
#sigpad { border: 2px dashed var(--line-strong); border-radius: 6px; background: #fff; touch-action: none; max-width: 100%; }
.sig-img { max-width: 320px; border: 1px solid var(--line-2); border-radius: 6px; background: #fff; display: block; }
.sig-view { margin-top: 1rem; }
.ref-video video { max-width: 360px; width: 100%; border-radius: 6px; display: block; }
.kv th { text-align: left; width: 14%; }
@media print {
  .topbar, .mainnav, .sidebar, .nav-toggle, .skip-link, .user,
  .noprint, .alert, button, form.inline-form { display: none !important; }
  body { padding-left: 0 !important; }
  .container { margin-left: 0 !important; max-width: none !important; padding: 0 !important; }
  .card { box-shadow: none; border: 1px solid #d1d5db; page-break-inside: avoid; }
  body { background: #fff; color: #000; }
}
/* Registers + service packs */
input.num { width: 100%; max-width: 110px; text-align: right; font-variant-numeric: tabular-nums; }
table.targets input.num { max-width: 96px; }
table.data tbody tr.row-now, table.data tbody tr:nth-child(even).row-now { background: var(--accent-soft); box-shadow: inset 3px 0 0 var(--accent); }
tr.row-now td { background: transparent; }

/* ===========================================================================
   LAYOUT SYSTEM — shared utilities. Appended last so equal-specificity rules
   above are overridden deliberately, not by accident. Must stay ABOVE the
   wallboard banner so it never reaches the TV surface.
   =========================================================================== */

/* Description paragraphs were running ~180 characters. */
.card > p, .card > .muted, .card > .desc, .card > small { margin: 0 0 12px; max-width: var(--prose); }
.card > .alert { margin: 12px 0; }
.card-sub { margin: -4px 0 12px; font-size: 12px; color: var(--ink-mute); max-width: var(--prose); }

/* <code> was unstyled and fell back to the UA `monospace` keyword (Courier),
   a different face from every other mono value in the app. */
code, kbd, samp { font-family: var(--mono); font-size: .92em; }
table.data code, .page-head code { background: none; border: 0; padding: 0; }

/* Every submit gets its own full-width row instead of landing mid-grid beside
   an unrelated field. */
.form-actions {
  display: flex; align-items: center; flex-wrap: wrap; gap: 12px;
  margin-top: 16px; padding-top: 14px; border-top: 1px solid var(--line);
}
.card > form > .form-actions:first-child { margin-top: 0; padding-top: 0; border-top: none; }

/* A short row of 2-4 related fields inside a stacked form. Replaces the .row
   misuse that pinned two 90px number fields to opposite ends of a card. */
.field-row {
  display: grid; grid-template-columns: repeat(auto-fill, minmax(170px, 1fr));
  gap: 12px 16px; align-items: end; margin: 0 0 12px; max-width: var(--measure);
}
.field-row label { display: flex; flex-direction: column; gap: 4px; margin: 0;
  font-size: 11px; font-weight: 600; text-transform: uppercase; letter-spacing: .04em; color: var(--ink-mute); }
.field-row input, .field-row select { width: 100%; margin-top: 0; min-height: var(--ctl-h); }
.field-row .wide { grid-column: 1 / -1; }
.form-note { display: block; margin: -6px 0 12px; font-size: 12px; color: var(--ink-mute); }

/* Table column sizing. `.mono` was emitted 7x in index.php with NO rule. */
.mono, table.data td.mono, table.data th.mono {
  font-family: var(--mono); font-variant-numeric: tabular-nums; white-space: nowrap;
}
table.data td.num, table.data th.num {
  text-align: right; font-variant-numeric: tabular-nums; white-space: nowrap; width: 1%;
}
/* Action / narrow columns collapse to content; the slack goes to prose. */
table.data th.act, table.data td.act, table.data th:empty { width: 1%; white-space: nowrap; }
table.data td.act { text-align: right; }
/* Exactly one prose column per table absorbs the remaining width. */
table.data .col-grow { width: 100%; }
table.data td.cell-title {
  width: 100%; max-width: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
}

.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; }
.w-sm { max-width: 110px; }
.legend-dot, .legend-sq { vertical-align: -1px; }
.card-head h2 .legend-dot { vertical-align: baseline; }
.grid-4 .stat-l { min-height: 2.4em; }

/* ---- Searchable dropdowns (public/assets/select-search.js) ----------------
   Any <select> with more than 10 options is enhanced into a combobox with a
   filter box. The native <select> stays in the DOM and stays FOCUSABLE (opacity
   0, not display:none) so `required` validation can still anchor its bubble. */
.ss { position: relative; display: block; width: 100%; margin-top: 4px; }
.filters .ss, .inline-form .ss, .rec-actions .ss { width: auto; min-width: 190px; margin-top: 0; }
.form-grid .ss, .set-row .ss, .user-edit .ss, .field-row .ss { margin-top: 0; }
.form-grid .ss, .set-row .ss, .user-edit .ss, .field-row .ss, .stack .ss { width: 100%; }

.ss > select.ss-native {
  position: absolute; inset: 0; width: 100%; height: 100%;
  opacity: 0; pointer-events: none; margin: 0; border: 0; padding: 0;
}

.ss-btn {
  display: flex; align-items: center; justify-content: space-between; gap: 8px;
  width: 100%; min-height: var(--ctl-h); margin-top: 0;
  background: #ffffff; color: var(--ink);
  border: 1px solid var(--line-2); border-radius: var(--radius);
  padding: 8px 10px; font: inherit; font-weight: 400; text-align: left;
  cursor: pointer; white-space: nowrap;
}
.ss-btn:hover { border-color: var(--line-strong); background: #ffffff; filter: none; }
.ss.is-open > .ss-btn,
.ss-btn:focus-visible {
  outline: none; border-color: var(--accent-border);
  box-shadow: 0 0 0 3px rgba(255,176,0,0.35);
}
.ss-label { overflow: hidden; text-overflow: ellipsis; }
.ss-label.is-placeholder { color: var(--ink-mute); }
.ss-caret { color: var(--ink-mute); font-size: 11px; flex: none; }

/* FIXED, and portalled to <body> by the script: .card sets overflow-x:auto for
   wide tables, which per spec makes overflow-y compute to auto as well, so an
   in-flow panel gets clipped by its own card. Position is set inline by JS. */
.ss-panel {
  position: fixed; z-index: 60;
  max-width: min(420px, calc(100vw - 16px));
  background: var(--panel); border: 1px solid var(--line-2);
  border-radius: var(--radius); box-shadow: var(--shadow-lg);
  padding: 8px; display: flex; flex-direction: column; gap: 6px;
}
/* MUST stay next to the rule above. `display:flex` there is author-origin and
   therefore beats the user-agent `[hidden] { display: none }`, so without this
   every closed panel stayed painted, clickable and exposed to screen readers. */
.ss-panel[hidden] { display: none; }

.ss-search { width: 100%; margin-top: 0; min-height: 34px; }
.ss-list { position: relative; max-height: 260px; overflow-y: auto; overscroll-behavior: contain; }

.ss-opt {
  padding: 7px 9px; border-radius: var(--radius-sm); cursor: pointer;
  white-space: nowrap; overflow: hidden; text-overflow: ellipsis; font-size: 13px;
}
.ss-opt.is-active   { background: var(--accent-soft); box-shadow: inset 3px 0 0 var(--accent-border); }
.ss-opt.is-selected { color: var(--accent-ink); font-weight: 600; }
.ss-opt.is-selected.is-active { background: var(--bg-2); box-shadow: inset 3px 0 0 var(--accent-border); }
.ss-opt.is-disabled { color: var(--ink-mute); cursor: default; }

.ss-group {
  padding: 8px 9px 4px; font-size: 11px; font-weight: 600;
  text-transform: uppercase; letter-spacing: .06em; color: var(--ink-mute);
}
.ss-empty { padding: 12px 9px; color: var(--ink-mute); font-size: 13px; text-align: center; }

/* Printing a combobox should print the value, not a button. */
@media print {
  .ss-panel, .ss-caret { display: none !important; }
  .ss-btn { border: 0; padding: 0; }
}

/* ---- Per-unit tab strip ---------------------------------------------------
   The one place in the app with per-record navigation. A sibling of .page-head,
   never a child (that grid places only three child roles; a fourth auto-places
   into whichever cell is free). Scrolls rather than wraps on narrow screens so
   the strip stays one line and the page below never shifts. */
.unit-tabs {
  display: flex; gap: 4px; align-items: stretch;
  margin: 10px 0 0; padding: 0 var(--gutter);
  border-bottom: 1px solid var(--line);
  overflow-x: auto; scrollbar-width: thin;
}
.unit-tab {
  flex: 0 0 auto; padding: 9px 14px; font-size: 14px; font-weight: 500;
  color: var(--ink-mute); text-decoration: none; white-space: nowrap;
  border-bottom: 2px solid transparent; margin-bottom: -1px; border-radius: 6px 6px 0 0;
}
.unit-tab:hover { color: var(--ink); background: var(--accent-soft); }
.unit-tab.is-active {
  color: var(--accent-ink); border-bottom-color: var(--accent-border); font-weight: 600;
}

/* ---- Site coordinates: map pin picker + optional address lookup ----------- */
.newsite { margin-top: 12px; border-top: 1px solid var(--line); padding-top: 12px; }
.newsite > summary {
  cursor: pointer; font-size: 13px; font-weight: 600; color: var(--accent-ink);
  list-style: none; display: inline-flex; align-items: center; gap: 6px;
  padding: 6px 10px; border: 1px solid var(--line-2); border-radius: var(--radius);
  background: var(--panel);
}
.newsite > summary::-webkit-details-marker { display: none; }
.newsite > summary:hover { border-color: var(--accent-border); background: var(--accent-soft); }
.newsite[open] > summary { margin-bottom: 12px; }
.newsite .stack { max-width: var(--measure); }

.geo-bar { display: flex; align-items: center; gap: 10px; flex-wrap: wrap; margin: 0 0 10px; }
.geo-status { font-size: 12px; }
.geo-search { display: flex; gap: 8px; align-items: center; flex-wrap: wrap; margin: 0 0 10px; position: relative; }
.geo-search .geo-q { width: 320px; margin-top: 0; min-height: var(--ctl-h); }
.geo-results {
  position: absolute; z-index: 30; top: calc(100% + 4px); left: 0;
  width: min(520px, 100%); max-height: 260px; overflow-y: auto;
  background: var(--panel); border: 1px solid var(--line-2);
  border-radius: var(--radius); box-shadow: var(--shadow-lg); padding: 6px;
}
.geo-hit {
  display: block; width: 100%; text-align: left; background: none; border: 0;
  padding: 7px 9px; border-radius: var(--radius-sm); font: inherit; font-weight: 400;
  color: var(--ink); cursor: pointer; white-space: normal;
}
.geo-hit:hover { background: var(--accent-soft); filter: none; }
.geo-map {
  height: 300px; margin: 0 0 12px; border: 1px solid var(--line-2);
  border-radius: var(--radius); overflow: hidden;
}

/* ---- Configure hub (the setup screens that left the sidebar) -------------- */
.cfg-grid { display: grid; grid-template-columns: repeat(auto-fill, minmax(260px, 1fr)); gap: 10px; }
.cfg-item {
  display: flex; flex-direction: column; gap: 3px;
  padding: 12px 14px; border: 1px solid var(--line-2); border-radius: var(--radius);
  background: var(--panel); color: var(--ink); text-decoration: none;
  transition: border-color .12s, background .12s;
}
.cfg-item:hover { border-color: var(--accent-border); background: var(--accent-soft); text-decoration: none; }
.cfg-name  { font-weight: 600; color: var(--accent-2); }
.cfg-item:hover .cfg-name { color: var(--accent-ink); }
.cfg-blurb { font-size: 12px; color: var(--ink-mute); }

/* ---- One-time onboarding code -------------------------------------------- */
.code-jumbo {
  font-family: var(--mono); font-size: 30px; font-weight: 700; letter-spacing: .12em;
  color: var(--accent-ink); background: var(--accent-soft);
  border: 1px solid var(--accent-border); border-radius: var(--radius);
  padding: 14px 18px; margin: 0 0 12px; display: inline-block; user-select: all;
}

/* ---- Signing key block ---------------------------------------------------- */
.pubkey {
  font-family: var(--mono); font-size: 12px; line-height: 1.5;
  background: var(--panel-2); border: 1px solid var(--line-2);
  border-radius: var(--radius); padding: 12px; overflow-x: auto;
  white-space: pre; margin: 0;
}
.rollout-form { gap: 6px; }
.rollout-form select, .rollout-form input { min-width: 0; }

/* ===========================================================================
   OPERATOR WALLBOARD (TV) — LIGHT.
   Converted to light on request. Alarm states here use SOLID tint plates with
   dark ink, never thin coloured outlines: at TV viewing distance a 1px red
   border reads as "no alarm". Keep type large and fills strong.
   =========================================================================== */
body.wallboard { background: var(--bg); color: var(--ink); margin: 0; font-family: system-ui, sans-serif; }
.wallboard .wb-head { display: flex; align-items: center; gap: .8rem; padding: 1rem 1.6rem; background: var(--panel); border-bottom: 1px solid var(--line-2); }
.wallboard .wb-logo { display: block; height: auto; flex-shrink: 0; }
.wallboard .wb-head h1 { font-size: 1.4rem; margin: 0; flex: 1; font-weight: 600; }
.wallboard .wb-ts { color: var(--ink-dim); font-variant-numeric: tabular-nums; }
.wb-grid { display: grid; grid-template-columns: repeat(auto-fit, minmax(260px, 1fr)); gap: 1rem; padding: 1.2rem 1.6rem; }
.wb-tile { background: var(--panel); border: 1px solid var(--line-2); border-radius: 12px; padding: 1rem 1.2rem; min-height: 150px; box-shadow: var(--shadow); }
.wb-tile h2 { margin: 0 0 .4rem; font-size: .95rem; font-weight: 600; color: var(--ink-dim); text-transform: uppercase; letter-spacing: .04em; }
.wb-big { font-size: 4.2rem; font-weight: 700; line-height: 1.05; font-variant-numeric: tabular-nums; }
.wb-big .wb-sub { font-size: 1.6rem; color: var(--ink-mute); margin-left: .3rem; }
.wb-foot { color: var(--ink-dim); margin-top: .3rem; }
.wb-wide { grid-column: span 2; }
.wb-bad { border-color: var(--crit); background: var(--crit-bg); box-shadow: inset 6px 0 0 var(--crit); }
.wb-bad .wb-big { color: var(--crit-ink); }
#wb-spark { width: 100%; height: 120px; }
.wb-bar { fill: #0b6bcb; } .wb-bar0 { fill: #dbe3ec; }
.wb-list { list-style: none; margin: 0; padding: 0; font-size: 1.15rem; }
.wb-list li { padding: .3rem 0; border-bottom: 1px solid var(--line); }
.wb-critical { color: var(--crit-ink); font-weight: 600; }
.wb-warning { color: var(--warn-ink); font-weight: 600; }
.wb-info { color: var(--ink-dim); }
.wb-ok { color: var(--ok-ink); }
@media (min-width: 1400px) { .wb-big { font-size: 5.4rem; } }

/* Wallboard: map + charts boards */
#wbmap { position: fixed; inset: 0; background: var(--bg); }
.wb-map-title { position: fixed; top: .8rem; left: 1rem; z-index: 1000; display: flex; align-items: center; gap: .6rem;
  background: rgba(255,255,255,.92); border: 1px solid var(--line-2); border-radius: 10px; padding: .5rem .9rem; font-weight: 600; box-shadow: var(--shadow); }
.wb-map-legend { position: fixed; bottom: .8rem; left: 1rem; z-index: 1000; display: flex; gap: 1.1rem; align-items: center;
  background: rgba(255,255,255,.92); border: 1px solid var(--line-2); border-radius: 10px; padding: .5rem .9rem; box-shadow: var(--shadow); }
.wb-map-legend span { display: inline-flex; align-items: center; gap: .35rem; }
.wb-dot { display: inline-block; width: 14px; height: 14px; border-radius: 50%; border: 2px solid #fff; box-shadow: 0 0 0 1px rgba(17,24,39,.35); }
.wb-dot.wb-ok { background: var(--ok); }
.wb-dot.wb-warning { background: var(--warn); }
.wb-dot.wb-offline { background: var(--ink-mute); }
.wb-dot.wb-critical { background: var(--crit); animation: wbpulse 1.2s ease-in-out infinite; }
@keyframes wbpulse { 0%,100% { box-shadow: 0 0 0 0 rgba(239,68,68,.7); } 50% { box-shadow: 0 0 0 9px rgba(239,68,68,0); } }
.wb-maplabel { background: rgba(255,255,255,.92) !important; color: var(--ink) !important; border: 1px solid var(--line-2) !important; box-shadow: none !important; font-size: .72rem; padding: 1px 5px; }
.wb-maplabel::before { display: none; }

.wb-kpis { display: grid; grid-template-columns: repeat(4, 1fr); gap: 1rem; padding: 1rem 1.6rem 0; }
.wb-kpis h2 { margin: 0; font-size: .85rem; color: var(--ink-dim); text-transform: uppercase; letter-spacing: .04em; }
.wb-kpi { font-size: 3rem; font-weight: 700; font-variant-numeric: tabular-nums; }
.wb-kpi small { font-size: 1.2rem; color: var(--ink-mute); margin-left: .25rem; }
.wb-kpi-bad span { color: var(--crit-ink); }
.wb-grid2 { grid-template-columns: repeat(2, 1fr); }
.wb-bar-soc { fill: var(--ok-ink); }
.wb-hbar { fill: #0b6bcb; } .wb-hbar-warn { fill: var(--warn); } .wb-hbar-bad { fill: var(--crit); }
.wb-svgtext { fill: var(--ink-dim); font-size: 7px; font-family: system-ui, sans-serif; }
.wb-thline { stroke: var(--crit); stroke-width: .8; stroke-dasharray: 3 2; opacity: .8; }
.wb-axis { display: flex; justify-content: space-between; color: var(--ink-mute); font-size: .8rem; margin-top: .2rem; }
#ch-err, #ch-soc { width: 100%; height: 150px; } #ch-hot { width: 100%; height: 240px; }
/* Metric-tile sparklines (24h trend from ClickHouse) */
.metric .spark { display: block; width: 100%; max-width: 140px; height: 26px; margin: .25rem 0 .1rem; }
.metric .spark polyline { stroke: var(--accent-border); opacity: .9; }
/* Wallboard: board switcher + offline map fallback */
.wb-nav { position: fixed; right: 1rem; bottom: 1rem; display: flex; gap: .5rem; opacity: .75; z-index: 1000; }
.wb-nav:hover { opacity: 1; }
.wb-nav a { color: var(--ink-dim); background: var(--panel); border: 1px solid var(--line-2); border-radius: 8px; padding: .35rem .7rem; text-decoration: none; font-size: .85rem; box-shadow: var(--shadow); }
.wb-fallbackmap { width: 100%; height: 100%; display: block; }
.wb-grid-line { stroke: #dbe3ec; stroke-width: 1; }
.wb-map-offline { position: absolute; left: 50%; bottom: 2.4rem; transform: translateX(-50%); color: var(--ink-dim); font-size: .9rem; background: rgba(255,255,255,.9); border: 1px solid var(--line); padding: .3rem .8rem; border-radius: 8px; }

/* ---- BOM build sheet ------------------------------------------------------
   The replacement history folds INSIDE its own slot's row: "was this part ever
   swapped?" is a question about that slot, and splitting it into a second table
   is what made the old page unreadable. */
.bom-hist { margin-top: 6px; }
.bom-hist > summary {
  cursor: pointer; font-size: 12px; color: var(--accent-ink); font-weight: 600;
}
.bom-hist > summary:hover { text-decoration: underline; }
.bom-hist .recall-list { margin-top: 6px; font-size: 13px; }

/* The build sheet's action column holds two buttons, not a form. Give it room
   and stop the slot column hogging the width it does not use. */
.bom-act { gap: 6px; margin: 0; flex-wrap: nowrap; }
.bom-act button { white-space: nowrap; }

/* ---- Camera barcode scanning ---------------------------------------------
   The Scan button sits beside the serial input, never instead of it: typing
   must keep working when the camera does not. */
.scan-field { display: flex; gap: 8px; align-items: stretch; margin-top: 4px; }
/* The form styles give an input margin-top: 4px, which inside a flex row pushes
   it 4px below the button instead of moving the pair down. Move the margin to
   the wrapper so the two line up on the same baseline. */
.scan-field > input { flex: 1 1 auto; min-width: 0; margin-top: 0; }
.scan-btn { flex: 0 0 auto; white-space: nowrap; }
.scan-note { display: block; margin-top: 4px; }

.scan-overlay {
  position: fixed; inset: 0; z-index: 100;
  background: rgba(17, 24, 39, .82);
  display: flex; align-items: center; justify-content: center; padding: 16px;
}
.scan-box {
  background: #fff; border-radius: var(--radius); padding: 14px;
  width: min(520px, 100%); box-shadow: 0 18px 50px rgba(0,0,0,.35);
}
.scan-video {
  width: 100%; max-height: 60vh; border-radius: 8px; background: #000;
  display: block; object-fit: cover;
}
/* The video and its aiming frame share a positioned stage, so the frame can be
   sized against the picture instead of against nothing. It used to be
   `height: 28%` on a static element whose containing block had height:auto —
   a percentage with nothing to resolve against, so it collapsed to its own 4px
   of border and drew a flat line across the middle of the camera view. */
.scan-stage {
  position: relative; line-height: 0;
  border-radius: 8px; overflow: hidden;   /* clips the dimming to the picture */
}
/* Purely visual — detection reads the WHOLE frame, because cropping to the box
   would reject a code the technician can plainly see. */
.scan-frame {
  position: absolute; left: 12%; right: 12%; top: 30%; bottom: 30%;
  border: 2px solid var(--accent-border); border-radius: 8px;
  box-shadow: 0 0 0 9999px rgba(0,0,0,.18);
  pointer-events: none;
}
.scan-hint { margin: 14px 0 0; color: var(--ink-mute); font-size: 14px; text-align: center; }
.scan-error { color: var(--ink); font-weight: 600; }
.scan-actions { display: flex; justify-content: flex-end; margin-top: 12px; }
/* The decode is shown and must be accepted — never written into the field
   behind the technician's back. */
.scan-confirm { margin-top: 12px; }
.scan-read  { margin: 0; color: var(--ink-mute); font-size: 13px; }
.scan-value {
  /* .mono sets white-space: nowrap, which silently clipped a 34-character serial
     off the side of the panel on a phone. A serial that cannot be read in full
     cannot be checked, and checking it is the entire point. */
  white-space: normal; overflow-wrap: anywhere;
  margin: 4px 0 0; font-size: 20px; font-weight: 600; word-break: break-all;
  color: var(--ink); background: var(--accent-soft); border: 1px solid var(--accent-border);
  border-radius: 6px; padding: 10px 12px;
}
.scan-confirm .scan-actions { display: flex; gap: 8px; justify-content: flex-end; margin-top: 12px; }

/* ---- the photo / OCR path -------------------------------------------------
   The live scanner needs BarcodeDetector, which iOS does not have, and no
   browser anywhere can do OCR — so a photo is taken and the server decodes it.
   The file input is the mechanism, never something to look at. */
.scan-file { display: none; }
.scan-actions { gap: 8px; }

/* Read text is a GUESS, and the warning says so before the list, not after. */
.scan-note-ocr {
  margin: 8px 0 0; font-size: 13px; line-height: 1.45;
  color: var(--warn-ink); background: var(--warn-bg);
  border: 1px solid var(--warn-line); border-radius: 6px; padding: 8px 10px;
}

/* Candidates are BUTTONS, one per row, and none is pre-selected — picking is
   the human step this whole feature is built around. */
.scan-candidates { display: flex; flex-direction: column; gap: 6px; margin-top: 10px; }
.scan-candidate {
  display: flex; align-items: center; justify-content: space-between; gap: 10px;
  width: 100%; text-align: left; cursor: pointer;
  background: var(--accent-soft); border: 1px solid var(--accent-border);
  border-radius: 6px; padding: 10px 12px; font: inherit;
}
.scan-candidate:hover, .scan-candidate:focus-visible { border-color: var(--accent); }
.scan-candidate .scan-value {
  margin: 0; padding: 0; border: 0; background: none; font-size: 18px;
  /* min-width:0 so the flex item may actually shrink and wrap, rather than
     forcing its content width and pushing out of the row. */
  flex: 1 1 auto; min-width: 0;
}
/* OCR results are visually quieter than checksummed barcode results, because
   they are less trustworthy and should not read as equally authoritative. */
.scan-candidate.is-ocr { background: #fff; }
.scan-kind {
  flex: 0 0 auto; font-size: 11px; text-transform: uppercase; letter-spacing: .04em;
  color: var(--ink-mute); white-space: nowrap;
}

/* ---- single sign-on on the login card -------------------------------------
   The SSO button leads; the password form stays underneath because with
   Keycloak on it is the break-glass route, and an outage is the worst possible
   time to be hunting for a hidden form. */
.btn-sso { display: block; text-align: center; text-decoration: none; margin-top: 14px; }
.login-or {
  display: flex; align-items: center; gap: 10px;
  margin: 18px 0 4px; color: var(--ink-mute); font-size: 12px;
}
.login-or::before, .login-or::after {
  content: ''; flex: 1 1 auto; height: 1px; background: var(--line);
}
