/* Minimal dashboard styling.
   Goal: legible, dense, easy for Claude Designer to restyle by swapping
   the CSS variables and tightening component rules.
*/

/* ---------------------------------------------------------------------------
 * Design tokens — Phase 1 of the visual refresh.
 *
 * Surfaces / borders are slightly cooler than before; semantic role colours
 * (ok / warn / danger / info / crit) get matching `*-soft` (16% bg) and
 * `*-fg` (text-on-soft) pairs so badges, row tints, and hero tiles all
 * draw from one swatch set instead of ad-hoc rgba() literals.
 *
 * Old token names (--accent-2, --warn, --danger) are kept as aliases so
 * every existing rule in this file and every template class keeps working.
 * ------------------------------------------------------------------------- */
:root {
  /* Surfaces */
  --bg:        #0d1117;
  --bg-2:      #151b23;
  --bg-3:      #1f2731;
  --border:    #2a3340;
  --border-2:  #1d242c;

  /* Text */
  --fg:        #e6edf3;
  --muted:     #8b95a3;

  /* Accent — bumped from #5b8dff to #79b8ff for AA-comfortable
     contrast on body text + table cells. The earlier softer blue
     read fine against large headings but lost legibility on 12.5 px
     hostname / location links. */
  --accent:    #79b8ff;
  --accent-2:  #a8d2ff;            /* hover / lift */
  --accent-soft:   rgba(121,184,255,0.16);
  --accent-fg:     #c8dfff;

  /* Semantic roles */
  --ok:        #3fb950;
  --ok-soft:   rgba(63,185,80,0.16);
  --ok-fg:     #58d36b;
  --warn:      #d4a72c;
  --warn-soft: rgba(212,167,44,0.16);
  --warn-fg:   #e3b94a;
  --danger:    #f85149;
  --danger-soft: rgba(248,81,73,0.16);
  --danger-fg: #ff7b73;
  --info:      #79b8ff;            /* alias of --accent */
  --info-soft: rgba(121,184,255,0.16);
  --info-fg:   #c8dfff;
  --crit:      #7a3030;
  --crit-soft: rgba(122,48,48,0.32);
  --crit-fg:   #f5a8a8;

  /* Backwards-compat aliases — older rules read these names directly. */
  --green-fg:  var(--ok-fg);
  --yellow-fg: var(--warn-fg);
  --red-fg:    var(--danger-fg);

  /* Spacing */
  --sp-1: 4px;
  --sp-2: 8px;
  --sp-3: 12px;
  --sp-4: 16px;
  --sp-5: 24px;
  --sp-6: 32px;
  --gap: var(--sp-3);              /* legacy alias */

  /* Typography */
  --mono: ui-monospace, SFMono-Regular, "SF Mono", Menlo, Consolas, monospace;
  --sans: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, sans-serif;
  --fs-xs:   11.5px;
  --fs-sm:   12.5px;
  --fs-base: 13.5px;
  --fs-md:   15px;
  --fs-lg:   17px;
  --fs-xl:   22px;
  --lh-tight: 1.25;
  --lh-base:  1.5;

  /* Radii / elevation */
  --radius-1: 4px;
  --radius-2: 6px;
  --radius-3: 10px;
  --radius:   var(--radius-2);     /* legacy alias */
  --shadow-1: 0 1px 0 rgba(0,0,0,.25);
  --shadow-2: 0 4px 14px rgba(0,0,0,.35);
}

.theme-light {
  --bg:        #ffffff;
  --bg-2:      #f6f8fa;
  --bg-3:      #eaeef2;
  --border:    #d0d7de;
  --border-2:  #e1e7ec;

  --fg:        #1f2328;
  --muted:     #57606a;

  --accent:    #2f6feb;
  --accent-2:  #1f5dd1;
  --accent-soft:   rgba(47,111,235,0.10);
  --accent-fg:     #1a4fb3;

  --ok:        #1a7f37;
  --ok-soft:   rgba(26,127,55,0.10);
  --ok-fg:     #0f5b27;
  --warn:      #9a6700;
  --warn-soft: rgba(154,103,0,0.10);
  --warn-fg:   #6e4900;
  --danger:    #cf222e;
  --danger-soft: rgba(207,34,46,0.10);
  --danger-fg: #95151f;
  --info:      var(--accent);
  --info-soft: var(--accent-soft);
  --info-fg:   var(--accent-fg);
  --crit:      #6e1f1f;
  --crit-soft: rgba(110,31,31,0.12);
  --crit-fg:   #6e1f1f;

  --green-fg:  var(--ok-fg);
  --yellow-fg: var(--warn-fg);
  --red-fg:    var(--danger-fg);

  --shadow-1: 0 1px 0 rgba(0,0,0,.06);
  --shadow-2: 0 4px 14px rgba(0,0,0,.08);
}

* { box-sizing: border-box; }

body {
  margin: 0;
  font-family: var(--sans);
  font-size: var(--fs-base);
  line-height: var(--lh-base);
  color: var(--fg);
  background: var(--bg);
  -webkit-font-smoothing: antialiased;
  text-rendering: optimizeLegibility;
}

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

code, .json, pre {
  font-family: var(--mono);
  font-size: 12.5px;
}

pre {
  background: var(--bg-2);
  border: 1px solid var(--border);
  padding: 8px 10px;
  border-radius: var(--radius);
  white-space: pre-wrap;
  overflow-x: auto;
}

.copyable { user-select: all; }
pre.copyable {
  position: relative;
  padding-right: 64px; /* leave room for the absolute Copy button */
}
.copy-btn {
  position: absolute;
  top: 6px;
  right: 6px;
  padding: 2px var(--sp-2);
  font-size: var(--fs-xs);
  font-family: inherit;
  color: var(--fg);
  background: var(--bg-2);
  border: 1px solid var(--border);
  border-radius: var(--radius-2);
  cursor: pointer;
  user-select: none;
}
.copy-btn:hover { background: var(--bg-3); color: var(--accent); }
.copy-btn.copy-btn-ok {
  background: var(--accent-soft);
  border-color: var(--accent);
  color: var(--accent);
}

.topbar {
  display: flex; justify-content: space-between; align-items: center;
  padding: var(--sp-2) var(--sp-4);
  background: var(--bg-2);
  border-bottom: 1px solid var(--border);
}
.topbar-left  { display: inline-flex; align-items: center; gap: var(--sp-3); }
.topbar-right { display: inline-flex; align-items: center; gap: var(--sp-2); }

/* Brand: small inline-SVG glyph + lowercase mono wordmark.
   `.brand-dot` picks up the accent so the dot reads as a status pip. */
.brand {
  display: inline-flex; align-items: center; gap: var(--sp-2);
  color: var(--fg);
  text-decoration: none;
  font-family: var(--mono);
  font-size: var(--fs-md);
  font-weight: 600;
  letter-spacing: 0.02em;
}
.brand:hover { text-decoration: none; color: var(--accent); }
.brand-glyph { color: var(--accent); flex: 0 0 auto; }
.brand-mark  { white-space: nowrap; }
.brand-dot   { color: var(--accent); }
.topbar-tz {
  display: inline-block;
  padding: 2px var(--sp-2);
  font-family: var(--mono);
  font-size: var(--fs-xs);
  color: var(--muted);
  background: var(--bg-3);
  border-radius: var(--radius-1);
  letter-spacing: 0.02em;
}
.muted { color: var(--muted); }
.btn-link {
  background: none; border: 0; color: var(--accent); cursor: pointer;
  font: inherit; padding: 0;
}

.layout { display: flex; min-height: calc(100vh - 49px); }
.sidenav {
  width: 220px; background: var(--bg-2); border-right: 1px solid var(--border);
  padding: 12px 0; flex-shrink: 0;
}
.sidenav ul { list-style: none; margin: 0; padding: 0; }
.sidenav li.nav-group {
  font-size: 11px; text-transform: uppercase; letter-spacing: 0.05em;
  color: var(--muted); padding: 12px 16px 4px;
}
.sidenav a {
  display: block; padding: 6px 16px; color: var(--fg);
  border-left: 2px solid transparent;
}
.sidenav a:hover { background: var(--bg-3); text-decoration: none; }
.sidenav a.active {
  background: var(--bg-3);
  border-left-color: var(--accent);
  color: var(--fg);
  font-weight: 500;
}

.content { flex: 1; padding: 24px; min-width: 0; }

.flashes { list-style: none; padding: 0; margin: 0 0 16px; }
.flash {
  padding: 8px 12px; border-radius: var(--radius);
  border: 1px solid var(--border); margin-bottom: 6px;
}
.flash-info    { background: var(--ok-soft);     border-color: var(--ok); }
.flash-warn,
.flash-warning { background: var(--warn-soft);   border-color: var(--warn); }
.flash-error   { background: var(--danger-soft); border-color: var(--danger); }

