/* Copied verbatim from the Django Portal build's portal.css (/root/tenant-poc), which itself
   was ported from mockup1.html's <style> block — see that project's "Shared chrome/CSS/JS"
   pattern. Kept identical here so /alpha2 matches /alpha's look; if this file changes on the
   Django side for a shared-feel reason (not a Django-specific one), re-sync it here too. */

:root {
  color-scheme: light;
  --page: #F3F5F7;
  --surface: #FFFFFF;
  --surface-2: #F8F9FB;
  --surface-3: #EEF1F4;
  --ink: #10202E;
  --ink-2: #4B5A6B;
  --muted: #8592A0;
  --border: rgba(16,32,46,.10);
  --border-strong: rgba(16,32,46,.18);
  --accent: #0B7285;
  --accent-hover: #095E6D;
  --accent-tint: #E4F1F3;
  --accent-ink: #06414C;
  --good: #0ca30c;
  --good-tint: #E3F6E3;
  --warning: #c98500;
  --warning-tint: #FCF0D8;
  --critical: #d03b3b;
  --critical-tint: #FBE4E4;
  --shadow: 0 1px 2px rgba(16,32,46,.06), 0 8px 24px -12px rgba(16,32,46,.18);
  --font-sans: "Open Sans", -apple-system, "Segoe UI", "Helvetica Neue", Arial, sans-serif;
  --font-mono: ui-monospace, "SF Mono", "Cascadia Mono", "Roboto Mono", Consolas, monospace;
}
@media (prefers-color-scheme: dark) {
  :root:not([data-theme="light"]) .brand-logo-light { display: none; }
  :root:not([data-theme="light"]) .brand-logo-dark { display: block; }
  :root:not([data-theme="light"]) {
    color-scheme: dark;
    --page: #0B1116;
    --surface: #121A21;
    --surface-2: #17212A;
    --surface-3: #1D2933;
    --ink: #F2F6F8;
    --ink-2: #B7C3CC;
    --muted: #7E8B96;
    --border: rgba(255,255,255,.09);
    --border-strong: rgba(255,255,255,.16);
    --accent: #35A9BD;
    --accent-hover: #55BDCF;
    --accent-tint: rgba(53,169,189,.14);
    --accent-ink: #BEEBF3;
    --good: #35c25d;
    --good-tint: rgba(53,194,93,.14);
    --warning: #e0a418;
    --warning-tint: rgba(224,164,24,.14);
    --critical: #e66767;
    --critical-tint: rgba(230,103,103,.14);
    --shadow: 0 1px 2px rgba(0,0,0,.3), 0 8px 24px -12px rgba(0,0,0,.5);
  }
}
:root[data-theme="dark"] .brand-logo-light { display: none; }
:root[data-theme="dark"] .brand-logo-dark { display: block; }
:root[data-theme="dark"] {
  color-scheme: dark;
  --page: #0B1116;
  --surface: #121A21;
  --surface-2: #17212A;
  --surface-3: #1D2933;
  --ink: #F2F6F8;
  --ink-2: #B7C3CC;
  --muted: #7E8B96;
  --border: rgba(255,255,255,.09);
  --border-strong: rgba(255,255,255,.16);
  --accent: #35A9BD;
  --accent-hover: #55BDCF;
  --accent-tint: rgba(53,169,189,.14);
  --accent-ink: #BEEBF3;
  --good: #35c25d;
  --good-tint: rgba(53,194,93,.14);
  --warning: #e0a418;
  --warning-tint: rgba(224,164,24,.14);
  --critical: #e66767;
  --critical-tint: rgba(230,103,103,.14);
  --shadow: 0 1px 2px rgba(0,0,0,.3), 0 8px 24px -12px rgba(0,0,0,.5);
}

* { box-sizing: border-box; }
html, body { margin: 0; padding: 0; }
body {
  background: var(--page);
  color: var(--ink);
  font-family: var(--font-sans);
  font-size: 14px;
  line-height: 1.45;
  -webkit-font-smoothing: antialiased;
}
a { color: inherit; }
button { font-family: inherit; font-size: inherit; }
:focus-visible { outline: 2px solid var(--accent); outline-offset: 2px; border-radius: 4px; }
.mono { font-family: var(--font-mono); font-variant-numeric: tabular-nums; }

/* ---- shell ---- */
.app { display: flex; min-height: 100vh; }
.sidebar {
  width: 268px; flex: 0 0 268px;
  background: var(--surface); border-right: 1px solid var(--border);
  display: flex; flex-direction: column; padding: 18px 14px; gap: 22px;
  position: sticky; top: 0; height: 100vh; overflow-y: auto;
}
.brand { display: flex; flex-direction: column; gap: 2px; padding: 0 6px; }
/* Matches nav.navgroup's own width exactly, not just .brand's narrower content box —
   .navgroup has no horizontal padding of its own (.navitem's padding is internal to each
   item), while .brand does (padding: 0 6px above), so counteract that 6px each side. */
.brand-logo { width: calc(100% + 12px); height: auto; display: block; margin: 0 -6px 4px; }
.brand-logo-dark { display: none; }
.brand-mark { display: flex; align-items: baseline; justify-content: center; gap: 6px; }
.brand-mark .dot { color: var(--accent); font-family: var(--font-mono); font-weight: 700; }
.brand-name { font-weight: 700; font-size: 19px; letter-spacing: -.01em; }

