/* ASILLA VMS — operator console design system.
 *
 * Shape follows the conventions operators already know from AWS Console,
 * Datadog and Grafana: dark navigation rail, light working surface, dense
 * data tables, one accent colour reserved for actions, and colour used only
 * to carry state (ok / warn / bad) rather than decoration.
 *
 * The token names are unchanged from the previous stylesheet because
 * templates and console JS reference them inline (var(--accent), var(--ok),
 * var(--bad)); only their values moved to the light palette.
 */

:root {
  /* surfaces */
  --bg: #f5f7fa;
  --panel: #ffffff;
  --panel-2: #f8fafc;
  --line: #e8ecf2;
  --line-soft: #eef1f6;

  /* nav rail */
  --nav: #0f1729;
  --nav-2: #16203a;
  --nav-hover: rgba(255, 255, 255, 0.07);
  --nav-text: #aab6c8;
  --nav-dim: #74839b;

  /* text */
  --text: #18212c;
  --text-2: #455364;
  --muted: #6a7787;

  /* accent + state */
  --accent: #2563eb;
  --accent-hover: #1d4ed8;
  --accent-weak: #eaf2fe;
  --ok: #1b7f43;
  --ok-weak: #e7f5ec;
  --warn: #9a5b00;
  --warn-weak: #fdf3e3;
  --bad: #c0362c;
  --bad-weak: #fdeceb;

  /* type + shape */
  --sans: -apple-system, BlinkMacSystemFont, "Segoe UI", Inter, Roboto, "Helvetica Neue", Arial, sans-serif;
  --mono: ui-monospace, SFMono-Regular, "SF Mono", Menlo, Consolas, monospace;
  --radius: 12px;
  --radius-sm: 8px;
  /* Every control in a filter row is this tall. Before it, a row held a 55px
     segmented control, a 35px select and a 32px button, so nothing lined up
     with anything and each page looked subtly broken in a different way. */
  --control-h: 34px;
  --shadow: 0 1px 2px rgba(16, 24, 40, 0.04), 0 1px 3px rgba(16, 24, 40, 0.06);
  --shadow-lift: 0 6px 18px rgba(16, 24, 40, 0.08);
}

* { box-sizing: border-box; }

body {
  margin: 0;
  min-height: 100vh;
  font-family: var(--sans);
  font-size: 14px;
  line-height: 1.55;
  color: var(--text);
  background: var(--bg);
  -webkit-font-smoothing: antialiased;
}

a { color: var(--accent); }

/* ── layout ─────────────────────────────────────────────────────────── */

.shell {
  display: grid;
  /* `minmax(0, 1fr)`, not `1fr`: a grid track's automatic minimum is its
   * content, so a table wider than the viewport widened the track — and with it
   * the rail and the whole page — instead of scrolling inside its own
   * `.table-wrap`. The page then scrolled sideways as a body, which moves the
   * navigation off screen. */
  grid-template-columns: 244px minmax(0, 1fr);
  min-height: 100vh;
}

.side {
  background: var(--nav);
  display: flex;
  flex-direction: column;
  gap: 0.25rem;
  padding: 0;
  position: sticky;
  top: 0;
  height: 100vh;
  overflow-y: auto;
}