/* ---------------------------------------------------------------------------
 * Form card — opt-in container for create / edit pages so they read as a
 * single composed sheet instead of bare full-bleed inputs against the
 * page background. Wrap the <form> (or <h1> + <form>) in
 * `<div class="form-card">…</div>`; the rules inside the container
 * stack labels above inputs, normalise input height + colour, and
 * lay row pairs out two-up via `.form-row.form-row-2`.
 *
 * Outside `.form-card` the existing inline-label markup keeps working
 * unchanged — login + MFA + profile pages aren't affected.
 * ------------------------------------------------------------------------- */
.form-card {
  max-width: 720px;
  background: var(--bg-2);
  border: 1px solid var(--border);
  border-radius: var(--radius-3);
  padding: var(--sp-5) var(--sp-5);
  box-shadow: var(--shadow-1);
}
.form-card h1 {
  margin: 0 0 var(--sp-4);
  font-size: var(--fs-xl);
}
.form-card .breadcrumb { margin-top: 0; }

/* Stack label text above the input. Bare text-node children of <label>
   sit on their own line as the label caption; the input/select/textarea
   sits below. */
.form-card form > label,
.form-card form > div > label,
.form-card form fieldset > label {
  display: flex;
  flex-direction: column;
  gap: var(--sp-1);
  margin-bottom: var(--sp-3);
  font-size: var(--fs-xs);
  font-weight: 600;
  color: var(--muted);
  text-transform: uppercase;
  letter-spacing: 0.05em;
}
.form-card form > label > input,
.form-card form > label > select,
.form-card form > label > textarea,
.form-card form > div > label > input,
.form-card form > div > label > select,
.form-card form > div > label > textarea,
.form-card form fieldset > label > input,
.form-card form fieldset > label > select,
.form-card form fieldset > label > textarea,
.form-card form .combobox-wrap {
  width: 100%;
  max-width: 100%;
  font-size: var(--fs-base);
  font-weight: 400;
  color: var(--fg);
  text-transform: none;
  letter-spacing: 0;
  background: var(--bg);
  border: 1px solid var(--border);
  border-radius: var(--radius-2);
  padding: var(--sp-2) var(--sp-3);
}
.form-card form .combobox-wrap { padding: 0; background: transparent; border: 0; }
.form-card form > label > textarea,
.form-card form > div > label > textarea,
.form-card form fieldset > label > textarea {
  min-height: 84px;
  resize: vertical;
  font-family: var(--sans);
  line-height: var(--lh-base);
}
.form-card form > label > input:focus,
.form-card form > label > select:focus,
.form-card form > label > textarea:focus,
.form-card form fieldset > label > input:focus,
.form-card form fieldset > label > select:focus,
.form-card form fieldset > label > textarea:focus {
  border-color: var(--accent);
  box-shadow: 0 0 0 3px var(--accent-soft);
}

/* Two-up row for short paired fields (Name + Slug, Max uses + Expires
   in days, …). Falls back to a single column on narrow viewports. */
.form-card .form-row {
  display: grid;
  gap: var(--sp-3);
  margin-bottom: 0;
}
.form-card .form-row-2 { grid-template-columns: 1fr 1fr; }
.form-card .form-row-3 { grid-template-columns: repeat(3, 1fr); }
.form-card .form-row > label { margin-bottom: var(--sp-3); }
@media (max-width: 720px) {
  .form-card .form-row-2,
  .form-card .form-row-3 { grid-template-columns: 1fr; }
}

/* Checkbox row reads as a horizontal toggle: small checkbox + the
   description text on a single baseline. Override the stacked-label
   rule above. */
.form-card label.checkbox-row {
  display: flex !important;
  flex-direction: row !important;
  align-items: center;
  gap: var(--sp-2);
  font-size: var(--fs-sm);
  font-weight: 500;
  color: var(--fg);
  text-transform: none;
  letter-spacing: 0;
  margin: var(--sp-2) 0 var(--sp-2);
}
.form-card label.checkbox-row > input[type="checkbox"],
.form-card label.checkbox-row > input[type="radio"] {
  width: 16px;
  height: 16px;
  margin: 0;
  flex-shrink: 0;
  accent-color: var(--accent);
}
/* Bare checkbox/radio inputs anywhere in a form-card (e.g. the
   permission matrix grid in groups_form, inline `Send by email` on
   users_form). The wide-input rule above otherwise stretches them
   to 100% and they render as long bars. Force a fixed square. */
.form-card input[type="checkbox"],
.form-card input[type="radio"] {
  width: 16px;
  height: 16px;
  vertical-align: middle;
  accent-color: var(--accent);
}

/* Submit button row — hairline divider + right-aligned actions. */
.form-card .form-actions {
  margin-top: var(--sp-4);
  padding-top: var(--sp-4);
  border-top: 1px solid var(--border);
  display: flex;
  justify-content: flex-end;
  gap: var(--sp-2);
}
.form-card .form-actions.left { justify-content: flex-start; }

/* Inline help text rendered under a label. */
.form-card .form-help {
  margin: calc(-1 * var(--sp-2)) 0 var(--sp-3);
  font-size: var(--fs-xs);
  color: var(--muted);
  text-transform: none;
  letter-spacing: 0;
  font-weight: 400;
}
/* When .form-help sits inside a multi-column .form-row, span the
   whole row so the helper text doesn't get clipped to one column. */
.form-card .form-row > .form-help {
  grid-column: 1 / -1;
  margin-top: 0;
}

/* File inputs — match the visual weight of text inputs / selects. The
   native control has its own internal button; we only style the outer
   chrome (border, padding, background) and let the browser draw the
   button itself. */
.form-card input[type="file"] {
  width: 100%;
  max-width: 100%;
  padding: var(--sp-2);
  font-size: var(--fs-base);
  color: var(--fg);
  background: var(--bg);
  border: 1px solid var(--border);
  border-radius: var(--radius-2);
  cursor: pointer;
}
.form-card input[type="file"]::file-selector-button {
  margin-right: var(--sp-3);
  padding: 4px var(--sp-3);
  font: inherit;
  color: var(--fg);
  background: var(--bg-3);
  border: 1px solid var(--border);
  border-radius: var(--radius-2);
  cursor: pointer;
}
.form-card input[type="file"]::file-selector-button:hover {
  background: var(--bg-2);
  color: var(--accent);
}

/* Destructive-confirm zone — used inside <details> for typed-hostname
   confirmations. Visually subordinate to the parent form-card (no
   nested card chrome) but clearly separated by a red accent rail so
   the operator sees they are in a danger context. */
.confirm-zone {
  margin-top: var(--sp-3);
  padding: var(--sp-3) var(--sp-4);
  border-left: 3px solid var(--danger);
  background: color-mix(in srgb, var(--danger) 6%, transparent);
  border-radius: 0 var(--radius-2) var(--radius-2) 0;
}
.confirm-zone > :first-child { margin-top: 0; }
.confirm-zone > :last-child { margin-bottom: 0; }
.confirm-zone label {
  display: block;
  margin: var(--sp-2) 0;
  font-weight: 500;
}
.confirm-zone input[type="text"] {
  width: 100%;
  max-width: 100%;
  padding: var(--sp-2) var(--sp-3);
  background: var(--bg);
  color: var(--fg);
  border: 1px solid var(--border);
  border-radius: var(--radius-2);
  font: inherit;
  margin-top: 4px;
}

/* Fieldset polish for multi-section forms (agent edit, etc.). */
.form-card fieldset {
  border: 1px solid var(--border-2);
  border-radius: var(--radius-2);
  padding: var(--sp-3) var(--sp-4) var(--sp-1);
  margin: 0 0 var(--sp-4);
}
.form-card fieldset > legend {
  padding: 0 var(--sp-2);
  font-size: var(--fs-xs);
  font-weight: 600;
  color: var(--accent);
  text-transform: uppercase;
  letter-spacing: 0.06em;
}

/* ---------------------------------------------------------------------------
 * Phase 8 — Toast region + empty-state styling.
 *
 * Toast region sits fixed in the bottom-right of the viewport (or
 * bottom-centre on mobile) and renders flash messages as positioned
 * auto-dismissing chips. The static `.flashes` list at the top of the
 * page is the no-JS fallback; main.js moves them into the region.
 * ------------------------------------------------------------------------- */
.toast-region {
  position: fixed;
  right: var(--sp-4);
  bottom: var(--sp-4);
  display: flex; flex-direction: column; gap: var(--sp-2);
  z-index: 200;
  pointer-events: none;
  max-width: min(420px, calc(100vw - var(--sp-4) * 2));
}
.toast {
  pointer-events: auto;
  background: var(--bg-2);
  border: 1px solid var(--border);
  border-radius: var(--radius-3);
  padding: var(--sp-3) var(--sp-4);
  font-size: var(--fs-sm);
  color: var(--fg);
  box-shadow: var(--shadow-2);
  display: flex; align-items: flex-start; gap: var(--sp-3);
  animation: toast-in 0.18s ease-out;
}
.toast-info,
.toast-success { border-left: 3px solid var(--ok); }
.toast-warn,
.toast-warning { border-left: 3px solid var(--warn); }
.toast-error,
.toast-danger  { border-left: 3px solid var(--danger); }
.toast-message { flex: 1 1 auto; line-height: var(--lh-base); }
.toast-close {
  flex: 0 0 auto;
  background: none; border: 0; padding: 0;
  font-size: 16px; line-height: 1; color: var(--muted);
  cursor: pointer;
}
.toast-close:hover { color: var(--fg); }
@keyframes toast-in {
  from { transform: translateY(8px); opacity: 0; }
  to   { transform: translateY(0);   opacity: 1; }
}
@media (max-width: 720px) {
  .toast-region {
    right: var(--sp-2); left: var(--sp-2); bottom: var(--sp-2);
    max-width: none;
  }
}