.legacy-toggle {
  display: flex; align-items: center; justify-content: space-between;
  padding: 8px 10px; border-radius: 7px; font-size: 12.5px; color: var(--ink-2);
  border: 1px solid var(--border); background: var(--surface-2);
}
.switch { position: relative; width: 34px; height: 20px; flex: none; }
.switch input { opacity: 0; width: 100%; height: 100%; margin: 0; cursor: pointer; position: absolute; inset: 0; z-index: 1; }
.switch .track { position: absolute; inset: 0; background: var(--border-strong); border-radius: 999px; transition: background .15s ease; }
.switch .thumb { position: absolute; top: 2px; left: 2px; width: 16px; height: 16px; border-radius: 50%; background: var(--surface); box-shadow: var(--shadow); transition: transform .15s ease; }
.switch input:checked + .track { background: var(--accent); }
.switch input:checked + .track .thumb { transform: translateX(14px); }

nav.navgroup { display: flex; flex-direction: column; gap: 2px; }
.nav-section-label {
  margin: 10px 10px 2px; padding-top: 8px; border-top: 1px solid var(--border);
  font-size: 10.5px; font-weight: 600; letter-spacing: .06em; text-transform: uppercase;
  color: var(--muted);
}
.navitem {
  display: flex; align-items: center; gap: 10px; padding: 8px 10px; border-radius: 7px;
  color: var(--ink-2); text-decoration: none; cursor: pointer; border: none; background: none;
  width: 100%; text-align: left; font-size: 13.5px;
}
.navitem:hover { background: var(--surface-2); color: var(--ink); }
.navitem[aria-current="true"] { background: var(--accent-tint); color: var(--accent-ink); font-weight: 600; }
.navitem .ic { width: 16px; text-align: center; font-family: var(--font-mono); font-size: 12px; color: var(--muted); }
.navitem[aria-current="true"] .ic { color: var(--accent); }
.sidebar-foot { margin-top: auto; padding: 10px 10px 2px; font-size: 11px; color: var(--muted); border-top: 1px solid var(--border); display: flex; flex-direction: column; gap: 8px; align-items: flex-start; }
.sidebar-foot .row-link { display: inline; }
button.sidebar-feedback-btn { background: none; border: none; padding: 0; margin: 0; cursor: pointer; font: inherit; text-align: left; }

.main { flex: 1; min-width: 0; display: flex; flex-direction: column; }
.topbar {
  position: sticky; top: 0; z-index: 5; display: flex; align-items: center; gap: 14px;
  padding: 12px 24px; background: color-mix(in oklab, var(--page) 88%, transparent);
  backdrop-filter: blur(6px); border-bottom: 1px solid var(--border);
}
.search-wrap { position: relative; flex: 1; max-width: 520px; }
.search-wrap .ic { position: absolute; left: 11px; top: 50%; transform: translateY(-50%); color: var(--muted); font-family: var(--font-mono); font-size: 13px; pointer-events: none; }
#globalSearch {
  width: 100%; padding: 8px 12px 8px 30px; border-radius: 8px;
  border: 1px solid var(--border-strong); background: var(--surface); color: var(--ink); font-size: 13.5px;
}
#globalSearch::placeholder { color: var(--muted); }
.topbar-right { margin-left: auto; display: flex; align-items: center; gap: 10px; }
.who {
  display: flex; align-items: center; gap: 8px; font-size: 12.5px; color: var(--ink-2);
  border: 1px solid var(--border); border-radius: 20px; padding: 4px 10px 4px 4px; background: var(--surface);
}
.who .av {
  width: 20px; height: 20px; border-radius: 50%; background: var(--accent); color: #fff;
  font-family: var(--font-mono); font-size: 10px; display: flex; align-items: center; justify-content: center; font-weight: 700;
}

.view { padding: 20px 24px 48px; }
.view-head { margin-bottom: 18px; display: flex; align-items: flex-start; justify-content: space-between; gap: 14px; }
.view-head-actions { display: flex; align-items: center; gap: 8px; flex: none; }
#routerStatus .view-head-actions { margin-top: 14px; }
.view-title { font-size: 19px; font-weight: 700; letter-spacing: -.01em; margin: 0 0 3px; }
.view-desc { color: var(--ink-2); font-size: 13px; margin: 0; max-width: 62ch; }
.legacy-empty { color: var(--muted); font-size: 12.5px; padding: 14px 12px !important; }

/* ---- dashboard stat tiles ---- */
.stat-grid { display: grid; grid-template-columns: repeat(auto-fit, minmax(150px, 1fr)); gap: 10px; margin-bottom: 20px; }
.stat-tile { background: var(--surface); border: 1px solid var(--border); border-radius: 10px; padding: 13px 14px; text-decoration: none; display: block; }
.stat-tile:hover { border-color: var(--accent); }
.stat-tile .stat-label { font-size: 11.5px; color: var(--muted); margin-bottom: 6px; }
.stat-tile .stat-value { font-family: var(--font-mono); font-variant-numeric: tabular-nums; font-size: 22px; font-weight: 600; letter-spacing: -.01em; color: var(--ink); }
.stat-tile .stat-value.warn { color: var(--warning); }
.stat-tile .stat-value.crit { color: var(--critical); }
.stat-tile .stat-sub { font-size: 11px; color: var(--muted); margin-top: 3px; }
.stat-tile .stat-value-total { font-size: 15px; font-weight: 500; color: var(--muted); }

/* ---- dashboard "Recent activity" card + full Activity page (ported from mockup1) ---- */
.dash-grid { display: grid; grid-template-columns: 1fr; gap: 14px; align-items: start; margin-top: 4px; }
.card { background: var(--surface); border: 1px solid var(--border); border-radius: 10px; padding: 16px; }
.card h3 { margin: 0 0 3px; font-size: 13.5px; font-weight: 700; }
.card .card-sub { font-size: 11.5px; color: var(--muted); margin: 0 0 12px; }

/* ---- Mikrotik config builder — progressive wizard ---- */
.wizard-step { margin-bottom: 14px; transition: opacity .15s ease; }
.wizard-step[data-locked] { opacity: .5; }
.wizard-step .field-row { margin-bottom: 10px; }
.wizard-step .field-row:last-child { margin-bottom: 0; }
.wizard-step .field-row label input[type="checkbox"] { margin-right: 6px; vertical-align: middle; }