.brand {
  font-size: 0.95rem;
  font-weight: 650;
  letter-spacing: 0.14em;
  color: #fff;
  padding: 1.15rem 1.1rem 0;
}
.brand span { color: #6aa8ff; }

.brand-sub {
  font-size: 0.66rem;
  letter-spacing: 0.16em;
  text-transform: uppercase;
  color: var(--nav-dim);
  padding: 0.15rem 1.1rem 0.9rem;
  border-bottom: 1px solid rgba(255, 255, 255, 0.07);
}

/* site switcher — the "which account am I in" control, like AWS's region menu */
.site-switcher {
  padding: 0.85rem 0.85rem 0.7rem;
  border-bottom: 1px solid rgba(255, 255, 255, 0.07);
}
.site-switcher::before {
  content: "Site";
  display: block;
  font-size: 0.62rem;
  letter-spacing: 0.14em;
  text-transform: uppercase;
  color: var(--nav-dim);
  margin-bottom: 0.3rem;
}
.site-switcher select {
  width: 100%;
  appearance: none;
  background: rgba(255, 255, 255, 0.05) url("data:image/svg+xml;charset=utf-8,%3Csvg xmlns='http://www.w3.org/2000/svg' width='10' height='6'%3E%3Cpath d='M0 0l5 6 5-6z' fill='%237c8aa0'/%3E%3C/svg%3E") no-repeat right 0.6rem center;
  border: 1px solid rgba(255, 255, 255, 0.14);
  color: #eaf0f7;
  padding: 0.45rem 1.6rem 0.45rem 0.55rem;
  border-radius: var(--radius-sm);
  font-family: var(--mono);
  font-size: 0.76rem;
}
.site-switcher select:focus {
  outline: 2px solid var(--accent);
  outline-offset: 1px;
}

.side nav {
  display: flex;
  flex-direction: column;
  gap: 1px;
  padding: 0.7rem 0.5rem;
}
.nav-label {
  font-size: 0.62rem;
  letter-spacing: 0.14em;
  text-transform: uppercase;
  color: var(--nav-dim);
  padding: 0.7rem 0.6rem 0.3rem;
}
.side nav a {
  display: flex;
  align-items: center;
  gap: 0.6rem;
  color: var(--nav-text);
  text-decoration: none;
  padding: 0.48rem 0.6rem;
  border-radius: var(--radius-sm);
  font-size: 0.85rem;
  position: relative;
}
.side nav a:hover { background: var(--nav-hover); color: #fff; }
.side nav a.on {
  /* A filled pill rather than a left-edge bar. At fifteen nav items the bar
     was easy to miss on a dark rail; a solid block is unmissable and is what
     the operator console this mirrors uses. */
  background: var(--accent);
  color: #fff;
  border-radius: var(--radius-sm);
  font-weight: 600;
}
.side nav a.on::before { content: none; }
.side nav .ic {
  width: 15px;
  height: 15px;
  flex: none;
  opacity: 0.8;
}
.side nav a.on .ic { opacity: 1; }

/* unacked POPUP counter */
.side nav em {
  font-style: normal;
  margin-left: auto;
  background: var(--bad);
  color: #fff;
  font-size: 0.66rem;
  font-weight: 650;
  min-width: 1.15rem;
  text-align: center;
  padding: 0.05rem 0.32rem;
  border-radius: 999px;
}

.side-foot {
  margin-top: auto;
  padding: 0.85rem 1.1rem 1.1rem;
  border-top: 1px solid rgba(255, 255, 255, 0.07);
  font-size: 0.8rem;
}
.side-foot > a {
  color: #6aa8ff;
  text-decoration: none;
  font-size: 0.82rem;
}
.side-foot > a:hover { text-decoration: underline; }
.side-foot .muted { color: var(--nav-dim); }
.side-foot form { margin-top: 0.6rem; }
.side-foot .btn.ghost {
  width: 100%;
  padding: 0.35rem 0.5rem;
  font-size: 0.76rem;
  color: var(--nav-text);
  border-color: rgba(255, 255, 255, 0.18);
  background: transparent;
}
.side-foot .btn.ghost:hover {
  background: var(--nav-hover);
  color: #fff;
  border-color: rgba(255, 255, 255, 0.3);
}

.muted {
  color: var(--muted);
  font-family: var(--mono);
  font-size: 0.7rem;
  margin-top: 0.35rem;
}

/* ── top bar ────────────────────────────────────────────────────────── */

.topbar {
  display: flex;
  align-items: center;
  gap: 0.75rem;
  height: 46px;
  padding: 0 1.5rem;
  background: var(--panel);
  border-bottom: 1px solid var(--line);
  position: sticky;
  top: 0;
  z-index: 5;
}
.crumbs {
  display: flex;
  align-items: center;
  gap: 0.4rem;
  font-size: 0.8rem;
  color: var(--muted);
}
.crumbs a { color: var(--muted); text-decoration: none; }
.crumbs a:hover { color: var(--accent); }
.crumbs .sep { opacity: 0.5; }
.crumbs .here { color: var(--text); font-weight: 550; }
.topbar-right {
  margin-left: auto;
  display: flex;
  align-items: center;
  gap: 0.6rem;
}

/* environment chip — operators must never confuse STG with production */
.env {
  font-family: var(--mono);
  font-size: 0.66rem;
  font-weight: 600;
  letter-spacing: 0.1em;
  text-transform: uppercase;
  padding: 0.2rem 0.45rem;
  border-radius: 3px;
  background: var(--warn-weak);
  color: var(--warn);
  border: 1px solid rgba(154, 91, 0, 0.25);
}
.who {
  font-size: 0.78rem;
  color: var(--text-2);
  display: flex;
  align-items: center;
  gap: 0.45rem;
}
.avatar {
  width: 22px;
  height: 22px;
  border-radius: 50%;
  background: var(--accent);
  color: #fff;
  font-size: 0.68rem;
  font-weight: 650;
  display: grid;
  place-items: center;
  text-transform: uppercase;
}

.main { padding: 0; }
.page { padding: 1.25rem 1.5rem 2.5rem; }

/* ── page header ────────────────────────────────────────────────────── */

.hrow {
  display: flex;
  justify-content: space-between;
  align-items: flex-start;
  gap: 1rem;
  margin-bottom: 1.1rem;
  flex-wrap: wrap;
}
h1 {
  margin: 0;
  font-size: 1.32rem;
  font-weight: 620;
  letter-spacing: -0.01em;
}
.hrow .sub {
  margin-top: 0.15rem;
  font-size: 0.82rem;
  color: var(--muted);
}
h2 { font-size: 1rem; font-weight: 600; margin: 0; }

.pills { display: flex; gap: 0.4rem; flex-wrap: wrap; align-items: center; }

.pill {
  display: inline-flex;
  align-items: center;
  gap: 0.35rem;
  font-size: 0.74rem;
  padding: 0.22rem 0.55rem;
  border: 1px solid var(--line);
  border-radius: 999px;
  background: var(--panel);
  color: var(--text-2);
  white-space: nowrap;
}
.pill::before {
  content: "";
  width: 6px;
  height: 6px;
  border-radius: 50%;
  background: var(--muted);
  flex: none;
}
.pill.ok { color: var(--ok); background: var(--ok-weak); border-color: rgba(27, 127, 67, 0.22); }
.pill.ok::before { background: var(--ok); }
.pill.warn { color: var(--warn); background: var(--warn-weak); border-color: rgba(154, 91, 0, 0.22); }
.pill.warn::before { background: var(--warn); }
.pill.bad { color: var(--bad); background: var(--bad-weak); border-color: rgba(192, 54, 44, 0.22); }
.pill.bad::before { background: var(--bad); }

/* segmented filter: one control, current selection obvious at a glance */
.seg {
  display: inline-flex;
  align-items: stretch;
  border: 1px solid #ccd4dd;
  border-radius: var(--radius-sm);
  overflow: hidden;
  background: var(--panel);
  min-height: var(--control-h);
}
.seg a {
  display: inline-flex;
  align-items: center;
  /* A two-word tab ("Needs human") wrapped, which made the whole control 43px
     against the 34px select beside it — the misalignment was a wrap, not a
     margin. */
  white-space: nowrap;
  padding: 0 0.85rem;
  font-size: 0.82rem;
  font-weight: 500;
  color: var(--text-2);
  text-decoration: none;
  border-right: 1px solid var(--line);
}
/* Standalone it is a row of tabs above a table and needs air beneath it;
   inside a filter row that same margin pushed it off the line the select and
   button sit on. Spacing belongs to the context, not to the control. */
.seg { margin-bottom: 1rem; }
.filters .seg, .hrow .seg { margin-bottom: 0; }
.seg a:last-child { border-right: 0; }
.seg a:hover { background: var(--panel-2); color: var(--text); }
.seg a.on { background: var(--accent); color: #fff; font-weight: 550; }

/* ── stat tiles ─────────────────────────────────────────────────────── */

.stats {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(210px, 1fr));
  gap: 1rem;
  margin-bottom: 1.35rem;
}
.stat {
  background: var(--panel);
  border: 1px solid var(--line);
  border-radius: var(--radius);
  padding: 1.1rem 1.15rem;
  box-shadow: var(--shadow);
  /* Room for an icon tile on the left when a page supplies one, and for a
     delta chip pinned top-right. Both are optional: every existing page
     renders `.stat > .k + .v` and must keep working untouched. */
  position: relative;
  display: grid;
  grid-template-columns: auto 1fr;
  grid-template-areas: "tile body";
  column-gap: 0.9rem;
  align-items: center;
}
.stat > .k, .stat > .v, .stat > .s { grid-column: 2; }
.stat > .ic-tile {
  grid-area: tile;
  width: 40px;
  height: 40px;
  border-radius: 10px;
  display: grid;
  place-items: center;
  background: var(--accent-weak);
  color: var(--accent);
}
.stat > .ic-tile .ic { width: 18px; height: 18px; opacity: 1; }
.stat.ok > .ic-tile { background: var(--ok-weak); color: var(--ok); }
.stat.warn > .ic-tile { background: var(--warn-weak); color: var(--warn); }
.stat.bad > .ic-tile { background: var(--bad-weak); color: var(--bad); }
.stat > .delta {
  position: absolute;
  top: 0.9rem;
  right: 1rem;
  font-size: 0.72rem;
  font-weight: 600;
  color: var(--ok);
  background: var(--ok-weak);
  border-radius: 999px;
  padding: 0.12rem 0.45rem;
}
.stat > .delta.down { color: var(--bad); background: var(--bad-weak); }
.stat .k {
  font-size: 0.72rem;
  letter-spacing: 0.02em;
  text-transform: none;
  color: var(--muted);
  font-weight: 500;
}
.stat .v {
  font-size: 1.85rem;
  font-weight: 600;
  letter-spacing: -0.02em;
  line-height: 1.15;
  margin-top: 0.3rem;
}
.stat .s { font-size: 0.76rem; color: var(--muted); }
.stat.ok .v { color: var(--ok); }
.stat.warn .v { color: var(--warn); }
.stat.bad .v { color: var(--bad); }

/* ── grids + panels ─────────────────────────────────────────────────── */

.grid { display: grid; grid-template-columns: 1.35fr 0.65fr; gap: 0.95rem; align-items: start; }
.grid2 { display: grid; grid-template-columns: repeat(2, minmax(0, 1fr)); gap: 0.95rem; align-items: start; }

.panel {
  background: var(--panel);
  border: 1px solid var(--line);
  border-radius: var(--radius);
  box-shadow: var(--shadow);
  overflow: hidden;
}

.panel-h {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 0.75rem;
  padding: 0.7rem 1rem;
  border-bottom: 1px solid var(--line);
  background: var(--panel-2);
  font-size: 0.82rem;
  font-weight: 600;
  color: var(--text);
}
/* a trailing span is the header's counter/meta — but only when it is not
   also the title (single-span headers keep the title treatment) */
.panel-h > span:last-child:not(:first-child),
.panel-h .meta {
  font-family: var(--mono);
  font-size: 0.72rem;
  font-weight: 400;
  color: var(--muted);
}
.panel-h a { font-size: 0.76rem; font-weight: 500; text-decoration: none; }
.panel-h a:hover { text-decoration: underline; }

/* ── tables ─────────────────────────────────────────────────────────── */

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

table { width: 100%; border-collapse: collapse; font-size: 0.84rem; }
th, td {
  text-align: left;
  padding: 0.8rem 1.1rem;
  border-bottom: 1px solid var(--line-soft);
  /* Middle, not top. With taller rows a top-aligned cell floats away from the
     badge or status dot beside it and the row stops reading as one thing. */
  vertical-align: middle;
}
th {
  font-size: 0.68rem;
  letter-spacing: 0.08em;
  text-transform: uppercase;
  color: var(--muted);
  font-weight: 600;
  background: var(--panel);
  border-bottom: 1px solid var(--line);
  white-space: nowrap;
  position: sticky;
  top: 0;
}
tbody tr:hover { background: var(--panel-2); }
tbody tr:last-child td { border-bottom: 0; }
td strong { font-weight: 600; }

/* The role capability matrix.
 *
 * It had no styles at all: the spans flowed as inline text, so "ADMIN" sat
 * against the row labels and the other roles landed wherever the line broke.
 * It reads as a table and is built as a grid, so it needs to be told how many
 * columns it has — the template passes --cap-cols.
 */
.cap-grid {
  display: grid;
  grid-template-columns: minmax(10rem, 1.6fr) repeat(var(--cap-cols, 3), minmax(5rem, 1fr));
  align-items: center;
  padding: 0.35rem 1.1rem 0.9rem;
}
.cap-grid > span {
  padding: 0.55rem 0.5rem;
  border-bottom: 1px solid var(--line-soft);
  font-size: 0.84rem;
}
/* A column of centred ticks is readable at a glance in a way a ragged left
   edge is not. The verdict cells carry their own class, so no positional
   selector is needed — and the first .hd is the empty corner, which centring
   does nothing to. */
.cap-grid > .hd,
.cap-grid > .cap-yes,
.cap-grid > .cap-no { text-align: center; }
.cap-grid > .hd:first-child { text-align: left; }
.cap-grid > .hd {
  font-size: 0.68rem;
  letter-spacing: 0.08em;
  text-transform: uppercase;
  color: var(--muted);
  font-weight: 600;
  border-bottom: 1px solid var(--line);
}
.cap-grid > .cap-yes { color: var(--ok); font-weight: 600; }
.cap-grid > .cap-no { color: var(--muted); }

/* The site combobox.
 *
 * The menu is positioned rather than inline so opening it does not push the
 * nav down, and it sits above everything because in the rail it overlaps the
 * navigation immediately below it.
 */
.sp { position: relative; }
.sp .site-pick { padding-right: 1.9rem; }
.sp-toggle {
  position: absolute;
  right: 0.45rem;
  top: 50%;
  transform: translateY(-50%);
  display: grid;
  place-items: center;
  width: 20px;
  height: 20px;
  padding: 0;
  border: 0;
  background: none;
  color: var(--muted);
  cursor: pointer;
  min-height: 0;
}
.sp-menu {
  position: absolute;
  z-index: 60;
  top: calc(100% + 4px);
  left: 0;
  right: 0;
  margin: 0;
  padding: 0.25rem;
  list-style: none;
  /* Tall enough to browse, short enough that it cannot cover the page. Two
     hundred and fifty sites scroll; they do not paginate. */
  max-height: 17rem;
  overflow-y: auto;
  background: var(--panel);
  border: 1px solid var(--line);
  border-radius: var(--radius-sm);
  box-shadow: var(--shadow-lift);
}
.sp-menu li {
  padding: 0.4rem 0.55rem;
  border-radius: 6px;
  cursor: pointer;
  font-size: 0.82rem;
  color: var(--text);
}
.sp-menu li b { display: block; font-family: var(--mono); font-weight: 600; }
.sp-menu li span {
  display: block;
  font-size: 0.74rem;
  color: var(--muted);
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}
.sp-menu li:hover, .sp-menu li.on { background: var(--accent-weak); }
.sp-menu .sp-empty { color: var(--muted); cursor: default; font-style: italic; }
.sp-menu .sp-empty:hover { background: none; }

/* What a box is running on. Sits above its settings table, because the
 * question "why is this site slow" is answered here far more often than by
 * any individual setting below it.
 */
.box-runtime {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(15rem, 1fr));
  gap: 0.9rem;
  padding: 0.85rem 1.1rem;
  border-bottom: 1px solid var(--line-soft);
}
.box-runtime > div { display: flex; flex-direction: column; gap: 0.15rem; }
.box-runtime em {
  font-style: normal;
  font-size: 0.68rem;
  letter-spacing: 0.08em;
  text-transform: uppercase;
  color: var(--muted);
  font-weight: 600;
}
.box-runtime strong { font-size: 0.95rem; font-weight: 600; }
.box-runtime strong.none { color: var(--muted); font-weight: 500; }
.box-runtime span { font-size: 0.76rem; color: var(--muted); }
.box-warn {
  padding: 0.7rem 1.1rem;
  background: var(--warn-weak);
  color: var(--warn);
  font-size: 0.82rem;
  border-bottom: 1px solid var(--line-soft);
}

/* ── badges ─────────────────────────────────────────────────────────── */

.badge {
  display: inline-flex;
  align-items: center;
  gap: 0.32rem;
  font-size: 0.72rem;
  font-weight: 550;
  padding: 0.14rem 0.45rem;
  border-radius: 4px;
  background: #eef1f4;
  color: var(--text-2);
  white-space: nowrap;
}
.badge::before {
  content: "";
  width: 6px;
  height: 6px;
  border-radius: 50%;
  background: currentColor;
  flex: none;
}
.badge.Running { background: var(--ok-weak); color: var(--ok); }
.badge.Connecting { background: var(--warn-weak); color: var(--warn); }
.badge.Disabled { background: #eef1f4; color: var(--muted); }
.badge.Error, .badge.Stopped { background: var(--bad-weak); color: var(--bad); }

/* ── forms ──────────────────────────────────────────────────────────── */

.form {
  display: grid;
  grid-template-columns: repeat(2, minmax(0, 1fr));
  gap: 0.75rem;
  padding: 1rem;
}
.form label {
  display: flex;
  flex-direction: column;
  gap: 0.3rem;
  font-size: 0.75rem;
  font-weight: 550;
  color: var(--text-2);
}
.form .span2 { grid-column: 1 / -1; }
/* controls are styled globally: several pages place inputs outside .form */
input,
select,
textarea {
  background: var(--panel);
  border: 1px solid #ccd4dd;
  color: var(--text);
  padding: 0.4rem 0.65rem;
  min-height: var(--control-h);
  border-radius: var(--radius-sm);
  font-family: var(--sans);
  font-size: 0.84rem;
  width: 100%;
}
input::placeholder, textarea::placeholder { color: #9aa6b4; }
input:focus,
select:focus,
textarea:focus {
  outline: none;
  border-color: var(--accent);
  box-shadow: 0 0 0 3px var(--accent-weak);
}
input[type="checkbox"], input[type="radio"] {
  width: auto;
  accent-color: var(--accent);
  /* The control height belongs to text inputs, selects and buttons. Applied
     to a checkbox it draws a 34px-tall empty box in the filter row. */
  min-height: 0;
  padding: 0;
}
textarea { min-height: 90px; font-family: var(--mono); font-size: 0.79rem; line-height: 1.55; }

/* ── buttons ────────────────────────────────────────────────────────── */

.btn {
  appearance: none;
  display: inline-flex;
  align-items: center;
  justify-content: center;
  gap: 0.4rem;
  border: 1px solid var(--accent);
  background: var(--accent);
  color: #fff;
  font-family: var(--sans);
  font-size: 0.82rem;
  font-weight: 550;
  padding: 0 0.9rem;
  min-height: var(--control-h);
  border-radius: var(--radius-sm);
  cursor: pointer;
  text-decoration: none;
  white-space: nowrap;
  transition: background 120ms ease, box-shadow 120ms ease;
}
.btn:hover { background: var(--accent-hover); border-color: var(--accent-hover); }
.btn:focus-visible { outline: none; box-shadow: 0 0 0 3px var(--accent-weak); }
.btn.ghost {
  background: var(--panel);
  color: var(--text);
  border-color: #ccd4dd;
}
.btn.ghost:hover { background: var(--panel-2); border-color: var(--muted); }
.btn.danger { background: var(--bad); border-color: var(--bad); color: #fff; }
.btn.danger:hover { background: #a52c23; border-color: #a52c23; }
/* destructive but not primary: reads as a warning until hovered */
.btn.ghost.danger {
  background: var(--panel);
  color: var(--bad);
  border-color: rgba(192, 54, 44, 0.3);
}
.btn.ghost.danger:hover { background: var(--bad); border-color: var(--bad); color: #fff; }
.btn.sm { padding: 0.28rem 0.6rem; font-size: 0.76rem; }
/* row actions stay compact so table density is preserved */
td .btn { padding: 0.24rem 0.55rem; font-size: 0.76rem; }
td form + form { margin-left: 0.3rem; }

/* ── POPUP alert callout ────────────────────────────────────────────── */

.popup-flash {
  margin: 1rem;
  padding: 0.9rem 1rem;
  border: 1px solid rgba(192, 54, 44, 0.28);
  border-left: 3px solid var(--bad);
  background: var(--bad-weak);
  border-radius: 4px;
  animation: in 220ms ease;
}
@keyframes in { from { opacity: 0; transform: translateY(4px); } to { opacity: 1; transform: none; } }
.popup-flash .k {
  font-family: var(--mono);
  font-size: 0.68rem;
  font-weight: 600;
  letter-spacing: 0.1em;
  color: var(--bad);
}
.popup-flash h2 { font-size: 1.05rem; }

/* ── lists ──────────────────────────────────────────────────────────── */

.list { list-style: none; margin: 0; padding: 0; }
.list li {
  padding: 0.65rem 1rem;
  border-bottom: 1px solid var(--line-soft);
  font-size: 0.84rem;
}
.list li:last-child { border-bottom: 0; }
.list li:hover { background: #f7fafd; }

.mono { font-family: var(--mono); font-size: 0.74rem; color: var(--muted); }

/* command / payload blocks get the terminal treatment, as on the edge console */
pre {
  font-family: var(--mono);
  font-size: 0.74rem;
  line-height: 1.65;
  background: #0d1117;
  color: #c8d4e3;
  border-radius: 5px;
  padding: 0.8rem 0.9rem;
  margin: 1rem;
  overflow: auto;
  white-space: pre-wrap;
  word-break: break-word;
}
pre code { background: transparent; color: inherit; padding: 0; font-size: inherit; }

code {
  font-family: var(--mono);
  font-size: 0.78rem;
  background: #eef1f4;
  padding: 0.08rem 0.3rem;
  border-radius: 3px;
  color: var(--text-2);
}

/* empty states read as guidance, not as an error */
.empty {
  padding: 1.5rem 1rem;
  text-align: center;
  color: var(--muted);
  font-size: 0.84rem;
}

/* ── sign in ────────────────────────────────────────────────────────── */

.login-shell {
  min-height: 100vh;
  display: grid;
  place-items: center;
  padding: 1rem;
  background:
    radial-gradient(700px 340px at 50% -5%, rgba(22, 104, 220, 0.07), transparent 70%),
    var(--bg);
}
.login-panel {
  width: 100%;
  max-width: 360px;
  padding: 1.6rem 1.5rem 1.5rem;
  box-shadow: var(--shadow-lift);
}
.login-panel .brand { padding: 0; text-align: center; color: var(--text); }
.login-panel .brand span { color: var(--accent); }
.login-panel .brand-sub {
  padding: 0.2rem 0 1.2rem;
  text-align: center;
  border-bottom: 0;
  color: var(--muted);
}
.login-form { grid-template-columns: 1fr; gap: 0.8rem; padding: 0; }
.login-form .btn { width: 100%; padding: 0.55rem; }
.login-foot {
  margin-top: 1.1rem;
  padding-top: 0.9rem;
  border-top: 1px solid var(--line-soft);
  text-align: center;
  font-size: 0.74rem;
  color: var(--muted);
}
.msg.err {
  background: var(--bad-weak);
  border: 1px solid rgba(192, 54, 44, 0.25);
  color: var(--bad);
  font-size: 0.8rem;
  padding: 0.5rem 0.65rem;
  border-radius: 4px;
  margin-bottom: 0.9rem;
}

/* ── PTZ overlay (cameras page) ─────────────────────────────────────── */

.ptz-controls {
  position: absolute;
  top: 0.4rem;
  right: 0.4rem;
  display: grid;
  grid-template-columns: repeat(3, 1.7rem);
  gap: 0.15rem;
  background: rgba(10, 15, 22, 0.72);
  padding: 0.3rem;
  border-radius: 4px;
  backdrop-filter: blur(2px);
}
.ptz-controls button {
  appearance: none;
  border: 1px solid rgba(255, 255, 255, 0.18);
  background: rgba(255, 255, 255, 0.08);
  color: #fff;
  font-size: 0.78rem;
  line-height: 1;
  padding: 0.26rem 0;
  border-radius: 3px;
  cursor: pointer;
}
.ptz-controls button:hover { background: var(--accent); border-color: var(--accent); }

/* ── responsive ─────────────────────────────────────────────────────── */

@media (max-width: 1000px) {
  .shell { grid-template-columns: minmax(0, 1fr); }
  .side { position: static; height: auto; }
  .side nav { flex-direction: row; flex-wrap: wrap; }
  .side nav a.on::before { display: none; }
  .grid, .grid2 { grid-template-columns: 1fr; }
  .page { padding: 1rem; }
}

/* ── per-camera feature configuration ───────────────────────────────────────
 *
 * One list of feature cards plus a geometry canvas. Laid out so the two things
 * an operator scans for are the two things that stand out: which features are
 * on (left border), and which are on but unusable because nothing was drawn
 * (amber block). Everything else is deliberately quiet.
 */
.pad { padding: 0.9rem 1rem; }

.note {
  border: 1px solid var(--line);
  border-left: 3px solid var(--accent);
  background: var(--panel);
  border-radius: var(--radius);
  padding: 0.75rem 0.9rem;
  margin-bottom: 1rem;
  color: var(--text-2);
  font-size: 0.86rem;
  line-height: 1.5;
}
.note.ok { border-left-color: var(--ok); background: var(--ok-weak); }
/* A note that is telling you something is wrong rather than explaining how the
 * page works. Used for "these cameras follow no profile" — a fact with a next
 * action, which is different from documentation. */
.note.warn { border-left-color: var(--warn); background: var(--warn-weak); }
.note.warn strong { color: var(--text); }

.feat-list { display: flex; flex-direction: column; }
.feat {
  display: grid;
  grid-template-columns: minmax(0, 1fr) minmax(300px, 380px);
  gap: 1.25rem;
  padding: 0.9rem 1rem;
  border-top: 1px solid var(--line-soft);
  /* Reserved so switching a feature on does not shift the row sideways. */
  border-left: 3px solid transparent;
}
.feat:first-child { border-top: none; }
.feat.on { border-left-color: var(--ok); background: var(--panel-2); }

.feat-head { display: flex; align-items: center; gap: 0.4rem; flex-wrap: wrap; }
.feat-sum { color: var(--text-2); font-size: 0.86rem; margin-top: 0.2rem; line-height: 1.45; }
.feat-caveat {
  margin-top: 0.4rem;
  font-size: 0.78rem;
  color: var(--muted);
  line-height: 1.45;
}
.feat-warn {
  margin-top: 0.5rem;
  padding: 0.4rem 0.55rem;
  border-radius: 4px;
  background: var(--warn-weak);
  color: var(--warn);
  font-size: 0.78rem;
  font-weight: 500;
}

.tag {
  display: inline-block;
  padding: 0.08rem 0.4rem;
  border-radius: 3px;
  background: var(--panel-2);
  border: 1px solid var(--line);
  color: var(--muted);
  font-size: 0.7rem;
  font-family: var(--mono);
  white-space: nowrap;
}
.tag.over { background: var(--accent-weak); border-color: #b9d5fb; color: var(--accent); }

/* Three-way inherit/on/off. A segmented control rather than a dropdown so the
 * state is readable while scrolling a list of thirteen. */
.tri { display: flex; border: 1px solid var(--line); border-radius: var(--radius); overflow: hidden; }
.tri label {
  flex: 1;
  text-align: center;
  padding: 0.32rem 0.3rem;
  font-size: 0.78rem;
  cursor: pointer;
  background: var(--panel);
  border-right: 1px solid var(--line);
  color: var(--text-2);
  white-space: nowrap;
}
.tri label:last-child { border-right: none; }
.tri label input { position: absolute; opacity: 0; pointer-events: none; }
.tri label.sel { background: var(--accent); color: #fff; font-weight: 600; }
.tri label:hover:not(.sel) { background: var(--accent-weak); }

.feat-params { margin-top: 0.6rem; display: flex; flex-direction: column; gap: 0.3rem; }
.feat-params label {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 0.6rem;
  font-size: 0.8rem;
  color: var(--text-2);
}
.feat-params label em { color: var(--muted); font-style: normal; font-size: 0.72rem; }
.feat-params input[type="number"], .feat-params select { width: 7.5rem; }
.feat-params small { font-size: 0.71rem; line-height: 1.4; display: block; margin: -0.1rem 0 0.25rem; }
.feat-geo { margin-top: 0.55rem; font-size: 0.75rem; color: var(--muted); }

/* Geometry canvas. The surface is a fixed 960x540 so normalized coordinates
 * round-trip identically regardless of how wide the panel happens to be. */
.geo-wrap {
  position: relative;
  max-width: 960px;
  border: 1px solid var(--line);
  border-radius: var(--radius);
  overflow: hidden;
  background: var(--panel-2);
}
.geo-wrap canvas { display: block; width: 100%; height: auto; cursor: crosshair; }
.geo-tools {
  display: flex;
  align-items: flex-end;
  gap: 0.6rem;
  flex-wrap: wrap;
  margin-top: 0.7rem;
}
.geo-tools label { font-size: 0.8rem; }

.cam-pick {
  display: grid;
  grid-template-columns: repeat(auto-fill, minmax(210px, 1fr));
  gap: 0.35rem;
  margin: 0.7rem 0;
}
.cam-pick label {
  display: flex;
  align-items: baseline;
  gap: 0.4rem;
  padding: 0.3rem 0.45rem;
  border: 1px solid var(--line);
  border-radius: 4px;
  font-size: 0.82rem;
  cursor: pointer;
}
.cam-pick label:hover { background: var(--accent-weak); }

/* Zones get drawn on the floor, which is the bottom of the frame — the part a
 * sticky footer covers. Reserving its height under the editor means the whole
 * canvas can always be scrolled clear of the bar, rather than the operator
 * placing the last corner of a polygon under it. */
.geo-tools { margin-bottom: 3.5rem; }

/* Thirteen features is a long page; the save control should not be a scroll
 * away from whatever was just changed. */
.sticky-save {
  position: sticky;
  bottom: 0;
  display: flex;
  align-items: center;
  gap: 0.9rem;
  padding: 0.7rem 1rem;
  background: var(--panel);
  border: 1px solid var(--line);
  border-radius: var(--radius);
  box-shadow: var(--shadow-lift);
}

@media (max-width: 900px) {
  .feat { grid-template-columns: 1fr; gap: 0.7rem; }
}

/* ── annotation queue ───────────────────────────────────────────────────────
 *
 * The video is the thing being judged, so it gets the room. The controls sit
 * beside it in a column narrow enough that it never becomes the taller of the
 * two — the first version left a screen of dead white space under a small video
 * because the button stack was longer than the clip was tall.
 *
 * Control positions never move between clips: the keyboard shortcuts only pay
 * off if a reviewer's hand does not have to hunt.
 */
.anno {
  display: grid;
  grid-template-columns: minmax(0, 1fr) minmax(280px, 320px);
  gap: 0.9rem;
  align-items: start;
}
.anno-video {
  background: var(--panel);
  border: 1px solid var(--line);
  border-radius: var(--radius);
  overflow: hidden;
}
.anno-head {
  display: flex;
  justify-content: flex-start;
  align-items: center;
  gap: 0.55rem 0.8rem;
  padding: 0.5rem 0.8rem;
  border-bottom: 1px solid var(--line-soft);
  flex-wrap: wrap;
}
.anno-head strong { font-size: 0.98rem; }
.anno-head #a-meta {
  flex: 1 1 16rem;
  min-width: 0;
  color: var(--text-2);
  font-size: 0.78rem;
  line-height: 1.35;
}
.anno-status {
  margin-left: auto;
  font-size: 0.68rem;
  font-weight: 600;
  letter-spacing: 0.03em;
  text-transform: uppercase;
  padding: 0.15rem 0.45rem;
  border-radius: 999px;
  border: 1px solid var(--line);
  white-space: nowrap;
}
.anno-status.is-open { color: var(--bad, #b42318); border-color: currentColor; }
.anno-status.is-reviewed { color: var(--ok, #1f7a4d); border-color: currentColor; }
.anno-stage { background: #0d1116; display: flex; justify-content: center; }
/* Tall by default: a reviewer judging footage wants pixels, and the controls
 * beside it are deliberately short enough not to force this smaller. */
.anno-stage video { display: block; width: 100%; max-height: 74vh; }
.anno-noclip {
  padding: 4rem 1.5rem;
  text-align: center;
  color: #c3ccd8;
  font-size: 0.9rem;
  line-height: 1.6;
  max-width: 46rem;
}
.anno-detail {
  padding: 0.5rem 0.8rem;
  border-top: 1px solid var(--line-soft);
  color: var(--text-2);
  font-size: 0.76rem;
  word-break: break-word;
}

.anno-side { display: flex; flex-direction: column; gap: 0.55rem; }
.anno-block {
  background: var(--panel);
  border: 1px solid var(--line);
  border-radius: var(--radius);
  padding: 0.6rem;
}
.anno-label {
  font-size: 0.68rem;
  text-transform: uppercase;
  letter-spacing: 0.04em;
  color: var(--muted);
  margin-bottom: 0.4rem;
}
.btn.wide { width: 100%; justify-content: center; margin-bottom: 0.3rem; }
.btn.wide:last-child { margin-bottom: 0; }
.btn em, .anno-labels em {
  font-style: normal;
  opacity: 0.6;
  font-family: var(--mono);
  font-size: 0.68rem;
  margin-left: 0.2rem;
}
/* Two columns, compact rows. Eleven labels in one column would make this panel
 * taller than the video and reintroduce the dead space. */
.anno-labels { display: grid; grid-template-columns: 1fr 1fr; gap: 0.35rem; }
.anno-labels .btn {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 0.4rem;
  width: 100%;
  min-height: 2.15rem;
  padding: 0.35rem 0.5rem;
  font-size: 0.74rem;
  line-height: 1.25;
  text-align: left;
  white-space: normal;
}
/* Reasons are longer phrases. Two columns crushed them into the video. */
#a-reasons { grid-template-columns: 1fr; }
#a-reasons .btn { min-height: 2.3rem; }
.anno-side textarea {
  width: 100%;
  font-family: inherit;
  font-size: 0.78rem;
  padding: 0.35rem;
  border: 1px solid var(--line);
  border-radius: 4px;
  resize: vertical;
}
.anno-keys {
  margin: 0.4rem 0 0;
  font-size: 0.7rem;
  line-height: 1.5;
  color: var(--muted);
}

@media (max-width: 1000px) {
  .anno { grid-template-columns: 1fr; }
  .anno-stage video { max-height: 52vh; }
  .anno-labels { grid-template-columns: repeat(3, 1fr); }
  #a-reasons { grid-template-columns: 1fr; }
}

/* What the detector measured, under the clip.
 *
 * A strip rather than a table: a reviewer scans it once per clip and never
 * reads it top to bottom, so density matters more than alignment. Positions
 * stay put between clips for the same reason the buttons do. */
.anno-facts {
  display: flex;
  flex-wrap: wrap;
  gap: 0.35rem 1.1rem;
  padding: 0.5rem 0.8rem;
  border-top: 1px solid var(--line-soft);
  background: var(--panel-2);
  font-size: 0.76rem;
}
.anno-fact { display: flex; flex-direction: column; line-height: 1.35; }
.anno-fact em {
  font-style: normal;
  color: var(--muted);
  font-size: 0.68rem;
  text-transform: uppercase;
  letter-spacing: 0.03em;
}
.anno-fact strong { font-family: var(--mono); font-size: 0.86rem; color: var(--text); }
.anno-facts .muted { padding: 0.1rem 0; }

/* The enforced polygon and the frame's detections, drawn from stored
 * coordinates rather than read off the video — so both are visible even when
 * the clip is missing. Taller than it was: it now carries boxes as well as the
 * zone, and at 84px two people standing side by side were a pair of slivers. */
.anno-shape {
  display: block;
  width: 100%;
  height: 132px;
  background: var(--panel-2);
  border: 1px solid var(--line-soft);
  border-radius: 4px;
  color: var(--muted);
}

/* One line per object the detector found in the incident's own frame. The
 * labels are here rather than inside the SVG because that viewBox is stretched
 * with preserveAspectRatio="none", which distorts glyphs into mush — and a
 * list is what a screen reader can actually read out. */
.anno-dets {
  list-style: none;
  margin: 0.35rem 0 0;
  padding: 0;
  display: flex;
  flex-wrap: wrap;
  gap: 0.25rem 0.4rem;
}

.anno-det {
  font-family: var(--mono);
  font-size: 0.78rem;
  color: var(--text);
  background: var(--panel-2);
  border: 1px solid #49aa19;
  border-radius: 3px;
  padding: 0.05rem 0.35rem;
  white-space: nowrap;
}

/* The subject the alert was actually measured on, in the same colour as its
 * box in the overlay. Without this a reviewer looking at three people has no
 * way to tell which one the dwell time belongs to. */
.anno-det.is-alert {
  border-color: #d89614;
  color: #d89614;
}

.anno-det.muted {
  border-color: var(--line-soft);
  color: var(--muted);
  white-space: normal;
}

/* ── occupancy over time ────────────────────────────────────────────────────
 * Mean and peak on one chart, with the band between them shaded, because the
 * gap is what an operator reads: a mean of 3 under a peak of 40 is a different
 * place from a mean of 3 under a peak of 4. */
.occ-stats { display: flex; flex-wrap: wrap; gap: 0.5rem 1.6rem; margin-bottom: 0.9rem; }
.occ-stat { display: flex; flex-direction: column; line-height: 1.3; }
.occ-stat em {
  font-style: normal; color: var(--muted); font-size: 0.68rem;
  text-transform: uppercase; letter-spacing: 0.03em;
}
.occ-stat strong { font-family: var(--mono); font-size: 1.15rem; color: var(--text); }
.occ-chart {
  display: block; width: 100%; height: 220px;
  color: var(--text-2); background: var(--panel-2);
  border: 1px solid var(--line-soft); border-radius: var(--radius);
}
.occ-legend {
  display: flex; gap: 1.1rem; align-items: center; flex-wrap: wrap;
  margin-top: 0.6rem; font-size: 0.76rem; color: var(--text-2);
}
.occ-swatch {
  display: inline-block; width: 0.7rem; height: 0.7rem; border-radius: 2px;
  margin-right: 0.35rem; vertical-align: -1px;
}
.occ-mean { background: var(--ok); }
.occ-peak { background: var(--accent); opacity: 0.55; }

/* ── analytics day view ─────────────────────────────────────────────────── */
.an-kpis {
  display: grid;
  grid-template-columns: repeat(auto-fill, minmax(8.5rem, 1fr));
  gap: 0.65rem;
  margin-bottom: 1rem;
}
.an-kpi {
  background: var(--panel);
  border: 1px solid var(--line-soft);
  border-radius: var(--radius);
  padding: 0.7rem 0.85rem;
  display: flex;
  flex-direction: column;
  gap: 0.15rem;
  min-width: 0;
}
.an-kpi em {
  font-style: normal;
  font-size: 0.64rem;
  text-transform: uppercase;
  letter-spacing: 0.04em;
  color: var(--muted);
}
.an-kpi strong {
  font-family: var(--mono);
  font-size: 1.25rem;
  color: var(--text);
  line-height: 1.2;
}
.an-kpi .muted { font-size: 0.72rem; }
.an-grid {
  display: grid;
  grid-template-columns: 1.4fr 1fr;
  gap: 1rem;
  align-items: start;
}
@media (max-width: 1000px) {
  .an-grid { grid-template-columns: 1fr; }
}
.an-chart {
  display: block; width: 100%; height: 200px;
  color: var(--text-2); background: var(--panel-2);
  border: 1px solid var(--line-soft); border-radius: var(--radius);
}
.an-chart-sm { height: 160px; }
.an-mix { display: flex; flex-direction: column; gap: 0.55rem; }
.an-mix-row {
  display: grid;
  grid-template-columns: 5rem 1fr auto;
  gap: 0.55rem;
  align-items: center;
  font-size: 0.82rem;
}
.an-mix-track {
  height: 0.55rem;
  background: var(--panel-2);
  border: 1px solid var(--line-soft);
  border-radius: 999px;
  overflow: hidden;
}
.an-mix-track i {
  display: block; height: 100%;
  border-radius: inherit;
}
.an-mix-n em { font-style: normal; color: var(--muted); font-weight: 400; }
.an-labels {
  display: flex; flex-wrap: wrap; gap: 0.45rem 0.9rem;
  margin-top: 0.9rem; padding-top: 0.7rem;
  border-top: 1px solid var(--line-soft);
  font-size: 0.78rem;
}
.an-alert-list {
  list-style: none; margin: 0; padding: 0;
  display: flex; flex-direction: column; gap: 0.55rem;
}
.an-alert-list li {
  display: grid;
  grid-template-columns: auto 2.5rem 1fr;
  gap: 0.55rem;
  align-items: center;
  font-size: 0.82rem;
}
.an-alert-list .an-bar {
  height: 0.45rem;
  background: var(--accent);
  opacity: 0.55;
  border-radius: 999px;
  min-width: 4px;
}
.an-hotspot {
  font-size: 0.8rem;
  color: var(--text-2);
}
.an-hotspot strong { color: var(--text); font-family: var(--mono); }

/* ── the shared listing ──────────────────────────────────────────────────
 * Nothing in this console paginated before, which is survivable at eight sites
 * and becomes the page itself at two hundred and fifty. One component so the
 * four new modules read as one product rather than four tables written in four
 * different weeks. */
.lst { display: flex; flex-direction: column; }
.lst-bar {
  display: flex;
  align-items: center;
  gap: 1rem;
  flex-wrap: wrap;
  padding: 0.55rem 1rem;
  border-bottom: 1px solid var(--line);
  background: var(--panel-2);
  font-size: 0.78rem;
  color: var(--text-2);
}
.lst-count { font-variant-numeric: tabular-nums; }
.lst-sizes { margin-left: auto; display: flex; align-items: center; gap: 0.2rem; }
.lst-sizes-k {
  color: var(--muted);
  font-size: 0.7rem;
  text-transform: uppercase;
  letter-spacing: 0.05em;
  margin-right: 0.25rem;
}
.lst-size {
  min-width: 1.9rem;
  text-align: center;
  padding: 0.1rem 0.35rem;
  border: 1px solid var(--line);
  border-radius: 4px;
  background: var(--panel);
  color: var(--text-2);
  font-family: var(--mono);
  font-size: 0.72rem;
  text-decoration: none;
}
.lst-size:hover { border-color: var(--accent); color: var(--accent); }
.lst-size.on { background: var(--accent); border-color: var(--accent); color: #fff; }

.lst-table { width: 100%; border-collapse: collapse; font-size: 0.82rem; }
.lst-table th, .lst-table td {
  text-align: left;
  padding: 0.5rem 0.85rem;
  border-bottom: 1px solid var(--line-soft);
  vertical-align: top;
}
.lst-table thead th {
  position: sticky;
  top: 0;
  z-index: 1;
  /* Opaque: a sticky header over a transparent cell lets the rows scroll
     through it. */
  background: var(--panel-2);
  border-bottom: 1px solid var(--line);
  font-size: 0.66rem;
  font-weight: 600;
  text-transform: uppercase;
  letter-spacing: 0.07em;
  color: var(--muted);
  white-space: nowrap;
}
.lst-table th a { color: inherit; text-decoration: none; }
.lst-table th.sortable a:hover { color: var(--text); }
.lst-table th.sortable a::after {
  content: "";
  display: inline-block;
  width: 0;
  height: 0;
  margin-left: 0.3rem;
  vertical-align: 1px;
  border-left: 3.5px solid transparent;
  border-right: 3.5px solid transparent;
  /* Reserved but transparent, so applying a sort does not change the header's
     width and shift every column. */
  border-top: 4px solid transparent;
}
.lst-table th.on a { color: var(--accent); }
.lst-table th.on.asc a::after { border-bottom: 4px solid var(--accent); border-top: 0; }
.lst-table th.on.desc a::after { border-top: 4px solid var(--accent); }
/* Numbers are compared down a column, so they align on the digit. */
.lst-table td.num, .lst-table th.num {
  text-align: right;
  font-family: var(--mono);
  font-variant-numeric: tabular-nums;
}
.lst-table td.mono { font-family: var(--mono); font-size: 0.76rem; }
.lst-table tbody tr:hover { background: #f7fafd; }
.lst-click { cursor: pointer; }
.lst-empty { padding: 2.2rem 1rem; text-align: center; color: var(--muted); }

.lst-foot {
  display: flex;
  align-items: center;
  gap: 0.25rem;
  padding: 0.5rem 1rem;
  border-top: 1px solid var(--line);
  background: var(--panel-2);
}
.lst-pg {
  min-width: 1.8rem;
  height: 1.8rem;
  display: inline-flex;
  align-items: center;
  justify-content: center;
  border: 1px solid var(--line);
  border-radius: 4px;
  background: var(--panel);
  color: var(--text-2);
  font-size: 0.76rem;
  text-decoration: none;
}
.lst-pg:hover { border-color: var(--accent); color: var(--accent); }
.lst-pg.on { background: var(--accent); border-color: var(--accent); color: #fff; }
/* Disabled rather than removed: an arrow that disappears at the ends makes the
   row jump, and the jump is more disorienting than a greyed control. */
.lst-pg.off { opacity: 0.35; pointer-events: none; }
.lst-gap { padding: 0 0.2rem; color: var(--muted); }

/* ── module chrome shared by the four new pages ─────────────────────────── */
.perspectives {
  display: flex;
  gap: 2px;
  padding: 3px;
  background: var(--panel-2);
  border: 1px solid var(--line);
  border-radius: 6px;
  width: max-content;
}
.perspectives a {
  padding: 0.3rem 0.75rem;
  border-radius: 4px;
  font-size: 0.8rem;
  font-weight: 500;
  color: var(--text-2);
  text-decoration: none;
}
.perspectives a:hover { background: var(--panel); color: var(--text); }
.perspectives a.on { background: var(--accent); color: #fff; font-weight: 550; }

/* Fleet totals. The same numbers whichever perspective is showing, so they read
 * as facts about the fleet rather than about the table. */
/* Headline numbers, on nineteen pages.
 *
 * They were one strip inside a panel, which reads as a single object — and
 * the numbers in it are not one object, they are seven independent facts.
 * Separate cards say that, and match the operator console this mirrors.
 *
 * The panel wrapping them is neutralised rather than removed from the
 * templates: nineteen pages build this markup, and a CSS change reaches all
 * of them at once without nineteen chances to get one wrong.
 */
.panel:has(> .fleet:only-child) {
  background: none;
  border: 0;
  box-shadow: none;
  padding: 0;
}
.fleet em {
  font-style: normal;
  font-size: 0.74rem;
  text-transform: none;
  letter-spacing: 0;
  color: var(--muted);
  font-weight: 500;
}
.fleet strong {
  font-family: var(--sans);
  font-size: 1.7rem;
  font-weight: 600;
  letter-spacing: -0.02em;
  line-height: 1.1;
  color: var(--text);
}
.fleet .warn strong { color: var(--warn); }
.fleet .bad strong { color: var(--bad); }

.filters {
  display: flex;
  gap: 0.5rem;
  flex-wrap: wrap;
  align-items: end;
  padding: 0.7rem 1rem;
  border-bottom: 1px solid var(--line);
  background: var(--panel-2);
}
.filters input, .filters select { width: auto; min-width: 8.5rem; }
.filters input[type="search"] { min-width: 15rem; }
.filters .grow { flex: 1; }

/* State and verdict words. Colour carries the meaning so a queue can be read
 * down the column without reading each row. */
.state {
  display: inline-block;
  font-size: 0.7rem;
  font-weight: 600;
  padding: 0.1rem 0.45rem;
  border-radius: 4px;
  white-space: nowrap;
  background: var(--panel-2);
  border: 1px solid var(--line);
  color: var(--text-2);
}
.state.pending, .state.submitted, .state.requested { background: var(--warn-weak); border-color: #eddcb8; color: var(--warn); }
.state.approved, .state.ready, .state.verified { background: var(--ok-weak); border-color: #bfe3cd; color: var(--ok); }
.state.rejected, .state.failed, .state.incompatible { background: var(--bad-weak); border-color: #f0c4c0; color: var(--bad); }
.state.executing, .state.collecting, .state.in_test { background: var(--accent-weak); border-color: #c5dcfb; color: var(--accent); }
.state.lapsed, .state.expired, .state.cancelled, .state.untested { color: var(--muted); }
.state.caveats { background: var(--warn-weak); border-color: #eddcb8; color: var(--warn); }

.risk-high { color: var(--bad); font-weight: 650; }
.risk-medium { color: var(--warn); font-weight: 600; }
.risk-low { color: var(--muted); }

/* Approval trail. Read top to bottom as a story, which is what an audit is. */
.trail { list-style: none; margin: 0; padding: 0.4rem 0 0; }
.trail li {
  position: relative;
  padding: 0 0 0.9rem 1.4rem;
  border-left: 2px solid var(--line);
  margin-left: 0.4rem;
}
.trail li::before {
  content: "";
  position: absolute;
  left: -0.36rem;
  top: 0.3rem;
  width: 0.6rem;
  height: 0.6rem;
  border-radius: 50%;
  background: var(--line);
  border: 2px solid var(--panel);
}
.trail li.approved::before { background: var(--ok); }
.trail li.rejected::before, .trail li.failed::before { background: var(--bad); }
.trail li.executed::before { background: var(--accent); }
.trail li:last-child { border-left-color: transparent; padding-bottom: 0.2rem; }
.trail .who { font-size: 0.82rem; color: var(--text); font-weight: 550; }
.trail .when { font-family: var(--mono); font-size: 0.7rem; color: var(--muted); }
.trail .said { font-size: 0.82rem; color: var(--text-2); margin-top: 0.15rem; }

/* Readiness checklist. Each line carries why it is asked — the reasoning is what
 * makes the register useful to someone who was not there when the lesson was
 * learned. */
.check { list-style: none; margin: 0; padding: 0; }
.check li {
  display: grid;
  grid-template-columns: 1.6rem 1fr;
  gap: 0.6rem;
  padding: 0.6rem 0;
  border-bottom: 1px solid var(--line-soft);
}
.check li:last-child { border-bottom: 0; }
.check .mark { font-weight: 700; text-align: center; }
.check .yes { color: var(--ok); }
.check .no { color: var(--bad); }
.check .unknown { color: var(--muted); }
.check b { display: block; font-size: 0.86rem; color: var(--text); }
.check span { font-size: 0.79rem; color: var(--text-2); }

/* Site picker. Replaces a <select> that carried one option per site on every
 * page — fine at eight sites, unusable at two hundred and fifty. */
.site-pick {
  width: 100%;
  appearance: none;
  background: rgba(255, 255, 255, 0.05);
  border: 1px solid rgba(255, 255, 255, 0.14);
  color: #eaf0f7;
  padding: 0.45rem 0.55rem;
  border-radius: 4px;
  font-family: var(--mono);
  font-size: 0.76rem;
}
.site-pick::placeholder { color: var(--nav-dim); }
.site-pick:focus { outline: 2px solid var(--accent); outline-offset: 1px; }
.site-switcher-n {
  margin-top: 0.3rem;
  font-size: 0.62rem;
  color: var(--nav-dim);
  letter-spacing: 0.02em;
}
/* On a light filter bar the same control has to invert. */
.filters .site-pick {
  background: var(--panel);
  border-color: #ccd4dd;
  color: var(--text);
  width: auto;
  min-width: 12rem;
}
.filters .site-pick::placeholder { color: #9aa6b4; }
.form .site-pick { background: var(--panel); border-color: #ccd4dd; color: var(--text); }

/* Client detail: sites, each with the cameras it carries. Grouped rather than
 * one flat camera table, because the site is the unit of assignment and the
 * cameras are the consequence. */
.cd-site { border: 1px solid var(--line); border-radius: var(--radius); margin-bottom: 0.9rem; }
.cd-site:last-child { margin-bottom: 0; }
.cd-site-h {
  display: flex;
  align-items: center;
  gap: 0.55rem;
  flex-wrap: wrap;
  padding: 0.55rem 0.8rem;
  background: var(--panel-2);
  border-bottom: 1px solid var(--line);
  border-radius: var(--radius) var(--radius) 0 0;
}
.cd-site-h .btn.small { margin-left: auto; padding: 0.15rem 0.5rem; font-size: 0.72rem; }
.cd-site .lst-table { font-size: 0.78rem; }
.cd-site .lst-table thead th { position: static; }
.move-cam { width: auto; font-size: 0.74rem; padding: 0.1rem 0.3rem; }

/* ── rich cells ──────────────────────────────────────────────────────────
 * The badges below existed from the day these modules landed and the table
 * rendered every cell as bare text, so `Awaiting approval` and `High` arrived as
 * unstyled words. A column now declares its kind and the partial draws it, which
 * is also what keeps the four modules looking like one product. */

/* Colour keys off a slug of the value, not the label — renaming "Awaiting
 * approval" in the UI must not silently turn the badge grey. */
.state { font-variant-numeric: tabular-nums; }
.s-awaiting-approval, .s-submitted, .s-requested, .s-under-test, .s-in-test,
.s-works-with-caveats, .s-trial {
  background: var(--warn-weak); border-color: #eddcb8; color: var(--warn);
}
.s-approved, .s-ready, .s-verified, .s-done, .s-active {
  background: var(--ok-weak); border-color: #bfe3cd; color: var(--ok);
}
.s-rejected, .s-failed, .s-incompatible, .s-suspended {
  background: var(--bad-weak); border-color: #f0c4c0; color: var(--bad);
}
.s-executing, .s-collecting, .s-answered {
  background: var(--accent-weak); border-color: #c5dcfb; color: var(--accent);
}
.s-lapsed, .s-expired, .s-cancelled, .s-not-tested, .s-churned, .s-declined,
.s-prospect { color: var(--muted); }
/* An explicit tone overrides the slug, for values the slug cannot know about —
 * a licence countdown, a coverage percentage. */
.state.t-ok { background: var(--ok-weak); border-color: #bfe3cd; color: var(--ok); }
.state.t-warn { background: var(--warn-weak); border-color: #eddcb8; color: var(--warn); }
.state.t-bad { background: var(--bad-weak); border-color: #f0c4c0; color: var(--bad); }

/* Risk. Weight as well as colour, because severity should survive a greyscale
 * print and a colour-blind reader. */
.risk { font-size: 0.76rem; font-weight: 600; white-space: nowrap; }
.risk::before { content: "●"; margin-right: 0.3rem; font-size: 0.62rem; vertical-align: 1px; }
.r-low { color: var(--muted); font-weight: 500; }
.r-medium { color: var(--warn); }
.r-high { color: var(--bad); font-weight: 700; }

/* A dot plus a word. Scans down a column far faster than the word alone. */
.dotcell { display: inline-flex; align-items: center; gap: 0.4rem; white-space: nowrap; }
.dot { width: 0.5rem; height: 0.5rem; border-radius: 50%; flex: none; background: var(--line); }
.d-ok { background: var(--ok); }
.d-warn { background: var(--warn); }
.d-bad { background: var(--bad); }
.d-muted { background: #c3ccd6; }

/* A fraction and a bar. "1/2 approvals" is a number you read; a half-filled bar
 * is one you scan. */
.meter { display: inline-flex; align-items: center; gap: 0.45rem; }
.meter-bar {
  width: 2.6rem; height: 5px; border-radius: 3px;
  background: var(--line); overflow: hidden; flex: none;
}
.meter-bar i { display: block; height: 100%; background: var(--accent); }
.meter-n { font-family: var(--mono); font-size: 0.72rem; color: var(--text-2); }

/* The row's own verb. A button when there is something to do, quiet text when
 * the row is waiting on something else — so a queue reads at a glance as
 * "these need me, those do not". */
.rowbtn {
  display: inline-block; padding: 0.12rem 0.55rem; border-radius: 4px;
  border: 1px solid var(--accent); background: var(--accent-weak);
  color: var(--accent); font-size: 0.74rem; font-weight: 600; white-space: nowrap;
}
.lst-click:hover .rowbtn { background: var(--accent); color: #fff; }
.rowwait { font-size: 0.74rem; color: var(--muted); }

/* The row's verbs. Buttons and forms sit on one line and do not wrap mid-verb;
 * the cell shrinks to its content so the columns that carry meaning get the
 * width instead. Forms are inline because each verb was its own <form> in the
 * old markup and stacking them turned a 3-verb row three lines tall. */
.rowacts { display: inline-flex; gap: 0.3rem; align-items: center; flex-wrap: nowrap; }
.rowacts form { display: inline; margin: 0; }
.k-links { width: 1%; white-space: nowrap; text-align: right; }
.btn.sm {
  padding: 0.16rem 0.5rem; font-size: 0.73rem; border-radius: 4px; line-height: 1.5;
}

/* The frame a decision was made on. Fixed box so a column of them is a column
 * and not a ragged edge, and `object-fit` so a 16:9 frame is not squashed into
 * whatever aspect the cell happened to be. */
.thumb {
  width: 96px; height: 54px; object-fit: cover; display: block;
  border-radius: 4px; border: 1px solid var(--line); background: var(--panel-2);
}
.k-thumb { width: 1%; }

/* Feature chips. `max-width` with wrapping rather than one long line: a camera
 * with six features enabled was pushing the URL column off the page. */
.k-tags { max-width: 22rem; }
.k-tags .tag { margin: 0.08rem 0.1rem 0.08rem 0; }

/* Two lines in one cell. What turns a ten-column table into a seven-column one
 * without dropping anything: a reference and who raised it belong together. */
.stackcell { display: flex; flex-direction: column; line-height: 1.35; }
.stackcell strong { font-weight: 550; color: var(--text); }
.stackcell em { font-style: normal; font-size: 0.72rem; color: var(--muted); }
.k-stack { min-width: 9rem; }

/* Keyboard access. A <tr> with only a click handler cannot be reached without a
 * mouse — the commonest accessibility hole in a console built out of tables. */
.lst-click:focus-visible {
  outline: 2px solid var(--accent);
  outline-offset: -2px;
}
.lst-click { transition: background 0.08s; }

/* ── page chrome ─────────────────────────────────────────────────────────
 * One toolbar shape for every module: what this page is, then the count, then
 * the primary action on the right. Four pages that each invent their own header
 * read as four products. */
.hrow {
  display: flex;
  align-items: center;
  gap: 0.9rem;
  flex-wrap: wrap;
  margin-bottom: 0.85rem;
}
.hrow h1 { margin: 0; font-size: 1.35rem; font-weight: 620; letter-spacing: -0.01em; }
.hrow .spacer { flex: 1; }
.hrow .subcount {
  font-family: var(--mono);
  font-size: 0.74rem;
  color: var(--muted);
  padding: 0.1rem 0.45rem;
  border: 1px solid var(--line);
  border-radius: 999px;
  background: var(--panel);
}

/* The stat strip. Tightened, and each figure sits on a rule so the row reads as
 * a set of measurements rather than a paragraph of numbers. */
.fleet {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(11rem, 1fr));
  gap: 1rem;
  padding: 0;
}
.fleet > div {
  display: flex;
  flex-direction: column;
  gap: 0.25rem;
  background: var(--panel);
  border: 1px solid var(--line);
  border-radius: var(--radius);
  box-shadow: var(--shadow);
  padding: 1rem 1.1rem;
}
.fleet em { margin-bottom: 0.1rem; }
.fleet strong { font-size: 1.7rem; letter-spacing: -0.02em; line-height: 1.1; }
/* A figure worth acting on gets a hairline, not just a colour — it survives a
 * greyscale screenshot in a ticket. */
.fleet .warn { box-shadow: var(--shadow), inset 3px 0 0 var(--warn); }
.fleet .bad { box-shadow: var(--shadow), inset 3px 0 0 var(--bad); }
.fleet .ok { box-shadow: var(--shadow), inset 3px 0 0 var(--ok); }
.fleet .ok strong { color: var(--ok); }
/* The third line: what the number actually counts. A figure with no unit is a
 * figure two people read differently. */
.fleet span {
  font-size: 0.68rem;
  color: var(--muted);
  margin-top: 0.2rem;
  line-height: 1.35;
}
/* The denominator in "12 / 16" — present, but never competing with the count. */
/* A tile whose value is a phrase rather than a figure — "4 hours ago", an
 * account manager's name. The number type ran to three lines. */
.fleet strong { overflow-wrap: anywhere; }
.fleet .txt strong { font-size: 0.9rem; font-family: var(--sans); letter-spacing: 0; }
.fleet strong .of {
  font-family: var(--mono);
  font-size: 0.8rem;
  font-weight: 400;
  color: var(--muted);
  letter-spacing: 0;
}

/* A panel header carrying a real heading rather than a bare label, so a page of
 * panels has a document outline instead of a wall of same-sized bold text. */
.panel-h h2 {
  font-size: 0.84rem;
  font-weight: 600;
  margin: 0;
  color: var(--text);
}
.panel-h .spacer { flex: 1; }
.panel-h .muted { font-size: 0.72rem; font-weight: 400; color: var(--muted); }

/* The newest alert, in full, at the top of the page it is the reason for.
 * `live` is the unacknowledged case: a left edge in the alert colour, because
 * the difference between "something is happening" and "something happened and
 * was dealt with" is the whole message. */
.panel.flash { border-color: var(--line); }
.panel.flash.live { box-shadow: inset 4px 0 0 var(--bad); border-color: #f0c9c4; }
.flash-k {
  display: flex; align-items: center; gap: 0.55rem;
  padding: 0.6rem 1rem; border-bottom: 1px solid var(--line);
  background: var(--panel-2);
  font-size: 0.7rem; font-weight: 600; text-transform: uppercase;
  letter-spacing: 0.05em; color: var(--muted);
}
.flash-b { padding: 0.85rem 1rem; }
.flash-b h2 { margin: 0 0 0.2rem; font-size: 1.15rem; letter-spacing: -0.01em; }
.flash-b .mono { font-size: 0.76rem; color: var(--text-2); }
.flash-vlm {
  margin: 0.6rem 0 0; padding: 0.55rem 0.7rem;
  background: var(--panel-2); border-radius: 5px;
  border-left: 2px solid var(--accent);
  font-size: 0.82rem; line-height: 1.5;
}
.flash-vlm em {
  display: block; font-style: normal; font-size: 0.64rem;
  text-transform: uppercase; letter-spacing: 0.05em;
  color: var(--muted); margin-bottom: 0.15rem;
}

/* Filter bar: everything on one baseline, including the checkboxes that used to
 * float free of their labels. */
.filters { align-items: center; }
.filters label {
  display: inline-flex;
  align-items: center;
  gap: 0.4rem;
  font-size: 0.8rem;
  color: var(--text-2);
  white-space: nowrap;
  margin: 0;
}
.filters label input[type="checkbox"] { margin: 0; }
.filters .btn { align-self: center; }

/* Inline panels — the "raise a request" / "new client" forms. They slide in
 * under the table rather than navigating, so the list stays as context. */
.form {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(13rem, 1fr));
  gap: 0.7rem 0.9rem;
  padding: 0.9rem 1rem 1rem;
}
.form label { display: flex; flex-direction: column; gap: 0.25rem; font-size: 0.78rem;
              color: var(--text-2); }
.form .span2 { grid-column: 1 / -1; }
/* Buttons on their own row, inside or outside a `.form` grid. It was only
 * defined under `.form`, so the same row of buttons in a `.pad` block lost its
 * rule and the Cancel sat flush against Apply. */
.actions, .form .actions {
  display: flex;
  align-items: center;
  gap: 0.6rem;
  padding-top: 0.3rem;
  border-top: 1px solid var(--line-soft);
  margin-top: 0.2rem;
}

/* An empty state that offers the next action instead of just reporting absence. */
.lst-empty strong { display: block; color: var(--text); font-weight: 550;
                    margin-bottom: 0.25rem; }
.lst-empty a { color: var(--accent); }

/* The tab strip, shared by the client perspectives and the device tabs. */
.perspectives a { transition: background 0.1s, color 0.1s; }
.perspectives a.on { box-shadow: 0 1px 2px rgba(16, 24, 40, 0.15); }

/* The request cell carries the summary, so it needs room to be a sentence
 * rather than three wrapped words. */
.k-stack { min-width: 9rem; max-width: 22rem; }
.stackcell em { white-space: normal; }
/* Keep the filter row on one line until it genuinely cannot be. */
.filters .site-pick { min-width: 10rem; }
.filters input[type="search"] { min-width: 13rem; }

/* A checkbox and its explanation, inside a form grid. The label is the row, the
 * caption sits under the text rather than beside it — a parenthetical that long
 * on one line pushes the control out of reach of its own words. */
.form label.rowcheck {
  flex-direction: row;
  align-items: flex-start;
  gap: 0.5rem;
}
.form label.rowcheck input { margin-top: 0.2rem; flex: none; }
.form label.rowcheck span { display: flex; flex-direction: column; gap: 0.15rem;
                            color: var(--text); font-size: 0.82rem; }
.form label.rowcheck em { font-style: normal; font-size: 0.74rem; color: var(--muted); }

.muted-cell { color: var(--muted); }

/* A long value that must not set the table's width. An RTSP URL with a path and
 * a query is wider than the rest of the row put together, and a table that
 * scrolls sideways because of one column is a table nobody sorts. The full
 * value is on the cell's title. */
.trunc { max-width: 16rem; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }

/* A value that must not be broken across lines. Timestamps mostly: a wrapped
 * `2026-08-26 17:38` reads as two different facts. */
.nowrap { white-space: nowrap; }
.k-thumb .muted-cell { white-space: nowrap; font-size: 0.7rem; }
/* An action that is only a status ("waiting for the edge") sits right-aligned
 * with the buttons it shares a column with, so the column has one edge. */
.k-action { text-align: right; white-space: nowrap; }


/* ── assigning cameras to a profile ──────────────────────────────────────── */
/* The profile picker and the select-all shortcuts share one baseline. On a site
 * with sixteen cameras ticking them individually is fine; "only unassigned" is
 * for the case this panel exists for. */
.assign-h {
  display: flex; align-items: end; gap: 0.7rem; flex-wrap: wrap;
  padding-bottom: 0.7rem; margin-bottom: 0.7rem;
  border-bottom: 1px solid var(--line-soft);
}
.assign-h .spacer { flex: 1; }
.assign-h label { display: flex; flex-direction: column; gap: 0.25rem;
                  font-size: 0.78rem; color: var(--text-2); }
/* A camera following no profile is the one you are looking for in this list. */
.cam-pick label.free { border-color: var(--warn); background: var(--warn-weak); }


/* ── saved views ──────────────────────────────────────────────────────────── */
/* A filter with a name. These were bare links in a mono paragraph under the
 * filter bar, indistinguishable from body text, so nobody clicked them. */
.saved {
  display: flex; align-items: center; gap: 0.4rem; flex-wrap: wrap;
  padding: 0.55rem 1rem; border-bottom: 1px solid var(--line);
  background: var(--panel);
}
.saved .spacer { flex: 1; }
.saved-k {
  font-size: 0.66rem; text-transform: uppercase; letter-spacing: 0.05em;
  color: var(--muted); margin-right: 0.15rem;
}
.chip {
  display: inline-block; padding: 0.15rem 0.55rem;
  border: 1px solid var(--line); border-radius: 999px;
  background: var(--panel-2); color: var(--text-2);
  font-size: 0.74rem; text-decoration: none; white-space: nowrap;
}
.chip:hover { border-color: var(--accent); color: var(--accent); background: var(--accent-weak); }
.chip.on { border-color: var(--accent); color: #fff; background: var(--accent); }
.saved-new { display: flex; gap: 0.35rem; align-items: center; margin: 0; }
.saved-new input { font-size: 0.76rem; padding: 0.2rem 0.45rem; }

/* A count inside a segmented tab. The tab says where you would go; the count
 * says whether it is worth going. */
.seg b {
  font-weight: 600; font-family: var(--mono); font-size: 0.72rem;
  opacity: 0.75; margin-left: 0.1rem;
}
.seg a.on b { opacity: 1; }

/* A cell of prose — a model's verdict, a reason. Wraps rather than forcing the
 * table wider, and is capped so one verbose row cannot squeeze every other
 * column into nothing. */
/* A cell of prose — a model's verdict, a reason. `.wrap` is the class a column
 * asks for via `css=`; `.k-wrap` would be a *kind*, and these columns are plain
 * text that merely needs to wrap. Styling only `.k-wrap` matches nothing here,
 * which is a rule that looks applied and is not. */
.wrap, .k-wrap {
  white-space: normal; line-height: 1.4;
  font-size: 0.8rem; color: var(--text-2);
  /* A floor as well as a ceiling. With only `max-width`, the auto table layout
   * squeezed this to about ten characters, so a one-line sentence wrapped over
   * four lines and every row grew with it. */
  min-width: 13rem; max-width: 22rem;
}
/* Not clipped to a fixed height instead: a <td> ignores `max-height` (height on
 * a table cell acts as a minimum), and `-webkit-line-clamp` needs
 * `display:-webkit-box`, which replaces `table-cell` and drops the column out of
 * table layout. Both fail silently. Widening is also the better answer — this
 * text is what an annotator is reading, so hiding half of it behind a hover to
 * save two lines of height trades the point of the column for its cost. */


/* ── provisioning ────────────────────────────────────────────────────────── */
/* Two panels side by side, one column once there is no room. It was an inline
 * `grid-template-columns:1fr 1fr` that never collapsed, so on a laptop the
 * payload block was about forty characters wide. */
.cols2 { display: grid; grid-template-columns: 1fr 1fr; gap: 1rem; align-items: start; }
@media (max-width: 1000px) { .cols2 { grid-template-columns: 1fr; } }

.note.bad { border-left-color: var(--bad); background: var(--bad-weak); }

/* A radio list where each option has a title and a sentence explaining it.
 * Pushing the wrong day-1 use case to a site is a real mistake, so the
 * description is part of the choice rather than a tooltip. */
.pick-k {
  font-size: 0.66rem; text-transform: uppercase; letter-spacing: 0.05em;
  color: var(--muted); margin: 0.9rem 0 0.35rem;
}
.pick-k:first-child { margin-top: 0; }
.pick {
  display: flex; gap: 0.55rem; align-items: flex-start; cursor: pointer;
  padding: 0.5rem 0.6rem; border: 1px solid var(--line); border-radius: 5px;
  margin-bottom: 0.35rem; background: var(--panel);
}
.pick:hover { border-color: var(--accent); background: var(--accent-weak); }
.pick:has(input:checked) { border-color: var(--accent); background: var(--accent-weak); }
.pick input { margin-top: 0.15rem; }
.pick strong { display: block; font-size: 0.82rem; font-weight: 550; }
.pick em {
  display: block; font-style: normal; font-size: 0.74rem;
  color: var(--muted); line-height: 1.4; margin-top: 0.1rem;
}

/* Label-and-value rows. Was a `.mono` div per fact, which read as log output
 * rather than as a description of the site's current state. */
.kv { display: grid; gap: 0.3rem; margin-bottom: 0.8rem; }
.kv > div { display: flex; gap: 0.5rem; font-size: 0.8rem; }
.kv em {
  font-style: normal; color: var(--muted); min-width: 8rem;
  font-size: 0.72rem; text-transform: uppercase; letter-spacing: 0.04em;
  padding-top: 0.08rem;
}
.kv span { color: var(--text); font-family: var(--mono); font-size: 0.78rem; }

/* A block of JSON or shell. Themed rather than the hardcoded `#0e1116` that
 * made this a black rectangle in the light theme. */
.code {
  margin: 0; padding: 0.7rem 0.8rem; max-height: 15rem; overflow: auto;
  background: var(--panel-2); border: 1px solid var(--line); border-radius: 5px;
  font-family: var(--mono); font-size: 0.74rem; line-height: 1.5;
  color: var(--text-2); white-space: pre-wrap;
}

/* Reference material behind a disclosure. It is worth having on the page and is
 * not worth a third of the page's height every visit. */
.discl > summary {
  padding: 0.7rem 1rem; cursor: pointer; font-size: 0.82rem; font-weight: 600;
  background: var(--panel-2); border-radius: var(--radius);
}
.discl[open] > summary { border-bottom: 1px solid var(--line); border-radius: var(--radius) var(--radius) 0 0; }
.discl > .code { margin: 1rem; max-height: none; }


/* ── dwell heatmap ───────────────────────────────────────────────────────── */
/* Eight by eight over the frame. The intensity arrives as a custom property so
 * the colour comes from the palette rather than from a hardcoded rgba() that
 * belonged to a theme this console no longer uses. */
.heat {
  display: grid; grid-template-columns: repeat(8, 1fr); gap: 2px; padding: 1rem;
}
.heat i {
  aspect-ratio: 1; border-radius: 2px;
  background: color-mix(in srgb, var(--accent) calc(var(--i) * 1%), var(--panel-2));
  border: 1px solid var(--line-soft);
}
/* A scale, because "darker means more" is not a reading. */
.heat-key {
  display: flex; align-items: center; gap: 0.45rem; flex-wrap: wrap;
  padding: 0 1rem 1rem; font-family: var(--mono); font-size: 0.7rem;
  color: var(--muted);
}
.heat-ramp {
  flex: 1; min-width: 5rem; height: 0.5rem; border-radius: 999px;
  border: 1px solid var(--line-soft);
  background: linear-gradient(to right, var(--panel-2), var(--accent));
}
.heat-key em { font-style: normal; width: 100%; }


/* ── detail pages ────────────────────────────────────────────────────────── */
/* A quoted passage of someone's — or something's — words. The model's verdict
 * and an operator's memo are visibly different objects here, which is the point
 * of keeping them in different fields (AON-365): a page that showed them as two
 * identical paragraphs undid that distinction at the last step. */
.quote {
  margin: 0 0 0.7rem; padding: 0.6rem 0.75rem;
  background: var(--panel-2); border-radius: 5px;
  border-left: 2px solid var(--line);
  font-size: 0.84rem; line-height: 1.55; color: var(--text);
}
.quote em {
  display: block; font-style: normal; font-size: 0.64rem;
  text-transform: uppercase; letter-spacing: 0.05em;
  color: var(--muted); margin-bottom: 0.2rem;
}
.quote.vlm { border-left-color: var(--accent); }
.quote.memo { border-left-color: var(--ok); }

/* A small heading inside a panel body. `<h3>` inherited the document scale and
 * came out bigger than the panel's own title. */
.minih {
  font-size: 0.7rem; font-weight: 600; text-transform: uppercase;
  letter-spacing: 0.05em; color: var(--muted);
  margin: 1.1rem 0 0.4rem;
}
.minih:first-child { margin-top: 0; }

/* The frame a decision was made on, full width of its panel. */
.frame { display: block; width: 100%; border-bottom: 1px solid var(--line); }

/* The site switcher in the provisioning header. Without a rule the input and its
 * button stacked, and a two-line control in a header row pushed the title down
 * with it. */
.site-switch { display: flex; gap: 0.35rem; align-items: center; margin: 0; }
.site-switch input { width: 11rem; font-size: 0.78rem; padding: 0.25rem 0.5rem; }

/* The rail saying a typed site does not exist. Amber rather than red: nothing
 * broke and nothing was lost — the selection simply did not change. */
.site-miss { color: var(--warn); }

/* Inline verdicts in a form's helper line — whether a camera can replay. Colour
 * carries the answer, because this is the line that decides whether raising the
 * request is worth doing at all. */
.ok-text { color: var(--ok) !important; }
.warn-text { color: var(--warn) !important; }
.bad-text { color: var(--bad) !important; }


/* ── upgrade step timeline ───────────────────────────────────────────────── */
/* An upgrade that reports only pass/fail turns into an SSH session. So each step
 * is a row on a spine: state in the mark, what it is for underneath, and what it
 * measured below that. */
.steps { list-style: none; margin: 0; padding: 0.4rem 0; }
.step {
  position: relative; display: flex; gap: 0.8rem;
  padding: 0.7rem 1rem 0.7rem 0.6rem;
}
/* The spine. Drawn on the item rather than the list so it stops at the last
 * step instead of trailing into the panel's padding. */
.step::before {
  content: ""; position: absolute; left: 1.32rem; top: 0; bottom: 0;
  width: 1px; background: var(--line);
}
.step:first-child::before { top: 1.35rem; }
.step:last-child::before { bottom: calc(100% - 1.35rem); }
.step-mark {
  position: relative; z-index: 1; flex: none;
  width: 0.75rem; height: 0.75rem; margin: 0.5rem 0 0 0.95rem;
  border-radius: 999px; background: var(--panel);
  border: 2px solid var(--line);
}
.step.s-done .step-mark { background: var(--ok); border-color: var(--ok); }
.step.s-failed .step-mark { background: var(--bad); border-color: var(--bad); }
.step.s-claimed .step-mark {
  background: var(--warn); border-color: var(--warn);
  /* Something is happening right now. The only animation in the console, because
   * this is the one place a reader genuinely needs to know the difference between
   * "in progress" and "stuck". */
  animation: step-pulse 1.4s ease-in-out infinite;
}
@keyframes step-pulse { 50% { opacity: 0.35; } }
.step.s-skipped .step-mark { border-style: dotted; }
.step.s-skipped .step-body { opacity: 0.55; }

.step-body { flex: 1; min-width: 0; }
.step-h { display: flex; align-items: center; gap: 0.45rem; flex-wrap: wrap; }
.step-h .spacer { flex: 1; }
.step-h strong { font-size: 0.88rem; font-weight: 600; }
.step-what {
  margin: 0.25rem 0 0; font-size: 0.79rem; line-height: 1.5; color: var(--muted);
  max-width: 62ch;
}
.step-detail {
  margin: 0.4rem 0 0; font-family: var(--mono); font-size: 0.76rem;
  color: var(--text-2);
}
.step-error {
  margin: 0.4rem 0 0; padding: 0.45rem 0.6rem;
  background: var(--bad-weak); border-left: 2px solid var(--bad); border-radius: 4px;
  font-family: var(--mono); font-size: 0.76rem; color: var(--text);
}
/* What the step measured — free disk, module states. The difference between
 * "preflight failed" and "preflight failed: 1.2 GB free, needs 4". */
.step-facts {
  display: flex; flex-wrap: wrap; gap: 0.3rem 1rem; margin-top: 0.45rem;
  font-family: var(--mono); font-size: 0.72rem; color: var(--text-2);
}
.step-facts em {
  font-style: normal; color: var(--muted); margin-right: 0.3rem;
}
.module-health-wrap { margin-top: 0.55rem; overflow-x: auto; }
.module-health-table {
  width: 100%; border-collapse: collapse; font-size: 0.78rem;
}
.module-health-table th {
  text-align: left; color: var(--muted); font-weight: 500;
  padding: 0.35rem 0.5rem; border-bottom: 1px solid var(--line);
}
.module-health-table td {
  padding: 0.35rem 0.5rem; border-bottom: 1px solid var(--line-weak, var(--line));
  vertical-align: top;
}
.module-health-table td .mono { font-size: 0.68rem; display: block; margin-top: 0.1rem; }
/* The cutover, and the rollback steps after it. The line between "abandon" and
 * "roll back" is the single most important thing on this page. */
.step.cutover { background: var(--warn-weak); }
.step.cutover .step-h strong { color: var(--text); }
.step.back { background: var(--panel-2); }


/* ── the role matrix ─────────────────────────────────────────────────────── */
/* Nine capability columns, so they are narrow and centred and the role column
 * carries the width. */
.capcol { text-align: center; width: 1%; white-space: nowrap; padding-left: 0.5rem;
          padding-right: 0.5rem; font-size: 0.72rem; }
th.capcol { color: var(--muted); font-weight: 500; }
.capcol .yes { color: var(--ok); font-weight: 500; }
.capcol .no { color: var(--border-strong, var(--line)); }
/* Legacy roles are still enforced, so they are shown — just not competing with
 * the three somebody should actually pick. */
tr.legacy td { opacity: 0.62; }
tr.legacy .stackcell strong { font-weight: 400; }


/* ── step controls and evidence ──────────────────────────────────────────── */
/* When it ran. A duration alone cannot be lined up against a graph or a ticket. */
.step-when {
  display: flex; flex-wrap: wrap; gap: 0.15rem 1rem; margin-top: 0.2rem;
  font-family: var(--mono); font-size: 0.7rem; color: var(--muted);
}
/* A step somebody passed over — the first thing an investigation needs to see,
 * so it is coloured like a warning and never collapsed. */
.step-skipped {
  margin: 0.4rem 0 0; padding: 0.45rem 0.6rem;
  background: var(--warn-weak); border-left: 2px solid var(--warn);
  border-radius: 4px; font-size: 0.78rem; line-height: 1.45;
}
/* The captured output. Behind a disclosure because it is long and only wanted
 * when something went wrong. */
.step-log { margin-top: 0.45rem; }
.step-log > summary {
  cursor: pointer; font-size: 0.74rem; color: var(--text-accent, var(--accent));
  font-family: var(--mono);
}
.step-log > .code { margin-top: 0.35rem; max-height: 18rem; }
.step-acts { display: flex; align-items: center; gap: 0.4rem; margin-top: 0.5rem; }


/* ── choosing diagnostic checks ──────────────────────────────────────────── */
/* Grouped, because eleven bare checkboxes is a list nobody reads during an
 * incident — which is exactly when this form gets used. */
.checkgrid {
  display: grid; grid-template-columns: repeat(auto-fit, minmax(15rem, 1fr));
  gap: 0.9rem; padding-top: 0.6rem;
}
.checkgroup { display: flex; flex-direction: column; gap: 0.3rem; }
.checkgroup-k {
  font-size: 0.66rem; text-transform: uppercase; letter-spacing: 0.05em;
  color: var(--muted);
}
.checkgroup .check {
  display: flex; gap: 0.45rem; align-items: baseline; cursor: pointer;
  padding: 0.3rem 0.4rem; border: 1px solid var(--line); border-radius: 4px;
  background: var(--panel);
}
.checkgroup .check:hover { border-color: var(--accent); background: var(--accent-weak); }
.checkgroup .check strong { font-size: 0.8rem; font-weight: 500; display: block; }
.checkgroup .check em {
  font-style: normal; font-size: 0.7rem; color: var(--muted);
}
/* An action is not a question. Coloured so nobody ticks one while skim-reading a
 * list of probes. */
.checkgroup .check.act { border-color: var(--warn); background: var(--warn-weak); }
.checkgroup .check.act:hover { border-color: var(--bad); }

/* A disclosure inside a form grid, without the panel chrome. */
.discl-inline > summary {
  cursor: pointer; font-size: 0.78rem; color: var(--text-accent, var(--accent));
  padding: 0.2rem 0;
}

/* Diagnostic check markers.
 *
 * The results list reuses `.step` for its spine and layout, but a check's states
 * are not an upgrade step's: `pass`/`warn`/`fail`/`unknown` rather than
 * `done`/`failed`/`claimed`. Without these rules every check drew the same
 * hollow marker whatever it found, so the shape of a run — two red, two amber,
 * the rest fine — was invisible until you read all eleven badges. */
.step.s-pass .step-mark { background: var(--ok); border-color: var(--ok); }
.step.s-fail .step-mark { background: var(--bad); border-color: var(--bad); }
.step.s-warn .step-mark { background: var(--warn); border-color: var(--warn); }
/* "Could not check" is deliberately not a colour. It is the absence of a
 * finding, and giving it one would put it on the same footing as a verdict. */
.step.s-unknown .step-mark { border-style: dashed; border-color: var(--muted); }
.step.s-pending .step-mark { background: var(--panel); border-color: var(--line); }

/* A step the plan moved past without anybody deciding to — the rollback overtook
 * it, or the plan was abandoned mid-flight. Hollow and dashed like "cannot
 * answer" in diagnostics, for the same reason: it is the absence of an outcome,
 * not an outcome. It must not read as Skipped (a person's decision, with a name
 * on it) and must not read as still running, which is what it did before this
 * state existed. */
.step.s-superseded .step-mark {
  border-style: dashed; border-color: var(--muted); background: var(--panel);
}
.step.s-superseded .step-body { opacity: 0.6; }

/* The "this step has stopped progressing" line. Same red framing as a step
 * error, because it wants the same attention, but in the body font: it is a
 * sentence addressed to a person deciding whether to intervene, not output
 * captured from a box, and `.step-error`'s monospace made prose read like a
 * stack trace. */
.step-stall {
  margin: 0.4rem 0 0; padding: 0.45rem 0.6rem;
  background: var(--bad-weak); border-left: 2px solid var(--bad); border-radius: 4px;
  font-size: 0.8rem; line-height: 1.45; color: var(--text);
}

/* ── page loader (filter submit / nav on heavy insight pages) ─────────────── */
.page-loader {
  position: fixed; inset: 0; z-index: 2000;
  display: flex; align-items: center; justify-content: center;
  background: color-mix(in srgb, var(--bg, #f7f7f5) 55%, transparent);
  backdrop-filter: blur(2px);
}
.page-loader[hidden] { display: none !important; }
.page-loader-card {
  display: flex; align-items: center; gap: 0.75rem;
  padding: 0.85rem 1.15rem;
  background: var(--panel, #fff);
  border: 1px solid var(--line-soft, #e5e5e5);
  border-radius: var(--radius, 6px);
  box-shadow: 0 8px 28px rgba(0,0,0,.08);
  font-size: 0.9rem; color: var(--text);
}
.page-loader-spin {
  width: 1.1rem; height: 1.1rem;
  border: 2px solid var(--line, #ddd);
  border-top-color: var(--accent, #1668dc);
  border-radius: 50%;
  animation: page-spin 0.7s linear infinite;
}
@keyframes page-spin { to { transform: rotate(360deg); } }
body.is-loading { cursor: progress; }