/* Empty-state card — a soft placeholder when a list view returns no
   rows. Replaces the prior pattern of a single "No agents match"
   <td colspan="N" class="muted"> at the bottom of an empty table. */
.empty-state {
  padding: var(--sp-5) var(--sp-4);
  background: var(--bg-2);
  border: 1px dashed var(--border);
  border-radius: var(--radius-3);
  text-align: center;
  color: var(--muted);
}
.empty-state-icon {
  font-size: 28px; line-height: 1;
  margin-bottom: var(--sp-2);
  color: var(--accent);
  opacity: 0.7;
}
.empty-state-title {
  color: var(--fg);
  font-weight: 600;
  margin-bottom: var(--sp-1);
}
.empty-state-action {
  margin-top: var(--sp-3);
}

h1 { font-size: 22px; margin: 0 0 16px; }
h2 { font-size: 17px; margin: 24px 0 10px; }
h3 { font-size: 14px; margin: 16px 0 8px; }

.page-header { display: flex; justify-content: space-between; align-items: center; gap: var(--gap); }
.actions { display: flex; gap: 8px; }

/* Buttons */
.btn {
  display: inline-block; padding: 6px 12px; border-radius: var(--radius);
  border: 1px solid var(--border); background: var(--bg-2); color: var(--fg);
  font: inherit; cursor: pointer; text-decoration: none;
}
.btn:hover { background: var(--bg-3); }
.btn-primary { background: var(--accent); color: white; border-color: var(--accent); }
.btn-primary:hover { filter: brightness(0.92); }
.btn-danger  { background: var(--danger); color: white; border-color: var(--danger); }
.inline { display: inline-block; }

/* Forms */
form label { display: block; margin: 8px 0; }
form label.inline { display: inline-flex; align-items: center; gap: 6px; }
input, select, textarea {
  font: inherit; padding: 6px 8px; border: 1px solid var(--border);
  border-radius: var(--radius); background: var(--bg-2); color: var(--fg);
  width: 100%; max-width: 480px;
}
fieldset { border: 1px solid var(--border); border-radius: var(--radius);
  padding: 8px 12px; margin: 8px 0; }
legend { padding: 0 6px; color: var(--muted); font-size: 12px; }

/* ---------------------------------------------------------------------------
 * Phase 4 — Tables.
 *
 * Compact density: row padding tightened, header row reads as a small-caps
 * label. Sticky `<thead>` keeps column titles visible while scrolling long
 * lists. Numeric cells (`.num`) use tabular-nums so digit columns don't
 * jitter when sorted.
 * ------------------------------------------------------------------------- */
.data-table {
  width: 100%;
  border-collapse: collapse;
  margin: var(--sp-2) 0;
  background: var(--bg-2);
  border: 1px solid var(--border);
  border-radius: var(--radius-3);
  overflow: hidden;
  font-size: var(--fs-sm);
}
.data-table th, .data-table td {
  padding: var(--sp-2) var(--sp-3);
  border-bottom: 1px solid var(--border-2);
  text-align: left;
  vertical-align: top;
}
.data-table tbody tr:last-child td { border-bottom: 0; }
.data-table th {
  background: var(--bg-3);
  font-weight: 600;
  font-size: var(--fs-xs);
  color: var(--muted);
  text-transform: uppercase;
  letter-spacing: 0.05em;
  position: sticky; top: 0; z-index: 2;
}
.data-table tbody tr:hover { background: var(--bg-3); }
.data-table.compact th, .data-table.compact td {
  padding: var(--sp-1) var(--sp-2);
  font-size: var(--fs-xs);
}

/* Numeric column alignment — Phase 6.2 anticipates this class on numeric
   <td> + <th> elements. Tabular numbers prevent digit-width jitter. */
.data-table .num {
  text-align: right;
  font-variant-numeric: tabular-nums;
}
.data-table .cell-status { text-align: center; }
.truncate { max-width: 320px; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }

/* Long-value handling — fact-grid + table cells wrap instead of overflowing
   their container. .long-value (auto-applied for values > 60 chars) shrinks
   the font slightly so the wrapped text still fits a card column. */
.kv code, .kv dd code,
.data-table td code {
  word-break: break-word;
  white-space: normal;
  overflow-wrap: anywhere;
}
.kv code.long-value,
.data-table td code.long-value { font-size: 11.5px; line-height: 1.35; }

/* Display-rule visuals — kv_row macro applies kv-highlighted / kv-secure
   to the <dd> when the rule matches. */
.kv .kv-highlighted {
  border-left: 3px solid var(--accent);
  background: var(--accent-soft);
  padding-left: var(--sp-2);
  border-radius: var(--radius);
}
.kv .kv-secure .secret-real { display: none; }
.kv .kv-secure.secure-shown .secret-mask { display: none; }
.kv .kv-secure.secure-shown .secret-real { display: inline; }
.kv .reveal-btn {
  margin-left: 6px; font-size: 11px;
  background: none; border: 1px solid var(--border);
  color: var(--muted); border-radius: 4px;
  padding: 0 6px; cursor: pointer;
}
.kv .reveal-btn:hover { color: var(--fg); }

/* Column chooser — per-user toggle for togglable list-page columns.
   Persisted in localStorage; admin agent_column rules drop columns
   server-side first so the chooser only ever sees survivors. */
.col-chooser { position: relative; display: inline-block; }
.col-chooser > summary { list-style: none; cursor: pointer; }
.col-chooser > summary::-webkit-details-marker { display: none; }
.col-chooser-menu {
  position: absolute; right: 0; top: calc(100% + 4px);
  background: var(--bg-2); border: 1px solid var(--border);
  border-radius: var(--radius); padding: 8px 10px;
  min-width: 200px; z-index: 10;
  box-shadow: 0 6px 24px rgba(0,0,0,0.20);
}
.col-chooser-menu label {
  display: flex; align-items: center; gap: 6px;
  margin: 4px 0; font-size: 13px; cursor: pointer;
}
.col-chooser-menu .col-chooser-reset {
  display: block; margin-top: 8px; padding-top: 6px;
  border-top: 1px solid var(--border);
  font-size: 12px; cursor: pointer; color: var(--accent);
  background: none; border-left: 0; border-right: 0; border-bottom: 0;
}
.cf-meta-hidden .change-meta,
.cf-when-hidden .change-when,
.cf-source-hidden span.muted,
.cf-entity-hidden .entity-kind,
.cf-entity-hidden .entity-type { display: none; }
@media (max-width: 720px) { .col-chooser { display: none; } }

/* ---------------------------------------------------------------------------
 * Phase 2 — Badge family: one source of truth for status pills.
 *
 * `.badge` is the base. `.badge-{ok,warn,danger,info,crit,muted}` apply role
 * tints from the soft-token palette so every "this is a status pill" cell
 * across the app draws from the same swatches.
 *
 * Templates use a mix of legacy class names — `.tier-green`, `.status-enabled`,
 * `.pill-green`, `.signal-green`, `.utier-stable`, `.ebws-yes` — and tests
 * assert a few of those (`green` / `yellow` / `red` / `black` strings round-trip
 * from Python helpers as class names). The grouped selectors below treat each
 * legacy name as an alias of the matching `.badge-*` rule, so we get
 * cross-page consistency without a template churn.
 * ------------------------------------------------------------------------- */
.badge {
  display: inline-block;
  padding: 2px var(--sp-2);
  border-radius: 999px;
  font-size: var(--fs-xs);
  font-weight: 600;
  text-transform: uppercase;
  letter-spacing: 0.04em;
  line-height: 1.4;
  border: 1px solid transparent;
}

.badge-ok,
.tier-green,
.status-enabled,
.ebws-yes,
.utier-stable,
.pill-green,
.pill-stable {
  background: var(--ok-soft);
  color: var(--ok-fg);
}
.badge-warn,
.tier-yellow,
.utier-upgrade_recommended,
.pill-yellow {
  background: var(--warn-soft);
  color: var(--warn-fg);
}
.badge-danger,
.tier-red,
.utier-upgrade_urgent,
.pill-red {
  background: var(--danger-soft);
  color: var(--danger-fg);
}
.badge-info,
.pill-info {
  background: var(--info-soft);
  color: var(--info-fg);
}
.badge-crit,
.tier-black,
.pill-black {
  background: var(--crit-soft);
  color: var(--crit-fg);
}
.badge-muted,
.status-disabled,
.ebws-no {
  background: var(--bg-3);
  color: var(--muted);
}
.status-archived {
  background: var(--bg-3);
  color: var(--muted);
  font-style: italic;
}

/* Filled / high-emphasis variant — used where a soft pill loses against the
   surrounding density. */