.activity-list { list-style: none; margin: 0; padding: 0; display: flex; flex-direction: column; }
.activity-item { display: flex; gap: 10px; padding: 9px 0; border-bottom: 1px solid var(--border); font-size: 12.5px; }
.activity-item:last-child { border-bottom: none; padding-bottom: 0; }
.activity-item .a-dot { width: 7px; height: 7px; border-radius: 50%; margin-top: 5px; flex: none; }
.activity-item .a-body { min-width: 0; }
.activity-item .a-desc { color: var(--ink); }
.activity-item .a-desc b { font-weight: 600; }
.activity-item .a-meta { color: var(--muted); font-family: var(--font-mono); font-size: 10.5px; margin-top: 2px; }
.activity-item .a-changes { margin-top: 5px; font-size: 11.5px; }
.activity-item .a-changes summary { cursor: pointer; color: var(--accent); }
.activity-item .a-changes ul { margin: 6px 0 0; padding-left: 16px; color: var(--ink-2); }
/* Line-level diff for multi-line changed values (e.g. YAML configs) — a plain "old → new"
   one-liner is unreadable once a value spans many lines, especially when only one line
   inside a large block actually changed. Each line is its own block-level span so
   added/removed/unchanged lines stack correctly regardless of wrapping. */
.a-diff { margin: 4px 0 0; padding: 6px 8px; background: var(--surface-2); border-radius: 4px;
  font-family: var(--font-mono); font-size: 11px; line-height: 1.6; white-space: pre-wrap;
  word-break: break-word; max-height: 260px; overflow-y: auto; }
.a-diff span { display: block; padding: 0 4px; border-radius: 2px; }
.a-diff-same { color: var(--muted); }
.a-diff-added { background: var(--good-tint); color: var(--good); }
.a-diff-removed { background: var(--critical-tint); color: var(--critical); text-decoration: line-through; }
.activity-list-full .activity-item { padding: 11px 0; }

.activity-filters {
  display: flex; flex-wrap: wrap; gap: 12px; align-items: flex-end;
  background: var(--surface); border: 1px solid var(--border); border-radius: 10px;
  padding: 12px 14px; margin-bottom: 16px;
}
.activity-filters .field-row { gap: 4px; min-width: 130px; }

/* ---- ip / empty placeholders ---- */
.ip-none { font-family: var(--font-mono); font-size: 11px; color: var(--muted); }

/* ---- generic add/edit/delete modal ---- */
.modal-overlay { position: fixed; inset: 0; background: rgba(16,32,46,.45); display: flex; align-items: center; justify-content: center; z-index: 100; padding: 20px; }
.modal-dialog { width: 100%; max-width: 460px; max-height: calc(100vh - 40px); overflow-y: auto; background: var(--surface); border: 1px solid var(--border); border-radius: 12px; box-shadow: var(--shadow); }
/* Wider + tall variant for forms with a large free-text field (currently just Config) — other
   domains' modals stay compact since they're mostly short inputs/selects. Rather than sizing
   to content, this variant actively fills the browser window (height: calc(100vh - 40px), not
   just max-height) and lays out as a flex column so the header/footer stay fixed while only
   the body scrolls — and, via the :last-of-type rule below, the config field-row/textarea
   itself grows to fill whatever vertical room that leaves, so resizing the browser window
   resizes the editing area instead of leaving it a fixed small box. */
.modal-dialog.modal-wide {
  max-width: 860px; height: calc(100vh - 40px); display: flex; flex-direction: column; overflow-y: hidden;
}
.modal-dialog.modal-wide .modal-header { flex-shrink: 0; }
/* .modal-body/.modal-footer are inside the <form> (so the submit button works), not direct
   children of .modal-dialog — the form itself needs to become the flex container passing
   remaining height down to them, or flex:1 on .modal-body alone does nothing. */
.modal-dialog.modal-wide form { display: flex; flex-direction: column; flex: 1; min-height: 0; }
.modal-dialog.modal-wide .modal-footer { flex-shrink: 0; }
.modal-dialog.modal-wide .modal-body { flex: 1; min-height: 0; overflow-y: auto; }
/* Config's form field order is fixed (ConfigPortalForm.Meta.fields = ["name", "config"]) —
   the config textarea is always the last field-row, so this targets it without needing a
   per-field hook in the generic _form_fields.html loop. */
.modal-dialog.modal-wide .field-row:last-of-type { flex: 1; min-height: 0; display: flex; flex-direction: column; }
.modal-dialog.modal-wide .field-row:last-of-type textarea { flex: 1; min-height: 0; height: auto; }
/* AI bug-reporter modal deliberately has no <form> (a plain form-submit would be intercepted by
   search.js's generic delegated #activeModal form submit handler, which this multi-turn/JSON
   flow doesn't want) — this plain-div equivalent of the ".modal-wide form" flex-fill rule above
   lets it reuse the same fills-the-window layout without a <form> wrapper. */
.modal-dialog.modal-wide .modal-flex-shell { display: flex; flex-direction: column; flex: 1; min-height: 0; }
/* Compound selector (.modal-body.ai-chat-body), not just .ai-chat-body alone — matches this
   file's existing .modal-dialog.modal-split .modal-body pattern below. A bare single-class
   .ai-chat-body rule sitting earlier in this file than the base .modal-body rule would lose to
   it on source order at equal specificity (flex-direction: column would win), collapsing the
   two-column layout into a single stacked column — this was hit for real and looked like
   "offset" fields (the draft column's border-left/padding-left still applying, just no longer
   sitting next to anything). Named this way it wins on specificity regardless of order. */