.badge-solid {
  background: var(--accent);
  color: #fff;
}
.badge-lg {
  padding: 4px var(--sp-3);
  font-size: var(--fs-sm);
  letter-spacing: 0.05em;
}

/* Row-leading accent stripes (left edge of <tr>). One token per role. */
.row-tier-yellow td:first-child,
.row-utier-upgrade_recommended td:first-child {
  box-shadow: inset 3px 0 0 var(--warn);
}
.row-tier-red    td:first-child,
.row-utier-upgrade_urgent      td:first-child {
  box-shadow: inset 3px 0 0 var(--danger);
}
.row-tier-black  td:first-child {
  box-shadow: inset 3px 0 0 var(--crit);
}
/* Backwards-compat: older selectors painted every cell, not just the first. */
.row-tier-yellow td { box-shadow: inset 3px 0 0 var(--warn); }
.row-tier-red    td { box-shadow: inset 3px 0 0 var(--danger); }
.row-tier-black  td { box-shadow: inset 3px 0 0 var(--crit); }

/* Lifecycle row tints. Use --muted text rather than the previous opacity
   trick which made the whole row muddy on dark. */
.row-status-disabled td,
.row-status-archived td { color: var(--muted); }
.row-status-archived td { font-style: italic; }

/* Inline status text colours (process, service columns). Foreground tones
   from the role palette, no background tint. */
.proc-true,
.svc-running { color: var(--ok-fg); }
.proc-false,
.svc-stopped { color: var(--danger-fg); font-weight: 600; }
.svc-paused  { color: var(--warn-fg); font-weight: 600; }

/* TeamViewer ID rendered as a deep-link to the locally-installed
   TeamViewer client (`teamviewer10:--id=N`). Underline + tooltip
   tell the user clicking will open the helper app. */
a.tv-link {
  text-decoration: underline dotted;
  cursor: pointer;
}
a.tv-link:hover { text-decoration: underline solid; }

/* Generic pager (templates/_pagination.html). Sits below paginated
   tables — total summary on the left, button row + jump form in the
   middle, per-page picker on the right. Wraps cleanly on mobile. */
nav.pager {
  display: flex; flex-wrap: wrap; align-items: center;
  gap: 8px 14px; margin: 12px 0 24px 0;
  font-size: 13px;
}
nav.pager .pager-summary { flex: 1 1 auto; }
nav.pager .pager-buttons { display: inline-flex; gap: 4px; align-items: center; }
nav.pager .pager-jump { display: inline-flex; gap: 4px; align-items: center; }
nav.pager .pager-jump input[type="number"] {
  width: 4em; padding: 2px 4px;
}
nav.pager .pager-size { display: inline-flex; gap: 4px; align-items: center; }
nav.pager .btn-disabled {
  opacity: 0.45; cursor: not-allowed;
}
@media (max-width: 720px) {
  nav.pager { font-size: 12px; }
  nav.pager .pager-summary { flex-basis: 100%; }
}
.svc-disabled, .svc-manual { color: var(--muted); }

.hash { font-size: 11.5px; word-break: break-all; }
.card-ebpos { border-color: var(--accent); }

/* Agent signal pills (TeamViewer / EBpos / Backup). Compact in the
   agents list cell; bigger in the agent-detail header row. */
.signals-cell { white-space: nowrap; }
.signal {
  display: inline-flex; align-items: center; gap: 3px;
  font-family: var(--mono); font-size: 11px; font-weight: 600;
  padding: 1px 6px; border-radius: 4px; margin-right: 4px;
  border: 1px solid transparent;
}
.signal-green  { background: var(--ok-soft);     color: var(--ok-fg);     border-color: var(--ok); }
.signal-red    { background: var(--danger-soft); color: var(--danger-fg); border-color: var(--danger); }
.signal-yellow { background: var(--warn-soft);   color: var(--warn-fg);   border-color: var(--warn); }
.signal-muted  { background: var(--bg-3);        color: var(--muted);     border-color: var(--border); }
.signal-lg {
  font-size: 12.5px; padding: 6px 10px; border-radius: 6px; gap: 6px;
}
.signal-lg .signal-glyph { font-weight: 700; letter-spacing: 0.05em; }
.signal-lg .signal-state { font-weight: 500; text-transform: lowercase; }
.signal-row {
  display: flex; flex-wrap: wrap; gap: var(--sp-2);
  margin: var(--sp-2) 0 var(--sp-3);
  position: sticky; top: 0; z-index: 5;
  background: var(--bg);
  padding: var(--sp-2) 0;
  border-bottom: 1px solid var(--border-2);
}

/* Upgrade tier (EBpos versions catalog). Distinct class from the clock
   tiers above; same color vocabulary so the green/yellow/red mapping
   stays consistent across the app. */
.utier-stable              { background: var(--ok-soft);     color: var(--ok-fg); }
.utier-upgrade_recommended { background: var(--warn-soft);   color: var(--warn-fg); }
.utier-upgrade_urgent      { background: var(--danger-soft); color: var(--danger-fg); font-weight: 700; }
.row-utier-upgrade_recommended td { box-shadow: inset 3px 0 0 var(--warn); }
.row-utier-upgrade_urgent      td { box-shadow: inset 3px 0 0 var(--danger); }

/* Registry viewer */
.reg-path { margin: 8px 0 12px; display: flex; flex-wrap: wrap; gap: 4px; align-items: center; }
.reg-path .btn-link { padding: 2px 6px; }
.reg-layout { grid-template-columns: 320px 1fr; }
.reg-tree {
  background: var(--bg-2); border: 1px solid var(--border);
  border-radius: var(--radius); padding: 10px 12px;
  max-height: 70vh; overflow: auto;
}
.reg-tree h3 { margin-top: 0; }
.reg-children { list-style: none; padding: 0; margin: 0; }
.reg-children li a {
  display: block; padding: 4px 6px; border-radius: 3px;
  font-family: var(--mono); font-size: 12.5px; color: var(--fg);
}
.reg-children li a:hover { background: var(--bg-3); text-decoration: none; }
.reg-tail { word-break: break-all; }
.reg-values { min-width: 0; }
.reg-values-table .reg-data { word-break: break-all; }

/* Key/value list */
.kv {
  display: grid; grid-template-columns: max-content 1fr; gap: 4px 12px;
  margin: 0 0 8px;
}
.kv dt { color: var(--muted); }
.kv dd { margin: 0; }

/* Cards grid (agent detail) */
.cards-grid {
  display: grid; grid-template-columns: repeat(auto-fill, minmax(320px, 1fr));
  gap: var(--sp-3);
}
.card {
  background: var(--bg-2);
  border: 1px solid var(--border);
  border-radius: var(--radius-3);
  padding: var(--sp-4);
  box-shadow: var(--shadow-1);
}
.card h3 {
  margin: 0 0 var(--sp-2);
  font-size: var(--fs-md);
  letter-spacing: 0.01em;
}

/* Roll-up cards on home */
.rollup-grid {
  display: grid; grid-template-columns: repeat(auto-fill, minmax(180px, 1fr));
  gap: var(--sp-3); margin: var(--sp-2) 0 var(--sp-4);
}
.rollup-card {
  background: var(--bg-2);
  border: 1px solid var(--border);
  border-radius: var(--radius-3);
  padding: var(--sp-3) var(--sp-4);
  box-shadow: var(--shadow-1);
}
.rollup-card.warn { border-color: var(--warn); }
.rollup-label { color: var(--muted); font-size: var(--fs-xs); text-transform: uppercase; letter-spacing: 0.05em; }
.rollup-value { font-size: var(--fs-xl); font-weight: 600; line-height: 1.1; }
.rollup-sub   { color: var(--muted); font-size: var(--fs-xs); }

/* Tabs — underline-active, drop the pill-button styling. */
.tabs {
  display: flex; gap: var(--sp-2); flex-wrap: wrap;
  border-bottom: 1px solid var(--border);
  margin-bottom: var(--sp-3);
}
.tab {
  display: inline-block;
  padding: var(--sp-2) var(--sp-3);
  margin-bottom: -1px;
  border: 0;
  border-bottom: 2px solid transparent;
  color: var(--muted);
  font-weight: 500;
  background: transparent;
}
.tab:hover { color: var(--fg); text-decoration: none; }
.tab.active {
  color: var(--fg);
  border-bottom-color: var(--accent);
  background: transparent;
}
.tab-pane { padding: var(--sp-2) 0 0; }
.tab-pane.hidden { display: none; }

/* Filter bar — sits in a card-style container above the table. */
.filter-bar {
  display: flex; flex-wrap: wrap; gap: var(--sp-2) var(--sp-3); align-items: end;
  background: var(--bg-2);
  border: 1px solid var(--border);
  border-radius: var(--radius-3);
  padding: var(--sp-3) var(--sp-4);
  margin-bottom: var(--sp-3);
}
.filter-bar label { margin: 0; }
.filter-bar input,
.filter-bar select {
  width: auto;
  min-width: 140px;
  height: 30px;
  padding: 2px var(--sp-2);
  background: var(--bg-3);
  border: 1px solid var(--border);
  border-radius: var(--radius-2);
  color: var(--fg);
}

/* /changes 2-column layout */
.layout-2col { display: grid; grid-template-columns: 280px 1fr; gap: 16px; }
.filter-sidebar fieldset { background: var(--bg-2); }

/* Change feed list */
.change-feed { list-style: none; padding: 0; margin: 0; }
.change-feed-row {
  display: grid;
  grid-template-columns: 280px 1fr 220px;
  gap: 8px;
  padding: 6px 8px; border-bottom: 1px solid var(--border);
  font-size: 12.5px;
}
.change-feed-row:hover { background: var(--bg-2); }
.change-feed-row .change-when { color: var(--muted); font-family: var(--mono); }
.change-feed-row .change-where { color: var(--fg); }
.change-feed-row .entity-key { background: var(--bg-3); padding: 1px 4px; border-radius: 3px; }
.change-feed-row .change-type-icon { font-weight: bold; padding: 0 4px; }
.change-feed-row.change-add    .change-type-icon { color: var(--ok-fg); }
.change-feed-row.change-remove .change-type-icon { color: var(--danger); }
.change-feed-row.change-update .change-type-icon { color: var(--accent); }
.change-feed-row .change-meta {
  color: var(--muted); font-size: 12px; text-align: right;
}
.change-feed-row .change-meta > * { margin-left: 6px; }

/* Tier dots — small color blob next to a hostname (mobile summary cell). */
.tier-dot {
  display: inline-block; width: 8px; height: 8px;
  border-radius: 50%; margin-right: 6px; vertical-align: middle;
  background: var(--muted);
}
.tier-dot.tier-green  { background: var(--ok); }
.tier-dot.tier-yellow { background: var(--warn); }
.tier-dot.tier-red    { background: var(--danger); }
.tier-dot.tier-black  { background: var(--crit); box-shadow: 0 0 0 1px var(--border); }

/* Mobile-summary block lives inside data cells; hidden on desktop. */
.mobile-summary { display: none; }
.mobile-summary-host {
  display: block; font-weight: 600; margin-top: 4px;
}
.mobile-summary-where { display: block; font-size: 12px; }

.strike { text-decoration: line-through; }

.breadcrumb { color: var(--muted); margin-bottom: 8px; font-size: 12.5px; }
.breadcrumb a { color: var(--accent); }

.quicklinks { display: flex; gap: 8px; align-items: center; margin: 8px 0 12px; }

.pagination { margin: 12px 0; display: flex; gap: 8px; align-items: center; }

.alert {
  border: 1px solid var(--accent);
  background: var(--accent-soft);
  padding: var(--sp-3);
  border-radius: var(--radius);
  margin: var(--sp-2) 0;
}
.alert-info { border-color: var(--ok);     background: var(--ok-soft); }
.alert-warn { border-color: var(--warn);   background: var(--warn-soft); }
.alert-error{ border-color: var(--danger); background: var(--danger-soft); }

/* Banner — wider, full-width emphasis above a page. Same role-tinted
   palette as `.alert` but spans the content column and uses bigger
   typographic emphasis. Templates use `.banner banner-{info,warn,warning}`. */
.banner {
  border: 1px solid var(--border);
  background: var(--bg-2);
  padding: var(--sp-3) var(--sp-4);
  border-radius: var(--radius-3);
  margin: 0 0 var(--sp-3);
  font-size: var(--fs-sm);
}
.banner-info    { border-color: var(--accent); background: var(--accent-soft); }
.banner-warn,
.banner-warning { border-color: var(--warn);   background: var(--warn-soft); }
.banner-danger,
.banner-error   { border-color: var(--danger); background: var(--danger-soft); }
.banner strong { color: var(--fg); }

.login-card {
  max-width: 360px; margin: 60px auto; padding: 24px;
  background: var(--bg-2); border: 1px solid var(--border); border-radius: var(--radius);
}

details > summary { cursor: pointer; list-style: none; }
details > summary::-webkit-details-marker { display: none; }
details > summary h2 { display: inline-block; margin: 8px 0; }

/* Avatar + user menu (topbar) */
.user-menu { position: relative; display: inline-block; }
.user-menu-trigger {
  display: inline-flex; align-items: center; gap: var(--sp-2);
  padding: var(--sp-1) var(--sp-2); border-radius: var(--radius-1);
  cursor: pointer;
  list-style: none;
}
.user-menu-trigger::-webkit-details-marker { display: none; }
.user-menu-trigger:hover { background: var(--bg-3); }
.avatar {
  display: inline-flex; justify-content: center; align-items: center;
  width: 26px; height: 26px; border-radius: 50%;
  background: var(--accent); color: #fff;
  font-size: var(--fs-sm); font-weight: 600; user-select: none;
}
.user-menu-email { font-size: var(--fs-sm); color: var(--muted); }
.user-menu-dropdown {
  position: absolute; right: 0; top: 100%; min-width: 200px;
  margin: var(--sp-1) 0 0; padding: var(--sp-1) 0; list-style: none;
  background: var(--bg-2); border: 1px solid var(--border);
  border-radius: var(--radius-2); z-index: 100;
  box-shadow: var(--shadow-2);
}
.user-menu-dropdown li { padding: 0; }
.user-menu-dropdown a, .user-menu-dropdown .btn-link {
  display: block; padding: 6px 12px; font-size: 13px;
  color: var(--fg); text-decoration: none;
  background: none; border: 0; width: 100%; text-align: left; cursor: pointer;
}
.user-menu-dropdown a:hover, .user-menu-dropdown .btn-link:hover {
  background: var(--bg-3, var(--bg-2));
}
.user-menu-sep { border-top: 1px solid var(--border); margin: 4px 0; height: 0; }

/* ------------------------------------------------------------------
   Dashboards module
   ------------------------------------------------------------------ */

.dash-shell.kiosk { padding: 0; margin: 0; }
.dash-shell.kiosk + footer,
body.kiosk .topbar, body.kiosk .sidenav { display: none !important; }

.dash-bar {
  display: flex; align-items: center; justify-content: space-between;
  gap: 16px; flex-wrap: wrap;
  padding: 8px 0; margin-bottom: 12px;
  border-bottom: 1px solid var(--border);
}
.dashtabs { display: flex; gap: 4px; flex-wrap: wrap; }
.dashtab {
  display: inline-block; padding: 6px 12px;
  border-radius: 4px; text-decoration: none;
  color: var(--muted); font-size: 13px;
}
.dashtab:hover { background: var(--bg-2); color: var(--fg); }
.dashtab.active {
  background: var(--accent); color: #fff;
}
.dash-controls { display: inline-flex; align-items: center; gap: 8px; }
.dash-title { margin: 8px 0 16px; }
.kiosk-exit { text-align: right; opacity: 0.4; font-size: 11px; }
.kiosk-exit:hover { opacity: 1; }

.hero-tiles {
  display: grid; gap: 12px;
  grid-template-columns: repeat(auto-fit, minmax(180px, 1fr));
  margin: 16px 0 8px;
}
.hero {
  padding: 18px 20px; border-radius: 6px;
  background: var(--bg-2); border: 1px solid var(--border);
  text-align: left;
}
.hero-num { font-size: 36px; font-weight: 600; line-height: 1; }
.hero-label { font-size: 12px; text-transform: uppercase; letter-spacing: 0.06em; color: var(--muted); margin-top: 4px; }
.hero-green  { border-left: 4px solid var(--ok); }
.hero-yellow { border-left: 4px solid var(--warn); }
.hero-red    { border-left: 4px solid var(--danger); }
.hero-black  { border-left: 4px solid var(--crit); }
.hero-green  .hero-num { color: var(--ok-fg); }
.hero-yellow .hero-num { color: var(--warn-fg); }
.hero-red    .hero-num { color: var(--danger-fg); }
.hero-black  .hero-num { color: var(--crit-fg); }

.status-bar {
  display: flex; height: 12px; margin: var(--sp-3) 0;
  border-radius: var(--radius-2); overflow: hidden;
  border: 1px solid var(--border); background: var(--bg-2);
}
.status-bar .seg { min-width: 0; }
.status-bar .seg-green  { background: var(--ok); }
.status-bar .seg-yellow { background: var(--warn); }
.status-bar .seg-red    { background: var(--danger); }
.status-bar .seg-black  { background: var(--crit); }

.dash-cols {
  display: grid; gap: 24px;
  grid-template-columns: 1fr 1fr;
  margin: 16px 0;
}
@media (max-width: 900px) { .dash-cols { grid-template-columns: 1fr; } }

.reason-list { list-style: none; padding: 0; margin: 0; }
.reason-list li { padding: 4px 0; border-bottom: 1px dashed var(--border); }
.reason-list li:last-child { border-bottom: 0; }

.chip {
  display: inline-block;
  padding: 1px 8px; margin-right: 6px;
  font-size: 11.5px; line-height: 1.6;
  background: var(--bg-2); border: 1px solid var(--border);
  border-radius: 999px; color: var(--muted);
}