.modal-body.ai-chat-body { flex-direction: row; align-items: stretch; gap: 18px; }
.ai-chat-transcript-col { flex: 1.3 1 0; min-width: 0; display: flex; flex-direction: column; gap: 10px; min-height: 0; }
.ai-chat-transcript { flex: 1; min-height: 0; overflow-y: auto; display: flex; flex-direction: column; gap: 8px; padding: 4px 2px; }
.ai-chat-bubble { max-width: 85%; padding: 8px 11px; border-radius: 9px; font-size: 13px; line-height: 1.4; }
.ai-chat-bubble.role-user { align-self: flex-end; background: var(--accent); color: #fff; }
.ai-chat-bubble.role-assistant { align-self: flex-start; background: var(--surface-3); color: var(--ink); }
.ai-chat-draft-col { flex: 1 1 0; min-width: 0; display: flex; flex-direction: column; gap: 8px; border-left: 1px solid var(--border); padding-left: 18px; }
.ai-chat-draft-col .field-row:last-of-type { flex: 1; min-height: 0; display: flex; flex-direction: column; }
.ai-chat-draft-col .field-row:last-of-type textarea { flex: 1; min-height: 60px; }
/* One compact line per tool call, rendered inline in the transcript stream itself (not a
   separate status area) — matches Loom's own reference UI: a 🔍-prefixed monospace rendering
   of the actual tool name + arguments, interleaved chronologically with narration bubbles. */
.ai-tool-call-line { align-self: flex-start; max-width: 92%; font-family: var(--font-mono); font-size: 11.5px; color: var(--ink-2); padding: 2px 4px; }

.ai-chat-status { font-size: 11.5px; color: var(--ink-2); }
.ai-chat-error { font-size: 11.5px; color: var(--critical); }
/* Persistent, not a one-off toast — stays visible for as long as budget_exhausted_notified is
   true on the conversation, alongside Send being disabled (see ai_bug_reporter.js). Reuses the
   existing --warning tokens rather than one-off colors, so it behaves correctly in dark mode
   the same way every other warning-toned element in this app already does. */
.ai-chat-budget-banner { font-size: 12px; color: var(--warning); background: var(--warning-tint); border: 1px solid var(--border-strong); border-radius: 6px; padding: 6px 10px; }
.ai-chat-input-row { flex-shrink: 0; display: flex; gap: 8px; align-items: flex-end; }
.ai-chat-input-row textarea { flex: 1; resize: vertical; min-height: 40px; }
/* Two-column variant for forms whose read-only "Polled data" section got long enough to make
   the default single-column modal scroll a lot (Network edit) — widens the dialog and lays the
   two sections side by side instead of stacked, so both fit without much taller a dialog.
   Doesn't reuse .modal-wide (that variant fills the viewport height for one growing textarea;
   this one just needs to be wider, height still sizes to content via the base rule above). */
.modal-dialog.modal-split { max-width: 760px; }
.modal-dialog.modal-split .modal-body { flex-direction: row; align-items: flex-start; gap: 28px; }
.modal-dialog.modal-split .modal-col { flex: 1 1 0; min-width: 0; display: flex; flex-direction: column; gap: 12px; }
.modal-header { display: flex; align-items: center; justify-content: space-between; gap: 10px; padding: 16px 18px; border-bottom: 1px solid var(--border); }
.modal-title { font-size: 15px; font-weight: 700; margin: 0; }
.modal-close { border: none; background: none; color: var(--muted); cursor: pointer; font-size: 16px; line-height: 1; padding: 4px; border-radius: 5px; }
.modal-close:hover { background: var(--surface-3); color: var(--ink); }
.modal-body { padding: 16px 18px; display: flex; flex-direction: column; gap: 12px; }
.modal-hint { font-size: 11.5px; color: var(--ink-2); background: var(--accent-tint); border-radius: 7px; padding: 8px 10px; margin: 0; }
.modal-section-title {
  font-size: 12px; font-weight: 700; text-transform: uppercase; letter-spacing: .04em; color: var(--ink-2);
  margin: 4px 0 0; padding-top: 12px; border-top: 1px solid var(--border);
}
.modal-section-title:first-child { margin-top: 0; padding-top: 0; border-top: none; }
.field-row { display: flex; flex-direction: column; gap: 4px; }
.field-row label { font-family: var(--font-mono); font-size: 10.5px; text-transform: uppercase; letter-spacing: .04em; color: var(--muted); }
/* Read-only field-rows (Router identity, Poll status, WAN IP, etc.) display their value in a
   plain <p> — resets the browser's default ~1em <p> margin, which was stacking on top of the
   .field-row gap above and making the label-to-value spacing much bigger than the 4px every
   other field-row uses. */
.field-row p { margin: 0; }
.field-row input[type="text"], .field-row input[type="email"], .field-row input[type="number"], .field-row input[type="password"], .field-row select, .field-row textarea {
  font-size: 13px; padding: 7px 9px; border-radius: 7px; border: 1px solid var(--border-strong); background: var(--surface); color: var(--ink);
}
.field-row textarea { font-family: var(--font-mono); font-size: 12px; resize: vertical; min-height: 70px; width: 100%; box-sizing: border-box; }
.field-row input:disabled, .field-row select:disabled { color: var(--muted); background: var(--surface-2); cursor: not-allowed; }
.field-row input:read-only { color: var(--muted); background: var(--surface-2); cursor: default; }
.field-row.wiz-locked { opacity: .55; }
.field-msg { font-size: 10.5px; color: var(--warning); margin: 0; }
.field-error { font-size: 10.5px; color: var(--critical); margin: 0; }
.modal-footer { display: flex; align-items: center; justify-content: flex-end; gap: 8px; padding: 14px 18px; border-top: 1px solid var(--border); }
/* Pins a footer button to the far left against the footer's flex-end packing — used to keep
   a consequential action (e.g. Feedback's "Close request") away from the right-hand slot
   habit puts a plain dismiss-click in. */
.modal-footer-left { margin-right: auto; }
.btn-cancel { font-size: 12.5px; border: 1px solid var(--border-strong); background: var(--surface); color: var(--ink-2); border-radius: 7px; padding: 7px 14px; cursor: pointer; }
.btn-cancel:hover { background: var(--surface-3); color: var(--ink); }
.btn-primary { font-size: 12.5px; border: 1px solid var(--accent); background: var(--accent); color: #fff; border-radius: 7px; padding: 7px 14px; cursor: pointer; font-weight: 600; }
.btn-primary:hover { background: var(--accent-hover); border-color: var(--accent-hover); }
.btn-danger { font-size: 12.5px; border: 1px solid var(--critical); background: var(--critical); color: #fff; border-radius: 7px; padding: 7px 14px; cursor: pointer; font-weight: 600; }
.btn-danger:hover { filter: brightness(0.9); }
/* alpha2: Edit/View links in list tables styled as buttons, matching the Disable/Delete <button>s beside them. */
a.btn-cancel, a.btn-primary, a.btn-danger { display: inline-block; text-decoration: none; line-height: normal; vertical-align: middle; }
a.btn-primary, a.btn-danger { color: #fff; }

.toolbar-btn { font-size: 11px; border: 1px solid var(--border-strong); background: var(--surface); color: var(--ink-2); border-radius: 6px; padding: 5px 11px; cursor: pointer; white-space: nowrap; }
.toolbar-btn:hover:not(:disabled) { background: var(--surface-3); color: var(--ink); }
.toolbar-btn.primary { border-color: var(--accent); background: var(--accent-tint); color: var(--accent-ink); }
.toolbar-btn.primary:hover:not(:disabled) { background: var(--accent); color: #fff; }
.edit-icon-btn { border: 1px solid var(--border-strong); background: var(--surface); color: var(--ink-2); border-radius: 6px; padding: 4px 6px; cursor: pointer; display: inline-flex; margin-right: 4px; }
.edit-icon-btn:hover { background: var(--accent-tint); color: var(--accent); border-color: var(--accent); }
.row-link { color: var(--ink); text-decoration: none; font-weight: 600; }
.row-link:hover { color: var(--accent); text-decoration: underline; }

/* ---- resizable / draggable columns (Devices) ---- */
.dcol-wrap { overflow-x: auto; }
table.dcol-table { width: auto; border-collapse: collapse; table-layout: fixed; }
table.dcol-table th {
  position: relative; text-align: left; font-size: 10px; text-transform: uppercase; letter-spacing: .04em;
  color: var(--muted); font-weight: 600; padding: 5px 10px 5px 8px;
  border-bottom: 1px solid var(--border); border-right: 1px solid var(--border-strong);
  white-space: nowrap; overflow: hidden; text-overflow: ellipsis; cursor: grab;
}
table.dcol-table th:active { cursor: grabbing; }
table.dcol-table th:last-child { border-right: none; }
table.dcol-table th.col-dragover { background: var(--accent-tint); }
table.dcol-table td { padding: 6px 8px; border-bottom: 1px solid var(--border); border-right: 1px solid var(--border); font-size: 12px; vertical-align: top; overflow-wrap: anywhere; }
table.dcol-table td:last-child { border-right: none; }
table.dcol-table tr:last-child > td { border-bottom: none; }
.col-resizer { position: absolute; right: -3px; top: 0; bottom: 0; width: 7px; cursor: col-resize; z-index: 2; touch-action: none; }
.col-resizer:hover, .col-resizer.resizing { background: var(--accent); opacity: .55; }
.dev-mac { font-family: var(--font-mono); font-size: 11px; }
.dev-model { font-size: 11.5px; color: var(--ink-2); }
.dev-seen { font-family: var(--font-mono); font-size: 10.5px; color: var(--muted); white-space: nowrap; }

/* ---- legacy flat pages (Tenants/Networks/DIDs/Configs) — same chrome as Devices ---- */
.flat-table-wrap { overflow-y: hidden; }
table.flat-table { width: auto; border-collapse: collapse; table-layout: fixed; min-width: 640px; }
table.flat-table th {
  position: relative; text-align: left; font-size: 10px; text-transform: uppercase; letter-spacing: .04em;
  color: var(--muted); font-weight: 600; padding: 5px 10px 5px 8px; border-bottom: 1px solid var(--border);
  border-right: 1px solid var(--border-strong); white-space: nowrap; overflow: hidden; text-overflow: ellipsis; cursor: grab;
}
table.flat-table th:active { cursor: grabbing; }
table.flat-table th.col-dragover { background: var(--accent-tint); }
table.flat-table th:last-child { border-right: none; }
table.flat-table td { padding: 6px 8px; border-bottom: 1px solid var(--border); border-right: 1px solid var(--border); font-size: 12px; vertical-align: top; overflow-wrap: anywhere; }
table.flat-table td:last-child { border-right: none; }
table.flat-table tr:last-child > td { border-bottom: none; }
.siloed-note { font-size: 11px; color: var(--muted); border: 1px dashed var(--border-strong); border-radius: 7px; padding: 7px 10px; margin-bottom: 14px; max-width: 66ch; }
td.hv-cell { width: 34px; text-align: center; }
.hv-link { display: inline-flex; opacity: .8; }
.hv-link:hover { opacity: 1; }
.hv-link img { display: block; border-radius: 3px; }

/* ---- Tenant password reveal/copy (tenants.view_tenant_password only). No white-space:nowrap
   here deliberately — a revealed value can run longer than this column's narrow default
   width, and with table-layout:fixed an overflowing nowrap cell can visually bleed into its
   neighbor instead of wrapping; the table-wide overflow-wrap:anywhere rule (table.flat-table
   td) already wraps it safely onto a second line within the cell instead. Column is
   resizable like any other via tablecols.js if staff want it wider. ---- */
td.pw-cell { width: 34px; }
.pw-toggle {
  background: none; border: 1px solid transparent; color: var(--muted); cursor: pointer;
  padding: 3px; border-radius: 5px; display: inline-flex; align-items: center; vertical-align: middle;
}
.shrink-col { width: 1%; white-space: nowrap; } /* narrow icon column that only grows while a value is revealed (L2TP PW) */
.pw-toggle:hover { background: var(--surface-3); color: var(--ink); }
.pw-toggle[aria-pressed="true"] { color: var(--accent); }
.pw-value { margin-left: 6px; font-size: 11.5px; }
/* Driven by a class, not the [hidden] attribute — some browsers don't apply the UA
   [hidden]{display:none} rule to inline <svg>, which left both icons visibly stacked. */
.pw-icon-closed { display: none; }
.pw-toggle[aria-pressed="true"] .pw-icon-open { display: none; }
.pw-toggle[aria-pressed="true"] .pw-icon-closed { display: inline; }

/* ---- click-to-sort column headers — applies to both the .flat-table and .dcol-table
   systems (Devices), same as tablecols.js's own column-key logic being table-system-
   agnostic. A quick click sorts; th's existing drag-reorder/resize (tablecols.js) still
   works since a real drag doesn't fire a click event. ---- */
th[data-sort-field]:hover { color: var(--ink); }
th.sort-asc::after, th.sort-desc::after { margin-left: 4px; font-size: 9px; display: inline-block; }
th.sort-asc { color: var(--accent); }
th.sort-asc::after { content: "\25B2"; }
th.sort-desc { color: var(--accent); }
th.sort-desc::after { content: "\25BC"; }

/* ---- Directory tree — twisty column + expand/collapse detail rows ---- */
th.twisty-th { width: 34px; cursor: default; }
td.twisty-cell { width: 34px; text-align: center; padding: 3px !important; }
.twisty-btn {
  background: var(--surface-2); border: 1px solid var(--border-strong); cursor: pointer;
  width: 24px; height: 24px; border-radius: 6px; line-height: 1;
  display: inline-flex; align-items: center; justify-content: center;
}
.twisty-btn:hover { background: var(--accent-tint); border-color: var(--accent); }
/* Plus/minus rather than a rotating triangle — reads as "this row expands/collapses"
   without relying on a rotation animation to carry the meaning. Pure CSS content swap keyed
   off the button's own aria-expanded state, which tree.js already toggles — no JS change
   needed to drive this. */
.twisty { display: inline-block; width: 12px; height: 12px; position: relative; color: var(--muted); }
.twisty::before { content: "+"; position: absolute; inset: 0; display: flex; align-items: center; justify-content: center; font-size: 15px; font-weight: 700; line-height: 1; }
.twisty-btn[aria-expanded="true"] .twisty::before { content: "\2212"; }
.twisty-btn[aria-expanded="true"] .twisty { color: var(--accent); }
tr.detail-row > td { background: var(--surface-2); padding: 10px 10px 14px 38px !important; border-right: none !important; position: relative; }
/* Tree connector lines — a vertical trunk under the parent row's twisty (twisty-cell is 34px
   wide, so its icon center sits ~17px in) running down to each nested section, with a small
   horizontal elbow into that section's heading. Purely illustrative (drawn with CSS
   pseudo-elements against the existing detail-row/detail-section structure) — no markup or
   JS change needed, and it automatically covers every nesting level that reuses these same
   classes (Tenant->Networks/DIDs, Network->Devices). */
tr.detail-row > td::before {
  content: ""; position: absolute; top: 0; bottom: 14px; left: 17px;
  border-left: 1px dashed var(--border-strong); pointer-events: none;
}
.detail-section { margin-bottom: 14px; position: relative; }
.detail-section:last-child { margin-bottom: 0; }
.detail-section::before {
  content: ""; position: absolute; top: 9px; left: -21px; width: 21px;
  border-top: 1px dashed var(--border-strong); pointer-events: none;
}
.detail-section h4 { margin: 0 0 6px; font-size: 11px; text-transform: uppercase; letter-spacing: .04em; color: var(--muted); font-weight: 600; }
.detail-section .flat-table-wrap:empty::before { content: "Loading…"; font-size: 11.5px; color: var(--muted); }

.pager { display: flex; align-items: center; gap: 12px; margin-top: 12px; font-size: 12px; }
.pager a { color: var(--accent); text-decoration: none; }
.pager a:hover { text-decoration: underline; }
.pager-disabled { color: var(--muted); }

/* ---- notification badge (generic small count pill, e.g. unseen-row markers) ---- */
.badge {
  display: inline-flex; align-items: center; justify-content: center; min-width: 16px; height: 16px;
  padding: 0 4px; border-radius: 999px; background: var(--critical); color: #fff;
  font-size: 10px; font-weight: 700; font-family: var(--font-mono); line-height: 1;
}

/* ---- global notification icon (topbar, near username) ---- */
.notif-icon {
  position: relative; display: inline-flex; align-items: center; justify-content: center;
  width: 32px; height: 32px; border-radius: 50%; text-decoration: none;
  color: var(--muted); font-size: 15px; background: var(--surface); border: 1px solid var(--border);
  flex: none;
}
.notif-icon:hover { background: var(--surface-2); color: var(--ink-2); }
.notif-icon.has-notifications { color: var(--warning); border-color: var(--warning); background: var(--warning-tint); }
.notif-icon .notif-count {
  position: absolute; top: -3px; right: -3px; min-width: 15px; height: 15px; padding: 0 3px;
  border-radius: 999px; background: var(--critical); color: #fff; font-size: 9px; font-weight: 700;
  display: flex; align-items: center; justify-content: center; font-family: var(--font-mono);
  line-height: 1; border: 1.5px solid var(--page);
}
/* ---- Notifications page (portal/notifications.html) ---- */
.notif-domain {
  font-family: var(--font-mono); font-size: 9.5px; text-transform: uppercase; letter-spacing: .05em;
  color: var(--accent); background: var(--accent-tint); border-radius: 4px; padding: 1px 5px; margin-right: 4px;
}
.notif-link { background: none; border: none; padding: 0; font: inherit; color: var(--ink); font-weight: 600; cursor: pointer; }
.notif-link:hover { color: var(--accent); text-decoration: underline; }

/* ---- status pills (Feedback) ---- */
.pill { display: inline-block; padding: 2px 8px; border-radius: 999px; font-size: 11px; font-weight: 600; }
.pill-new { background: var(--warning-tint); color: var(--warning); }
.pill-in_progress { background: var(--accent-tint); color: var(--accent-ink); }
.pill-accepted { background: var(--accent-tint); color: var(--accent-ink); }
.pill-denied { background: var(--critical-tint); color: var(--critical); }
.pill-resolved { background: var(--good-tint); color: var(--good); }
.pill-closed { background: var(--surface-3); color: var(--muted); }
/* Router poll status (see the router polling plan) */
.pill-never_polled { background: var(--surface-3); color: var(--muted); }
.pill-online { background: var(--good-tint); color: var(--good); }
.pill-stale { background: var(--warning-tint); color: var(--warning); }
.pill-unreachable { background: var(--warning-tint); color: var(--warning); }
.pill-offline { background: var(--critical-tint); color: var(--critical); }

/* ---- Active/Archive tabs (My Feedback) ---- */
.tabs { display: flex; gap: 4px; border-bottom: 1px solid var(--border); margin: 4px 0 14px; }
.tab {
  padding: 7px 12px; font-size: 12.5px; font-weight: 600; color: var(--muted);
  border-bottom: 2px solid transparent; margin-bottom: -1px;
}
.tab:hover { color: var(--ink); }
.tab-active { color: var(--accent); border-bottom-color: var(--accent); }

/* ---- My Feedback detail ---- */
.detail-meta { display: flex; align-items: center; gap: 8px; }
.detail-meta-text { font-size: 12px; color: var(--muted); }
.section-label {
  font-family: var(--font-mono); text-transform: uppercase; letter-spacing: .06em;
  font-size: 10px; color: var(--muted); font-weight: 600; margin: 4px 0 8px;
}
.feedback-notes { list-style: none; margin: 0; padding: 0; display: flex; flex-direction: column; gap: 10px; }
.feedback-note {
  border-left: 3px solid var(--accent); background: var(--surface-2); border-radius: 0 8px 8px 0;
  padding: 8px 10px; font-size: 12.5px;
}
.feedback-note .fn-meta { font-size: 10.5px; color: var(--muted); font-family: var(--font-mono); margin-bottom: 4px; }
/* Add-a-comment form embedded directly under the thread in _feedback_detail.html. Deliberately
   no align-items:flex-start here — that would shrink the flex column's children (the
   textarea's .field-row) to their intrinsic content width, which is exactly what was
   fighting the width:100% below (a <textarea>'s intrinsic width defaults to a plain HTML
   `cols` attribute, ~20-40 chars, not the modal's actual width). Default align-items:stretch
   lets the field-row/textarea fill the full modal width; align-self below keeps just the
   submit button from also stretching to that same full width. */
.modal-body form:has(textarea[name="body"]) { display: flex; flex-direction: column; gap: 8px; }
.modal-body form:has(textarea[name="body"]) textarea { width: 100%; box-sizing: border-box; }
.modal-body form:has(textarea[name="body"]) button[type="submit"] { align-self: flex-start; }

/* ---- server fleet (Dashboard card + webshell entry point) ---- */
.server-strip { display: flex; flex-wrap: wrap; gap: 6px; }
.server-chip {
  display: flex; align-items: center; gap: 5px;
  font-family: var(--font-mono); font-size: 11px;
  border: 1px solid var(--border); border-radius: 6px; padding: 4px 7px;
  background: var(--surface-2);
}
.server-chip .d { width: 6px; height: 6px; border-radius: 50%; flex: none; }
a.server-chip { color: var(--ink); text-decoration: none; cursor: pointer; }
a.server-chip:hover { border-color: var(--accent); background: var(--accent-tint); }
button.server-chip { border: 1px solid var(--border); font: inherit; cursor: default; }

.webshell-terminal {
  background: #000; border-radius: 8px; padding: 10px;
  height: 420px; min-height: 200px; max-height: 90vh;
  /* overflow:hidden (not auto) deliberately — xterm.js already renders its own internal
     scrollbar (.xterm-viewport) for terminal scrollback. Setting overflow:auto here too
     produced two competing vertical scrollbars. `resize` still only needs overflow != visible
     to show its drag handle — hidden satisfies that without adding a second scrollbar. */
  resize: vertical; overflow: hidden;
}
.webshell-terminal .xterm { padding: 2px; height: 100%; }

/* ---- login page ---- */
.login-body { display: flex; align-items: center; justify-content: center; min-height: 100vh; background: var(--page); }
.login-card { width: 100%; max-width: 340px; background: var(--surface); border: 1px solid var(--border); border-radius: 12px; box-shadow: var(--shadow); padding: 24px; display: flex; flex-direction: column; gap: 10px; }
.login-card .field-row + .field-row { margin-top: 14px; }
.login-submit { width: 100%; margin-top: 4px; }
.login-card form .login-submit { margin-top: 10px; }
.login-divider { text-align: center; color: var(--text-muted, #888); margin: -2px 0; font-size: 0.85em; }
.google-btn { display: flex; align-items: center; justify-content: center; gap: 8px; text-decoration: none; }
.google-btn .google-icon { flex: none; display: block; background: #fff; border-radius: 2px; padding: 1px; }

/* ---- AI-assisted Feedback intake ---- */
/* Full page (not a modal — alpha2 has no modal system), reusing the same two-column
   transcript+draft layout tenant-poc's own modal version uses. */
.ai-chat-page { display: flex; flex-direction: column; gap: 12px; }
.ai-chat-body { display: flex; flex-direction: row; align-items: stretch; gap: 18px; height: 60vh; }
.ai-chat-transcript-col { flex: 1.3 1 0; min-width: 0; display: flex; flex-direction: column; gap: 10px; min-height: 0; }
.ai-chat-transcript { flex: 1; min-height: 0; overflow-y: auto; display: flex; flex-direction: column; gap: 8px; padding: 4px 2px; }
.ai-chat-bubble { max-width: 85%; padding: 8px 11px; border-radius: 9px; font-size: 13px; line-height: 1.4; }
.ai-chat-bubble.role-user { align-self: flex-end; background: var(--accent); color: #fff; }
.ai-chat-bubble.role-assistant { align-self: flex-start; background: var(--surface-3); color: var(--ink); }
.ai-chat-draft-col { flex: 1 1 0; min-width: 0; display: flex; flex-direction: column; gap: 8px; border-left: 1px solid var(--border); padding-left: 18px; overflow-y: auto; }
.ai-chat-draft-col .field-row:last-of-type { flex: 1; min-height: 0; display: flex; flex-direction: column; }
.ai-chat-draft-col .field-row:last-of-type textarea { flex: 1; min-height: 120px; }
/* One compact line per tool call, rendered inline in the transcript stream itself — a
   🔍-prefixed monospace rendering of the actual tool name + arguments, interleaved
   chronologically with narration bubbles. */
.ai-tool-call-line { align-self: flex-start; max-width: 92%; font-family: var(--font-mono); font-size: 11.5px; color: var(--ink-2); padding: 2px 4px; }
.ai-chat-status { font-size: 11.5px; color: var(--ink-2); }
.ai-chat-error { font-size: 11.5px; color: var(--critical); }
/* Persistent, not a one-off toast — stays visible for as long as budget_exhausted_notified is
   true on the conversation, alongside Send being disabled. */
.ai-chat-budget-banner { font-size: 12px; color: var(--warning); background: var(--warning-tint); border: 1px solid var(--border-strong); border-radius: 6px; padding: 6px 10px; }
.ai-chat-input-row { flex-shrink: 0; display: flex; gap: 8px; align-items: flex-end; }
.ai-chat-input-row textarea { flex: 1; resize: vertical; min-height: 40px; }
.ai-chat-actions { display: flex; justify-content: flex-end; gap: 8px; }
.ai-chat-actions .ai-chat-actions-left { margin-right: auto; }

/* ---- router poll status chip (networks list, network edit, Tools > Router Poll) ---- */
.poll-chip {
  display: inline-block; font-size: 11px; font-weight: 600; padding: 2px 8px; border-radius: 10px;
  background: var(--surface-3); color: var(--ink-2); white-space: nowrap;
}
.poll-chip.green { background: var(--good-tint); color: var(--good); }
.poll-chip.amber { background: var(--warning-tint); color: var(--warning); }
.poll-chip.red { background: var(--critical-tint); color: var(--critical); }
.poll-chip.alert { background: var(--critical); color: #fff; }
.poll-detail { font-size: 11px; color: var(--muted); margin-left: 6px; }
.poll-when { white-space: nowrap; font-size: 12px; }
.poll-route { white-space: nowrap; font-size: 12px; }
.poll-route[title], .poll-chip[title] { cursor: help; }
.poll-route.failed { color: var(--critical); font-weight: 600; }
.poll-route.failed::after { content: " — FAILED OVER"; font-family: var(--font-sans); }
.poll-when[title] { cursor: help; }
.poll-btn { font: inherit; font-size: 12px; cursor: pointer; margin-left: 6px; }
.poll-btn.btn-cancel { padding: 5px 12px; }
/* Poll is accent-tinted so it's easy to tell apart from the neutral Edit/View buttons. */
button.poll-btn.btn-cancel { border-color: var(--accent); background: var(--accent-tint); color: var(--accent-ink); }
button.poll-btn.btn-cancel:hover:not(:disabled) { background: var(--accent); border-color: var(--accent); color: #fff; }
.poll-btn:disabled { opacity: .55; cursor: default; }

/* Voice server chip (x-voice-server): which server a tenant runs on, links to the Directory's server view. */
.vs-chip { display: inline-block; padding: 1px 7px; border-radius: 999px; font-size: 11px; font-weight: 600; font-family: var(--font-mono);
  background: var(--accent-tint); color: var(--accent-ink); text-decoration: none; white-space: nowrap; }
.vs-chip:hover { text-decoration: underline; }
.vs-chip.vs-vpbx { background: var(--good-tint); color: var(--good); }
.vs-chip.vs-unknown { background: var(--warning-tint); color: var(--warning); }
.vs-backup { margin-left: 4px; font-size: 11px; color: var(--muted); font-family: var(--font-mono); white-space: nowrap; }

/* ---- dataset banner (which database this instance is connected to) ---- */
.dataset-banner { font-size: 11.5px; padding: 4px 24px; border-bottom: 1px solid var(--border); color: var(--ink-2); }
.dataset-banner strong { letter-spacing: .04em; }
.dataset-banner.dataset-dev { background: var(--warning-tint); color: var(--warning); }
.dataset-banner.dataset-prod { background: var(--surface-2, transparent); }

/* ---- dev dataset: edits to production tables are overwritten by the next import ---- */
.prod-table-warning { margin: 0 0 14px; padding: 8px 12px; border-radius: 8px; font-size: 12.5px;
  background: var(--warning-tint); color: var(--warning); border: 1px solid color-mix(in oklab, var(--warning) 40%, transparent); }