.pill {
  display: inline-block; padding: 2px 8px;
  font-size: 11px; font-weight: 600;
  text-transform: uppercase; letter-spacing: 0.04em;
  border-radius: 4px; line-height: 1.4;
}
/* Subtle attention pulse — applied to alert badges in the sidenav.
   The reduced-motion override at the bottom of this file kills the
   animation when the user prefers reduced motion. */
@keyframes pill-pulse {
  0%, 100% { box-shadow: 0 0 0 0 var(--danger-soft); }
  50%      { box-shadow: 0 0 0 6px transparent; }
}
.pill.pulse { animation: pill-pulse 2.4s ease-in-out infinite; }

.pill-green   { background: var(--ok-soft);     color: var(--ok-fg); }
.pill-yellow  { background: var(--warn-soft);   color: var(--warn-fg); }
.pill-red     { background: var(--danger-soft); color: var(--danger-fg); }
.pill-black   { background: var(--crit-soft);   color: var(--crit-fg); }
.pill-stable  { background: var(--ok-soft);     color: var(--ok-fg); }
.pill-upgrade_recommended { background: var(--warn-soft);   color: var(--warn-fg); }
.pill-upgrade_urgent      { background: var(--danger-soft); color: var(--danger-fg); }

.row-tier-red    td:first-child { box-shadow: inset 3px 0 0 var(--danger); }
.row-tier-yellow td:first-child { box-shadow: inset 3px 0 0 var(--warn); }
.row-tier-black  td:first-child { box-shadow: inset 3px 0 0 var(--crit); }

.agent-grid {
  display: grid; gap: 4px;
  grid-template-columns: repeat(auto-fill, minmax(86px, 1fr));
  margin: 12px 0;
}
.agent-cell {
  display: flex; align-items: center; justify-content: center;
  padding: 14px 6px; border-radius: 4px;
  font-size: 11px; text-decoration: none;
  font-family: ui-monospace, SFMono-Regular, Menlo, Consolas, monospace;
  border: 1px solid var(--border);
  background: var(--bg-2); color: var(--fg);
}
.agent-cell:hover { filter: brightness(1.1); }
.agent-cell .agent-host {
  overflow: hidden; text-overflow: ellipsis; white-space: nowrap; max-width: 100%;
}
.agent-cell.tier-green  { background: var(--ok-soft);     border-color: var(--ok); }
.agent-cell.tier-yellow { background: var(--warn-soft);   border-color: var(--warn); }
.agent-cell.tier-red    { background: var(--danger-soft); border-color: var(--danger); }
.agent-cell.tier-black  { background: var(--crit-soft);   color: var(--crit-fg); border-color: var(--crit); }

.bar {
  display: inline-block; width: 100%; height: 8px;
  background: var(--bg-2); border-radius: 4px;
  border: 1px solid var(--border); overflow: hidden;
}
.bar-fill {
  display: block; height: 100%;
  background: var(--accent);
}

.data-table.compact td, .data-table.compact th { padding: 4px 8px; font-size: 12.5px; }
.num { text-align: right; font-variant-numeric: tabular-nums; }
.small { font-size: 12px; }

/* Changes-pulse histogram */
.pulse-bars {
  display: flex; align-items: flex-end; gap: 2px;
  height: 140px; margin: 16px 0 4px;
  padding: 4px;
  background: var(--bg-2); border: 1px solid var(--border); border-radius: 4px;
}
.pulse-col {
  flex: 1 1 0; display: flex; flex-direction: column; justify-content: flex-end;
  align-items: center; min-width: 6px;
  position: relative;
}
.pulse-col .pulse-bar {
  display: block; width: 100%;
  background: var(--accent);
  border-radius: 2px 2px 0 0;
  min-height: 1px;
}
.pulse-col.spike .pulse-bar {
  background: var(--danger-fg);
}
.pulse-col .pulse-tick {
  display: none;     /* axis labels would crowd; use title attribute */
}

/* Profile hero */
.profile-hero {
  display: flex; align-items: center; gap: 20px;
  padding: 16px 0; margin-bottom: 12px;
  border-bottom: 1px solid var(--border);
}
.profile-hero h1 { margin: 0 0 2px; font-size: 24px; }
.profile-hero-meta { margin: 8px 0 0; }
.profile-hero-meta .chip,
.profile-hero-meta .pill { margin-right: 6px; }
.avatar-xl {
  width: 64px; height: 64px;
  font-size: 24px;
}

/* Snapshot anchors panel on agent history */
.anchors-panel {
  margin: 12px 0;
  padding: 10px 12px;
  background: var(--bg-2);
  border: 1px solid var(--border);
  border-radius: 4px;
}
.anchors-panel summary { cursor: pointer; }
.anchors-list {
  list-style: none; margin: 8px 0 0; padding: 0;
}
.anchor {
  padding: 4px 0;
  border-bottom: 1px dashed var(--border);
  font-size: 12.5px;
}
.anchor:last-child { border-bottom: 0; }
.anchor a { text-decoration: none; color: var(--fg); }
.anchor a:hover { background: var(--bg-3, var(--bg-2)); }
.anchor .chip { margin-right: 8px; min-width: 100px; text-align: center; }
.anchor .anchor-time {
  display: inline-block; min-width: 160px;
  font-family: ui-monospace, SFMono-Regular, Menlo, Consolas, monospace;
  color: var(--muted);
  margin-right: 10px;
}
.anchor-first_seen .chip    { background: var(--ok-soft);     color: var(--ok-fg); }
.anchor-ebpos_version .chip { background: var(--accent-soft); color: var(--accent-fg); }
.anchor-hostname .chip      { background: var(--warn-soft);   color: var(--warn-fg); }
.anchor-location .chip      { background: var(--bg-3);        color: var(--muted); }
.anchor-identity .chip      { background: var(--danger-soft); color: var(--danger-fg); }

/* ---- Hamburger toggle (only visible on mobile via media query below) ----- */
.nav-toggle {
  display: none;
  background: none; border: 0; cursor: pointer;
  padding: 8px; margin-right: 8px;
  flex-direction: column; gap: 4px;
}
.nav-toggle span {
  display: block; width: 22px; height: 2px;
  background: var(--fg); border-radius: 2px;
}
.topbar-left { display: flex; align-items: center; }

/* ---- Mobile breakpoint --------------------------------------------------- */
@media (max-width: 720px) {
  .content { padding: 12px; }
  .topbar { padding: 8px 12px; }
  .user-menu-email { display: none; }
  .nav-toggle { display: inline-flex; }

  /* Sidenav becomes a top drawer toggled by .nav-toggle. */
  .layout { flex-direction: column; }
  .sidenav {
    width: 100%; border-right: 0; border-bottom: 1px solid var(--border);
    display: none; padding: 4px 0;
  }
  body.nav-open .sidenav { display: block; }
  .sidenav a { padding: 10px 16px; }

  /* Filter bars & forms wrap cleanly. */
  .filter-bar { flex-wrap: wrap; gap: var(--sp-2); padding: var(--sp-3); }
  .filter-bar label { display: block; width: 100%; }
  .filter-bar input,
  .filter-bar select { min-width: 0; width: 100%; }
  input, select, textarea { max-width: 100%; }
  .page-header { flex-wrap: wrap; }
  .page-header h1 { font-size: var(--fs-lg); }
  .page-header .actions { width: 100%; flex-wrap: wrap; }

  /* Form fieldsets — let labels breathe at narrow widths and stop the
     sticky signal-row from overlapping a heading. */
  fieldset { padding: var(--sp-3); }
  fieldset > label { display: block; margin-bottom: var(--sp-2); }
  .signal-row { gap: var(--sp-1); padding: var(--sp-1) 0; }

  /* Topbar at 375px: the TZ chip duplicates info already visible in
     timestamps; hide it and reclaim the row. */
  .topbar-tz { display: none; }

  /* Combobox popover stretches to viewport width on phones so the
     option list is readable without horizontal scroll. */
  .combobox-wrap { max-width: 100%; }
  .combobox-list { max-height: 50vh; }

  /* Foldable tables — each row becomes a card; first cell stays visible,
     rest collapse until the user taps to expand. */
  .data-table.foldable,
  .data-table.foldable thead,
  .data-table.foldable tbody,
  .data-table.foldable tr,
  .data-table.foldable td,
  .data-table.foldable th { display: block; }
  .data-table.foldable thead { display: none; }
  .data-table.foldable {
    background: transparent; border: 0; border-radius: 0;
    overflow: visible;
  }
  .data-table.foldable tbody tr {
    background: var(--bg-2);
    border: 1px solid var(--border);
    border-radius: var(--radius);
    margin-bottom: 8px;
    padding: 10px 36px 10px 12px;
    position: relative;
    cursor: pointer;
  }
  .data-table.foldable tbody tr::after {
    content: "▸";
    position: absolute; top: 10px; right: 12px;
    color: var(--muted);
    transition: transform 0.15s ease;
  }
  .data-table.foldable tbody tr.row-open::after {
    transform: rotate(90deg);
  }
  .data-table.foldable tbody tr:hover { background: var(--bg-2); }
  .data-table.foldable td {
    padding: 4px 0; border: 0; text-align: left;
  }
  .data-table.foldable td:first-child {
    font-weight: 600; padding-bottom: 6px;
  }
  /* Hide every cell except the first until the row is open. */
  .data-table.foldable tbody tr td:not(:first-child) { display: none; }
  .data-table.foldable tbody tr.row-open td:not(:first-child) {
    display: flex; gap: 8px; align-items: baseline;
    border-top: 1px dashed var(--border); padding-top: 6px;
  }
  .data-table.foldable td[data-label]:not(:first-child)::before {
    content: attr(data-label);
    color: var(--muted); font-size: 12px;
    min-width: 110px; flex-shrink: 0;
  }
  /* Empty-state row (single colspan cell) shouldn't get the chevron. */
  .data-table.foldable tbody tr:has(td[colspan])::after { content: ""; }
  .data-table.foldable tbody tr:has(td[colspan]) {
    cursor: default; padding-right: 12px;
  }

  /* Generic key-value descriptive lists wrap cleanly. */
  dl.kv { grid-template-columns: 1fr; }

  /* Show the synthesised mobile-summary block (hostname + tier dot +
     breadcrumb) inside the first cell of foldable tables, so cards aren't
     just "GREEN" with no identifier. */
  .mobile-summary { display: block; margin-top: 4px; }

  /* Change feed — restructure each row as a card with the most
     important data on top (hostname + change), and time/source/kind
     folded behind a tap. */
  .change-feed-row {
    grid-template-columns: 1fr;
    gap: 4px;
    padding: 10px 36px 10px 12px;
    background: var(--bg-2);
    border: 1px solid var(--border);
    border-radius: var(--radius);
    margin-bottom: 8px;
    border-bottom: 1px solid var(--border); /* override desktop hairline */
    cursor: pointer; position: relative;
  }
  .change-feed-row::after {
    content: "▸"; position: absolute; top: 10px; right: 12px;
    color: var(--muted); transition: transform 0.15s ease;
  }
  .change-feed-row.row-open::after { transform: rotate(90deg); }
  .change-feed-row .change-where { font-weight: 600; order: 1; }
  .change-feed-row .change-what  { order: 2; }
  .change-feed-row .change-meta  {
    order: 3; display: none; text-align: left;
    border-top: 1px dashed var(--border); padding-top: 6px;
  }
  .change-feed-row .change-meta > * { margin-left: 0; margin-right: 10px; }
  .change-feed-row.row-open .change-meta { display: block; }
}

/* Profile push table — highlight the row that matches the current
   browser's device UUID so users can identify their active device. */
.data-table tbody tr.is-current {
  background-color: color-mix(in srgb, var(--accent) 12%, transparent);
}
.data-table tbody tr.is-current td:first-child {
  border-left: 3px solid var(--accent);
}

/* Sortable tables — clickable header + asc/desc indicator. The arrow
   uses ::after so the th's intrinsic width doesn't reflow when the
   indicator appears or flips. */
table.sortable th.sortable-th {
  cursor: pointer;
  user-select: none;
  position: relative;
  padding-right: 18px;
}
table.sortable th.sortable-th::after {
  content: '';
  position: absolute;
  right: 6px;
  top: 50%;
  transform: translateY(-50%);
  opacity: 0.35;
  font-size: 10px;
  color: var(--text-muted, #9aa);
}
table.sortable th.sortable-th:hover::after {
  opacity: 0.7;
  content: '⇅';
}
table.sortable th.sort-asc::after {
  content: '▲';
  opacity: 0.9;
  color: var(--accent);
}
table.sortable th.sort-desc::after {
  content: '▼';
  opacity: 0.9;
  color: var(--accent);
}

/* ---------------------------------------------------------------------------
 * Phase 1 — Focus rings + reduced-motion respect.
 *
 * `:focus-visible` only paints when the user is keyboard-navigating (or when
 * the browser otherwise decides a visible ring is appropriate), so mouse
 * clicks don't get a hard outline. Buttons / inputs additionally get a soft
 * outer halo via box-shadow for AA-pleasant emphasis.
 *
 * The motion override flips every CSS animation / transition off when the
 * OS-level reduce-motion pref is set, without us having to edit each rule.
 * ------------------------------------------------------------------------- */
:focus-visible {
  outline: 2px solid var(--accent);
  outline-offset: 2px;
  border-radius: var(--radius-1);
}
button:focus-visible,
input:focus-visible,
select:focus-visible,
textarea:focus-visible,
.btn:focus-visible,
a.btn:focus-visible {
  outline: 2px solid var(--accent);
  outline-offset: 2px;
  box-shadow: 0 0 0 4px var(--accent-soft);
}

/* ---------------------------------------------------------------------------
 * Combobox — searchable enhancement of any `<select class="combobox">`.
 *
 * Native <select> stays in the DOM (so form submit + cascading helpers
 * keep working); JS hides it visually and renders a text input + a
 * filtered popover above it. Without JS the user gets the plain
 * native control as a fallback.
 * ------------------------------------------------------------------------- */
.combobox-wrap {
  position: relative;
  display: block;
  max-width: 480px;
}
.combobox-inputbox {
  position: relative;
  display: block;
}
.combobox-input {
  width: 100%;
  height: 30px;
  /* Right-padding leaves room for the × clear button + ▾ chevron. */
  padding: 4px 44px 4px var(--sp-2);
  background: var(--bg-3);
  color: var(--fg);
  border: 1px solid var(--border);
  border-radius: var(--radius-2);
  font: inherit;
}
.combobox-input::placeholder { color: var(--muted); }
.combobox-clear {
  position: absolute;
  top: 50%;
  right: 22px;
  transform: translateY(-50%);
  width: 18px;
  height: 18px;
  padding: 0;
  display: inline-flex;
  align-items: center;
  justify-content: center;
  background: transparent;
  border: 0;
  border-radius: 50%;
  color: var(--muted);
  font-size: 16px;
  line-height: 1;
  cursor: pointer;
}
.combobox-clear:hover { background: var(--bg-2); color: var(--fg); }
.combobox-clear[hidden] { display: none; }
.combobox-chevron {
  position: absolute;
  top: 50%;
  right: 8px;
  transform: translateY(-50%);
  color: var(--muted);
  font-size: 10px;
  line-height: 1;
  pointer-events: none;
}
.combobox-list {
  position: absolute;
  top: calc(100% + 2px); left: 0; right: 0;
  z-index: 20;
  margin: 0; padding: var(--sp-1) 0;
  list-style: none;
  background: var(--bg-2);
  border: 1px solid var(--border);
  border-radius: var(--radius-2);
  max-height: 240px; overflow-y: auto;
  box-shadow: var(--shadow-2);
  font-size: var(--fs-sm);
}
.combobox-list.flip-up {
  top: auto;
  bottom: calc(100% + 2px);
}
.combobox-option,
.combobox-empty {
  padding: 4px var(--sp-3);
  cursor: pointer;
  color: var(--fg);
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
}
.combobox-option.is-active,
.combobox-option:hover { background: var(--bg-3); color: var(--accent); }
.combobox-option mark {
  background: var(--accent-soft);
  color: inherit;
  padding: 0;
  border-radius: 2px;
  font-weight: 600;
}
.combobox-empty { cursor: default; color: var(--muted); }
/* Hide the native <select> visually but keep it in the layout flow so
   form submit + scripts still find it. `display: none` would remove it
   from a11y tree; `visibility: hidden` keeps the field validation hooks
   but takes up no extra row height because the wrap above sits in front. */
select.combobox-native {
  position: absolute;
  width: 1px; height: 1px;
  opacity: 0;
  pointer-events: none;
}

/* ---------------------------------------------------------------------------
 * Layout utilities — for the handful of places where a one-off margin or
 * inline form would otherwise inline-style its way past the design tokens.
 * Keep this set small; if you reach for a third margin variant, add it
 * here rather than introducing `style="margin-…"` in a template.
 * ------------------------------------------------------------------------- */
.mt-2 { margin-top: var(--sp-2); }
.mt-3 { margin-top: var(--sp-3); }
.mt-4 { margin-top: var(--sp-4); }
.mt-5 { margin-top: var(--sp-5); }
.mb-2 { margin-bottom: var(--sp-2); }
.mb-3 { margin-bottom: var(--sp-3); }
.mono { font-family: var(--mono); }
/* Inline `<form>` that wraps a single button — defaults to block,
   which forces the button onto its own line. Used in agent-detail
   action rows + the user-management action cluster. */
form.inline,
.inline-form {
  display: inline;
}
/* `<label class="inline">` — a label that sits next to its sibling
   button instead of stacking above it. Pairs with `form.inline`. */
label.inline {
  display: inline-flex;
  align-items: center;
  gap: var(--sp-2);
  margin-right: var(--sp-2);
  font-weight: 500;
}
/* Two-column `<ul>` for long compact reference lists (e.g. permission
   keys on api_clients_detail). */
.col-2 {
  columns: 2;
  column-gap: 32px;
}
/* Wide-input min-width for filter bars / registry-viewer search where
   the default field is too narrow to type meaningful queries into. */
.min-w-md { min-width: 360px; }

/* Clock-issues help-list spacing. */
ul.clock-issues-help {
  margin: var(--sp-2) 0 0 20px;
  line-height: 1.6;
}

/* Radio-group block — visually a labelled cluster of radios without the
   nested-fieldset border that the browser draws around <fieldset>. Used
   inside an outer fieldset (e.g. agent-edit "EBpos overrides") where a
   second border would be busy. */
.form-card .radio-group {
  margin: var(--sp-2) 0 var(--sp-3);
}
.form-card .radio-group-label {
  margin: 0 0 var(--sp-1);
  font-size: var(--fs-xs);
  font-weight: 600;
  color: var(--muted);
  text-transform: uppercase;
  letter-spacing: 0.05em;
}

/* Phase 9 — Skip link. Hidden until focused so keyboard-only users
   can jump past the topbar + sidenav straight into <main>. */
.skip-link {
  position: absolute;
  left: -9999px;
  top: var(--sp-2);
  z-index: 1000;
  padding: var(--sp-2) var(--sp-3);
  background: var(--accent);
  color: #fff;
  border-radius: var(--radius-2);
  font-weight: 600;
}
.skip-link:focus,
.skip-link:focus-visible {
  left: var(--sp-3);
  outline: 2px solid var(--accent);
  outline-offset: 2px;
  text-decoration: none;
}

@media (prefers-reduced-motion: reduce) {
  *,
  *::before,
  *::after {
    animation-duration: 0.01ms !important;
    animation-iteration-count: 1 !important;
    transition-duration: 0.01ms !important;
    scroll-behavior: auto !important;
  }
}

/* Remote-command panel on the agent commands page. Phase 13d redesign:
   grouped sections with header + blurb, each task rendered as a card
   with an icon, name, help, and a contextual action (button or
   reveal-form). Destructive group is wrapped in <details> so it stays
   hidden unless the operator explicitly opts in. */

.task-intro {
  margin-top: var(--sp-2);
  margin-bottom: var(--sp-4);
  max-width: 64ch;
}

.task-group {
  margin: var(--sp-4) 0 var(--sp-5);
  background: var(--bg-1);
  border: 1px solid var(--border);
  border-radius: 10px;
  padding: var(--sp-3) var(--sp-4) var(--sp-4);
}

.task-group-header {
  display: flex;
  align-items: baseline;
  gap: var(--sp-3);
  flex-wrap: wrap;
  margin-bottom: var(--sp-3);
  padding-bottom: var(--sp-2);
  border-bottom: 1px solid var(--border);
}
.task-group-title {
  font-size: 1.05rem;
  font-weight: 600;
  letter-spacing: 0.01em;
}
.task-group-blurb {
  flex: 1 1 auto;
}

/* Destructive group reveal: <details>/<summary> with a danger accent. */
.task-group-destructive {
  border-color: var(--danger, #c0392b);
  background: rgba(192, 57, 43, 0.05);
}
.task-group-destructive > .task-group-header {
  cursor: pointer;
  user-select: none;
  list-style: none;
  margin-bottom: 0;
  padding-bottom: 0;
  border-bottom: 0;
}
.task-group-destructive[open] > .task-group-header {
  margin-bottom: var(--sp-3);
  padding-bottom: var(--sp-2);
  border-bottom: 1px solid var(--danger, #c0392b);
}
.task-group-destructive > .task-group-header::-webkit-details-marker { display: none; }
.task-group-destructive .task-group-icon {
  width: 18px; height: 18px;
  color: var(--danger, #c0392b);
  flex-shrink: 0;
}
.task-group-destructive .task-group-toggle::before { content: '▸ '; }
.task-group-destructive[open] .task-group-toggle::before { content: '▾ '; }

/* Card grid — auto-fills with cards ~280-360px wide. */
.task-grid {
  display: grid;
  grid-template-columns: repeat(auto-fill, minmax(280px, 1fr));
  gap: var(--sp-3);
}

.task-card {
  background: var(--bg-2);
  border: 1px solid var(--border);
  border-radius: 8px;
  padding: var(--sp-3);
  display: flex;
  flex-direction: column;
  gap: var(--sp-2);
  transition: border-color 120ms ease, transform 120ms ease,
              box-shadow 120ms ease;
}
.task-card:hover {
  border-color: var(--accent, #5b8def);
  box-shadow: 0 2px 12px rgba(0, 0, 0, 0.18);
  transform: translateY(-1px);
}
.task-card-destructive {
  border-color: rgba(192, 57, 43, 0.4);
}
.task-card-destructive:hover {
  border-color: var(--danger, #c0392b);
  box-shadow: 0 2px 12px rgba(192, 57, 43, 0.25);
}

.task-card-header {
  display: flex;
  align-items: center;
  gap: var(--sp-2);
}
.task-icon {
  width: 24px;
  height: 24px;
  flex-shrink: 0;
  color: var(--accent, #5b8def);
}
.task-card-destructive .task-icon { color: var(--danger, #c0392b); }
.task-card-title {
  margin: 0;
  font-size: 0.98rem;
  font-weight: 600;
  letter-spacing: 0;
}
.task-card-help {
  margin: 0;
  flex: 1;
  line-height: 1.4;
}

.task-card-form {
  margin-top: var(--sp-2);
  display: flex;
  flex-direction: column;
  gap: var(--sp-2);
}

/* Buttons inside a card span full width and align text left so
   the icon + label feel anchored to the card. */
.task-card-form > .btn,
.task-card-form > details > summary.btn {
  width: 100%;
  text-align: center;
  justify-content: center;
}

.task-card-fire {
  font-weight: 600;
}

.task-card-expand > summary {
  cursor: pointer;
  list-style: none;
}
.task-card-expand > summary::-webkit-details-marker { display: none; }
.task-card-expand[open] > summary {
  margin-bottom: var(--sp-2);
}

.task-card-schedule-toggle {
  background: transparent;
  border: 1px dashed var(--border);
  color: var(--muted);
  font-weight: normal;
}
.task-card-schedule-toggle:hover {
  color: var(--fg);
  border-color: var(--accent, #5b8def);
}

/* Schedule block partial — keep the dates inline, body wraps below. */
.task-schedule {
  border: 1px dashed var(--border);
  border-radius: 6px;
  padding: var(--sp-2) var(--sp-3);
  margin-top: var(--sp-2);
}
.task-schedule legend {
  font-weight: 600;
  letter-spacing: 0.04em;
  text-transform: uppercase;
  font-size: 0.72rem;
  color: var(--muted);
}
.task-schedule label {
  display: inline-flex;
  flex-direction: column;
  gap: 2px;
  margin-right: var(--sp-3);
  margin-top: var(--sp-1);
}

@media (max-width: 720px) {
  .task-grid { grid-template-columns: 1fr; }
}


/* ---------------------------------------------------------------------------
   Fleet explorer — collapsible companies → locations → agents.
   Pure CSS on top of native <details>; HTMX handles the lazy load.
--------------------------------------------------------------------------- */
.fleet-tree {
  list-style: none;
  padding: 0;
  margin: 1.2em 0;
}
.fleet-tree-locations {
  margin: 0.4em 0 0.4em 1.4em;
  border-left: 2px solid var(--border, #2a3140);
  padding-left: 0.9em;
}
.fleet-tree li.fleet-company,
.fleet-tree li.fleet-location {
  margin: 0.3em 0;
}
.fleet-disclosure {
  border: 1px solid var(--border, #2a3140);
  border-radius: 6px;
  background: var(--bg-elevated, #161b22);
}
.fleet-tree-locations .fleet-disclosure {
  background: transparent;
  border: none;
  border-radius: 0;
}
.fleet-disclosure-summary {
  cursor: pointer;
  user-select: none;
  padding: 0.55em 0.9em;
  display: flex;
  align-items: center;
  gap: 0.7em;
}
.fleet-disclosure-summary::-webkit-details-marker { display: none; }
.fleet-disclosure-summary::before {
  content: "▸";
  display: inline-block;
  width: 1ch;
  font-size: 0.85em;
  opacity: 0.7;
  transition: transform 0.12s ease;
}
.fleet-disclosure[open] > .fleet-disclosure-summary::before {
  transform: rotate(90deg);
}
.fleet-disclosure-summary:hover { background: var(--bg-hover, #1d2330); }
.fleet-name { font-weight: 600; }
.fleet-counts { margin-left: auto; }
.fleet-disclosure-body {
  padding: 0.4em 0.9em 0.7em 0.9em;
}
.fleet-tree-locations .fleet-disclosure-body { padding-left: 0; padding-right: 0; }
.fleet-col-seed[hidden] { display: none !important; }



/* Server-side sortable headers (used by /logs, /auth-logs, /audit-logs).
   These are paginated views where click-to-sort has to re-issue the
   query with ORDER BY (so the sort spans every page). The link itself
   carries the action; the asc/desc arrow is rendered server-side
   inside the <a> via the sort_th macro. */
table .server-sortable a {
  color: inherit;
  text-decoration: none;
  display: inline-block;
  padding-right: 4px;
}
table .server-sortable a:hover { text-decoration: underline; }
table .server-sortable .sort-arrow {
  color: var(--accent);
  font-size: 0.85em;
  margin-left: 2px;
}

