:root {
  --bg: #0d1117;
  --bg2: #161b22;
  --bg3: #1c2330;
  --border: #2d3646;
  --fg: #e6edf3;
  --fg-dim: #8b98a9;
  --accent: #4493f8;
  /* The SIGNAL trio, the same one the dashboard opens with — the GUI and the
     board are one product and must not disagree about what red means. Held at
     nearly equal chroma so they read as one family, with critical carrying the
     most of it: on a dark ground the amber is unavoidably the brightest, and
     without that extra saturation on the red it would outrank it.
     The dashboard can be switched to a colour-blind-safe set in the settings;
     the GUI is read close up by somebody who can also read the words next to
     the colour, so it stays on the tuned one. */
  --ok: #33cf90;
  --warn: #ffb42e;
  --crit: #fb5062;
  --unk: #8b98a9;
  /* role identity colours — shared by the role badges and the topology map */
  --role-worker: #6fcf8f;
  --role-web: #79aef5;
  --role-client: #b492e8;
  --role-backup: #e08cae;
  --role-storage: #56c7c0;
  --role-discovery: #d2a24c;
  --role-notification: #b58cf0;
  --radius: 10px;
  /* One monospace stack for the whole GUI. Consolas alone left macOS on the
     generic default, which is Menlo - noticeably wider per character, and a
     column measured in pixels then cuts an IP address short. */
  --mono: ui-monospace, SFMono-Regular, "SF Mono", Menlo, Consolas,
    "Liberation Mono", "DejaVu Sans Mono", monospace;
  font-size: 15px;
}
* { box-sizing: border-box; margin: 0; padding: 0; }

/* themed scrollbars (page + inner scroll areas like the service list) */
* {
  scrollbar-width: thin;                       /* Firefox + Chrome 121+ */
  scrollbar-color: var(--border) transparent;
}
::-webkit-scrollbar { width: 10px; height: 10px; }   /* older WebKit/Blink */
::-webkit-scrollbar-track { background: transparent; }
::-webkit-scrollbar-thumb {
  background: var(--border);
  border-radius: 6px;
  border: 2px solid transparent;
  background-clip: padding-box;
}
::-webkit-scrollbar-thumb:hover { background: var(--fg-dim); background-clip: padding-box; }
::-webkit-scrollbar-corner { background: transparent; }
body {
  background: var(--bg);
  color: var(--fg);
  font-family: "Segoe UI", system-ui, -apple-system, sans-serif;
  min-height: 100vh;
}

/* Native controls — the popup of a select, the date picker, the spinners of
   a number field — are drawn by the browser, not by this stylesheet. This
   tells it which palette to use; without it a dark GUI opens white popups. */
body { color-scheme: dark; }
body.light { color-scheme: light; }

/* Base look of every form control and button. Everything below with a class
   of its own still wins (a class beats an element selector), so this is the
   floor: a control without a class is themed instead of falling back to the
   browser default. */
button {
  background: var(--bg3); color: var(--fg); border: 1px solid var(--border);
  border-radius: var(--radius); padding: 8px 14px; font-size: 14px;
  font-family: inherit; cursor: pointer;
}
button:hover { border-color: var(--accent); color: var(--accent); }
button:disabled { opacity: .5; cursor: default; }
select, textarea {
  background: var(--bg); color: var(--fg); border: 1px solid var(--border);
  border-radius: 8px; padding: 8px 10px; font-size: 14px; font-family: inherit;
}
select:focus, textarea:focus { outline: 1px solid var(--accent); }
option { background: var(--bg2); color: var(--fg); }

/* light theme */
body.light {
  --bg: #f2f4f8;
  --bg2: #ffffff;
  --bg3: #e8ecf2;
  --border: #d5dbe4;
  --fg: #1a2230;
  --fg-dim: #5f6b7d;
  --accent: #0969da;
  --ok: #0a7a52;
  --warn: #a06600;
  --crit: #a80f26;
  --unk: #5f6b7d;
  --role-worker: #2e8b57;
  --role-web: #3b78c4;
  --role-client: #7a5bb5;
  --role-backup: #a8436e;
  --role-storage: #1f7a75;
  --role-discovery: #8a5a00;
  --role-notification: #6f42c1;
}
/* on white a tint carries further, so it is lighter — the colour itself comes
   from the same token as the text on it */
body.light .pill.s0, body.light .badge.alive { background: color-mix(in srgb, var(--ok) 12%, transparent); }
body.light .pill.s1 { background: color-mix(in srgb, var(--warn) 12%, transparent); }
body.light .pill.s2, body.light .badge.dead { background: color-mix(in srgb, var(--crit) 12%, transparent); }
body.light .pill.s3 { background: color-mix(in srgb, var(--unk) 12%, transparent); }
body.light .pill.sp, body.light .badge.self { background: color-mix(in srgb, var(--accent) 12%, transparent); }
body.light #drawer { box-shadow: -12px 0 30px rgba(0,0,0,.15); }
body.light #toast { box-shadow: 0 8px 24px rgba(0,0,0,.18); }

/* header controls (language + theme) */
.hdr-controls { display: flex; align-items: center; gap: 6px; }
.hdr-controls select {
  background: var(--bg3); border: 1px solid var(--border); color: var(--fg);
  border-radius: 8px; padding: 5px 8px; font-size: 13px; cursor: pointer;
}
.hdr-controls .icon {
  background: var(--bg3); border: 1px solid var(--border); color: var(--fg);
  border-radius: 8px; padding: 5px 10px; font-size: 14px; cursor: pointer;
}
.hdr-controls .icon:hover { filter: brightness(1.15); }

/* header */
header {
  display: flex; align-items: center; gap: 24px;
  padding: 10px 20px;
  background: var(--bg2);
  border-bottom: 1px solid var(--border);
  position: sticky; top: 0; z-index: 10;
}
.brand { display: flex; align-items: baseline; gap: 8px; }
/* the drawn eye scales with the header, not with the font of the system */
.logo { width: 22px; height: 22px; flex: none; align-self: center; }
.brand h1 { font-size: 18px; letter-spacing: 3px; }
.brand .sub { color: var(--fg-dim); font-size: 12px; }
nav { display: flex; gap: 4px; flex: 1; }
.tab {
  background: none; border: none; color: var(--fg-dim);
  padding: 8px 14px; border-radius: var(--radius); cursor: pointer; font-size: 14px;
}
.tab:hover { color: var(--fg); background: var(--bg3); }
.tab.active { color: var(--fg); background: var(--bg3); }
.conn { display: flex; align-items: center; gap: 8px; color: var(--fg-dim); font-size: 13px; }
.dot { width: 10px; height: 10px; border-radius: 50%; background: var(--fg-dim); }
.dot.on { background: var(--ok); box-shadow: 0 0 6px var(--ok); }
.dot.off { background: var(--crit); }

/* A full HD screen has 1920 px and the check table is the widest thing here —
   the old 1400 cap left a third of it empty while names were being truncated.
   The cap stays, though: on an ultra-wide monitor a table pulled across 3000 px
   loses the eye between the name and the status. The topology map has its own,
   much smaller cap, so it does not scale up with this. */
main { padding: 20px; max-width: 1800px; margin: 0 auto; }
.tabpane { display: none; }
.tabpane.active { display: block; }

/* tiles */
.tiles { display: grid; grid-template-columns: repeat(auto-fit, minmax(150px, 1fr)); gap: 12px; margin-bottom: 18px; }
.tile {
  background: var(--bg2); border: 1px solid var(--border); border-radius: var(--radius);
  padding: 14px 18px;
}
.tile .num { font-size: 30px; font-weight: 600; }
.tile .lbl { color: var(--fg-dim); font-size: 13px; margin-top: 2px; }
.tile.ok .num { color: var(--ok); }
.tile.warning .num { color: var(--warn); }
.tile.critical .num { color: var(--crit); }
.tile.unknown .num, .tile.pending .num { color: var(--unk); }

/* toolbar */
.toolbar { display: flex; gap: 10px; margin-bottom: 12px; }
.toolbar input[type=search] {
  flex: 1; background: var(--bg2); border: 1px solid var(--border); border-radius: var(--radius);
  color: var(--fg); padding: 9px 14px; font-size: 14px;
}
button.primary {
  background: var(--accent); color: #fff; border: none; border-radius: var(--radius);
  padding: 9px 16px; font-size: 14px; cursor: pointer; font-weight: 600;
}
button.primary:hover { filter: brightness(1.15); }

/* table */
.tablewrap { background: var(--bg2); border: 1px solid var(--border); border-radius: var(--radius); overflow-x: auto; }
table { width: 100%; border-collapse: collapse; font-size: 14px; }
/* fixed layout keeps header and row columns aligned regardless of
   collapsed group rows (which span all columns) */
/* min-width covers the fixed columns (925 px) plus a readable share for the two
   flexible ones. Below it the wrapper scrolls instead of squeezing everything,
   which is what used to cut the type names short. */
#checks-table { table-layout: fixed; min-width: 1145px; }
#checks-table td {
  overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
}
th {
  text-align: left; padding: 10px 12px; color: var(--fg-dim); font-weight: 500;
  border-bottom: 1px solid var(--border); font-size: 12px; text-transform: uppercase; letter-spacing: .5px;
}
td { padding: 9px 12px; border-bottom: 1px solid var(--border); vertical-align: middle; }
tbody tr { cursor: pointer; }
tbody tr:hover { background: var(--bg3); }
tbody tr:last-child td { border-bottom: none; }
.pill {
  display: inline-block; padding: 2px 10px; border-radius: 20px;
  font-size: 12px; font-weight: 600;
}
/* the tint follows the token rather than repeating its value, so a palette
   change cannot leave a background behind on the old colour */
.pill.s0 { background: color-mix(in srgb, var(--ok) 15%, transparent); color: var(--ok); }
.pill.s1 { background: color-mix(in srgb, var(--warn) 15%, transparent); color: var(--warn); }
.pill.s2 { background: color-mix(in srgb, var(--crit) 15%, transparent); color: var(--crit); }
.pill.s3 { background: color-mix(in srgb, var(--unk) 15%, transparent); color: var(--unk); }
.pill.sp { background: color-mix(in srgb, var(--accent) 15%, transparent); color: var(--accent); }
.pill.stale {
  background: transparent; color: var(--warn);
  border: 1px dashed var(--warn); cursor: help;
}
/* the compact form used in the check table (see staleMark) */
.pill.stale.mark { padding: 1px 7px; font-weight: 700; }
.stale-text { color: var(--warn); font-weight: 600; }
.tile.stale .num { color: var(--warn); }
.dim { color: var(--fg-dim); }
/* the candidate count behind a node name: a footnote to the name, not a second
   value competing with it */
.cand { opacity: .6; font-size: 11.5px; }
.mono { font-family: var(--mono); font-size: 13px; }
td .icon, .drawer-head .icon {
  background: none; border: none; color: var(--fg-dim); cursor: pointer;
  padding: 4px 7px; border-radius: 6px; font-size: 14px;
}
td .icon:hover, .drawer-head .icon:hover { background: var(--bg); color: var(--fg); }
.empty { padding: 40px; text-align: center; color: var(--fg-dim); }
.hidden { display: none !important; }

/* cards */
.cards { display: grid; grid-template-columns: repeat(auto-fill, minmax(300px, 1fr)); gap: 14px; }
.card {
  background: var(--bg2); border: 1px solid var(--border); border-radius: var(--radius); padding: 16px;
}
.card h3 { font-size: 15px; display: flex; align-items: center; gap: 8px; margin-bottom: 4px; }
.card .meta { color: var(--fg-dim); font-size: 12px; margin-bottom: 12px; }
/* label column sized by its text, value column takes the rest: neither
   "Checks (owner)" nor a row of role badges may break into a second line */
.kv { display: grid; grid-template-columns: auto minmax(0, 1fr); gap: 4px 12px; font-size: 13px; }
.kv .k { color: var(--fg-dim); white-space: nowrap; }
.kv .v {
  text-align: right; font-variant-numeric: tabular-nums;
  white-space: nowrap; overflow: hidden; text-overflow: ellipsis;
}
/* Roles need more room than a value column can give, so the badges are free
   to use the width next to the label. A node with the usual two or three
   roles keeps them on one line; a monolithic node ("-roles all") carries six
   and wraps into a second line rather than running out of the card. */
.kv .k.kv-roles { grid-column: 1; align-self: start; }
.kv .v.kv-roles {
  grid-column: 2; display: flex; flex-wrap: wrap; justify-content: flex-end;
  gap: 4px; row-gap: 3px; min-width: 0;
}
.kv .v .badge.role { font-size: 10px; padding: 1px 6px; letter-spacing: .2px; }
.badge {
  font-size: 11px; padding: 1px 8px; border-radius: 10px; font-weight: 600;
}
.badge.alive { background: color-mix(in srgb, var(--ok) 15%, transparent); color: var(--ok); }
.badge.dead { background: color-mix(in srgb, var(--crit) 15%, transparent); color: var(--crit); }
.badge.self { background: color-mix(in srgb, var(--accent) 15%, transparent); color: var(--accent); }
/* role badges: soft, muted tones per role */
/* role names are written in capitals wherever they appear */
.badge.role, svg.topo .topo-sub, .topo-legend span:not(.dim) {
  text-transform: uppercase; letter-spacing: .4px;
}
.badge.role { background: var(--bg3); color: var(--fg-dim); }
.badge.role-worker { background: color-mix(in srgb, var(--role-worker) 16%, transparent); color: var(--role-worker); }
.badge.role-web { background: rgba(68, 147, 248, .10); color: var(--role-web); }
.badge.role-client { background: rgba(163, 113, 247, .12); color: var(--role-client); }
.badge.role-backup { background: rgba(224, 140, 174, .12); color: var(--role-backup); }
.badge.role-storage { background: rgba(86, 199, 192, .12); color: var(--role-storage); }
.badge.role-discovery { background: rgba(210, 162, 76, .12); color: var(--role-discovery); }
.badge.role-notification { background: rgba(181, 140, 240, .12); color: var(--role-notification); }
body.light .badge.role-worker { background: rgba(26, 127, 55, .09); }
body.light .badge.role-web { background: rgba(9, 105, 218, .09); }
body.light .badge.role-client { background: rgba(111, 66, 193, .09); }
body.light .badge.role-backup { background: rgba(168, 67, 110, .09); }
body.light .badge.role-storage { background: rgba(31, 122, 117, .10); }
body.light .badge.role-discovery { background: rgba(138, 90, 0, .10); }
body.light .badge.role-notification { background: rgba(111, 66, 193, .10); }

/* drawer */
#drawer {
  position: fixed; top: 0; right: 0; width: min(1180px, 100vw); height: 100vh;
  background: var(--bg2); border-left: 1px solid var(--border);
  padding: 20px; overflow-y: auto; z-index: 20;
  box-shadow: -12px 0 30px rgba(0,0,0,.4);
}
.drawer-head { display: flex; justify-content: space-between; align-items: center; margin-bottom: 14px; }
.drawer-head h2 { font-size: 17px; }
#drawer .out {
  background: var(--bg); border: 1px solid var(--border); border-radius: var(--radius);
  padding: 12px; font-family: var(--mono); font-size: 13px; margin: 10px 0;
  white-space: pre-wrap; word-break: break-word;
}
#drawer svg { width: 100%; height: 70px; margin: 4px 0 8px; }
#drawer svg.chart { height: 120px; }
.chart-h { margin: 14px 0 2px; font-size: 14px; }
.series-btns { display: flex; gap: 6px; flex-wrap: wrap; margin: 12px 0 2px; }
.series-btn {
  background: var(--bg3); border: 1px solid var(--border); color: var(--fg-dim);
  border-radius: 14px; padding: 3px 12px; font-size: 12px; cursor: pointer;
}
.series-btn:hover { color: var(--fg); }
.series-btn.active {
  background: rgba(68, 147, 248, .15); color: var(--accent); border-color: var(--accent);
}
.hist { font-size: 13px; width: 100%; }
.hist td { padding: 5px 8px; }

/* modal */
.modal-back {
  position: fixed; inset: 0; background: rgba(0,0,0,.6); z-index: 30;
  display: flex; align-items: center; justify-content: center; padding: 20px;
}
.modal {
  background: var(--bg2); border: 1px solid var(--border); border-radius: 14px;
  padding: 22px; width: 560px; max-width: 100%; max-height: 90vh; overflow-y: auto;
}
.modal.small { width: 380px; }
.modal h2 { font-size: 17px; margin-bottom: 14px; }
/* the dashboard form is the one form that lives in a settings card rather than
   in an editor, and it wants the same fields */
.modal label, .modal .field, .dash-form label {
  display: block; margin-bottom: 10px; font-size: 13px; color: var(--fg-dim);
}
.modal label.row, .dash-form label.row {
  display: flex; align-items: center; gap: 8px; color: var(--fg);
}
.dash-form { margin-top: 12px; }
/* a select in this form is a field like the inputs beside it: on its own line
   under its label, full width. Without this it stays inline and the help text
   wraps around it. Scoped to labels so the panel rows below keep their own
   layout. */
.dash-form label > select {
  display: block; width: 100%; margin-top: 4px;
  background: var(--bg); border: 1px solid var(--border); border-radius: 8px;
  color: var(--fg); padding: 8px 10px; font-size: 14px;
}
.dash-form label > select:focus { outline: 1px solid var(--accent); }
/* the address for the screen: link and copy button on one line with it */
#dashboard-url { display: flex; align-items: center; flex-wrap: wrap; gap: 8px; margin-top: 14px; }
.dash-link { color: var(--accent); text-decoration: none; word-break: break-all; }
.dash-link:hover { text-decoration: underline; }
.dash-h { font-size: 14px; margin: 20px 0 2px; }
/* One row per panel, so the board reads as a list top to bottom — the order
   here is the order on the screen.
   A grid rather than a flex row: every row has the same eight slots whether
   its type fills them or not, so the columns line up down the whole list. With
   flex, a row with one control fewer moved everything after it. */
.panel-row {
  display: grid; align-items: center; gap: 8px; margin-bottom: 6px;
  grid-template-columns:
    26px                 /* move up/down */
    minmax(120px, 190px) /* type */
    minmax(0, 1fr)       /* title */
    minmax(0, 1fr)       /* what it is about */
    minmax(0, 150px)     /* how it is drawn */
    68px 68px            /* width, height */
    32px;                /* remove */
  grid-template-areas: "move type title what how w h del";
}
.panel-row .p-move { grid-area: move; }
.panel-row > [data-p="type"] { grid-area: type; }
.panel-row > [data-p="title"] { grid-area: title; }
.panel-row .p-what { grid-area: what; }
.panel-row .p-how { grid-area: how; }
.panel-row .p-num.w { grid-area: w; }
.panel-row .p-num.h { grid-area: h; }
.panel-row > .danger { grid-area: del; }
/* the two slots hold at most one control each and hand it the whole cell */
.panel-row .p-what, .panel-row .p-how { min-width: 0; display: block; }
.panel-row .p-what > *, .panel-row .p-how > * { width: 100%; }
.panel-row > select, .panel-row > input { margin-top: 0 !important; min-width: 0; }
.panel-row .p-num {
  display: grid; grid-template-columns: auto minmax(0, 1fr); align-items: center;
  gap: 5px; margin: 0; font-size: 12px; color: var(--fg-dim);
}
.panel-row .p-num input { margin-top: 0 !important; }
.panel-row .p-move { display: flex; flex-direction: column; gap: 1px; }
.panel-row .p-move button {
  padding: 0; width: 22px; height: 15px; line-height: 1; font-size: 9px;
  background: var(--bg3); border: 1px solid var(--border); border-radius: 3px;
  color: var(--fg-dim); cursor: pointer;
}
.panel-row .p-move button:disabled { opacity: .35; cursor: default; }
.panel-row > .danger { padding: 4px 8px; }
.dash-add {
  margin-top: 4px; padding: 5px 12px; font-size: 13px;
  background: var(--bg3); border: 1px solid var(--border); border-radius: 8px;
  color: var(--fg); cursor: pointer;
}
.dash-add:hover { border-color: var(--accent); color: var(--accent); }
/* Narrow: the same eight slots stacked into four lines instead of squeezed
   into one. Named areas rather than a smaller column count, because letting
   eight children auto-place into five columns put the values in the wrong
   boxes. */
@media (max-width: 1250px) {
  .panel-row {
    grid-template-columns: 26px minmax(0, 1fr) 76px 76px;
    grid-template-areas:
      "move type  type del"
      "move title title title"
      "move what  what  what"
      "move how   w     h";
    row-gap: 6px; margin-bottom: 12px; padding-bottom: 12px;
    border-bottom: 1px solid var(--border);
  }
  .panel-row .p-move { align-self: start; }
  /* stacked, the empty slots are blank lines rather than aligned columns —
     the alignment they exist for only happens in the wide layout above */
  .panel-row .p-what:has(.p-none), .panel-row .p-how:has(.p-none) { display: none; }
}
.dash-form input:not([type=checkbox]),
.modal input:not([type=checkbox]), .modal select, .modal textarea {
  display: block; width: 100%; margin-top: 4px;
  background: var(--bg); border: 1px solid var(--border); border-radius: 8px;
  color: var(--fg); padding: 8px 10px; font-size: 14px;
}
.modal input:focus, .modal select:focus,
.dash-form input:focus { outline: 1px solid var(--accent); }
/* Equal columns that may shrink. A bare 1fr floors each column at its
   content's min-content width, and one long option in a select - a check
   named after a URL - then widens its third past a third and pushes the form
   out of the card. */
.grid3 { display: grid; grid-template-columns: repeat(3, minmax(0, 1fr)); gap: 10px; }
.grid2 { display: grid; grid-template-columns: repeat(2, minmax(0, 1fr)); gap: 10px; }
.help { color: var(--fg-dim); font-size: 12px; margin: -4px 0 10px; }
.error { color: var(--crit); font-size: 13px; margin-top: 10px; }
.modal-actions { display: flex; justify-content: flex-end; gap: 10px; margin-top: 16px; }
.modal-actions button {
  background: var(--bg3); color: var(--fg); border: 1px solid var(--border);
  border-radius: var(--radius); padding: 8px 16px; cursor: pointer; font-size: 14px;
}
.modal-actions button.primary { background: var(--accent); border-color: var(--accent); font-weight: 600; }
.modal-actions button.danger {
  background: color-mix(in srgb, var(--crit) 12%, transparent); color: var(--crit); border-color: transparent;
  margin-right: auto; /* push destructive action away from save/cancel */
}
.group-actions { text-align: right; }
.group-actions .icon { visibility: hidden; }
.group-row:hover .group-actions .icon { visibility: visible; }

/* configuration tab: discover (left) + check editor (right) */
/* both panels are one row: the discover card is stretched to the height of
   the check form next to it instead of ending in mid air. Wrapped to its own
   line (narrow window) it keeps its natural height. */
/* Two forms side by side, over the full width — the same width as the list
   below them, so the tab reads as one thing and not as two cards floating
   above a table. Folded away they are two headings; unfolded each gets half
   the page, and below the breakpoint they stack. */
.config-grid {
  display: grid; grid-template-columns: repeat(auto-fit, minmax(420px, 1fr));
  gap: 16px; align-items: start;
}
/* .modal is sized for a dialog; in this tab the card IS the column, so it
   takes the width the grid gives it */
.config-grid .editor { width: auto; max-width: none; max-height: none; }
.config-grid > * { min-width: 0; }

.disc-node { margin: 6px 0 4px; }
.disc-h { font-size: 14px; margin: 14px 0 6px; }
.disc-filter {
  display: block; width: 100%; margin-bottom: 6px;
  background: var(--bg); border: 1px solid var(--border); border-radius: 8px;
  color: var(--fg); padding: 7px 10px; font-size: 13px;
}
.disc-list { display: flex; flex-direction: column; gap: 2px; }
.disc-scroll { max-height: 260px; overflow-y: auto; border: 1px solid var(--border); border-radius: 8px; padding: 4px; }
.disc-item {
  display: flex; align-items: center; gap: 8px;
  padding: 5px 8px; border-radius: 6px; cursor: pointer;
  font-size: 13px; color: var(--fg) !important; margin: 0 !important;
}
.disc-item:hover { background: var(--bg3); }
.disc-item.monitored { opacity: .5; cursor: default; }
.disc-label { font-weight: 500; white-space: nowrap; }
.disc-sub { color: var(--fg-dim); overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }

/* grouped check table */
.group-row td {
  background: var(--bg3); font-weight: 600; cursor: pointer;
  padding: 7px 12px; font-size: 13px;
}
.group-row .dim { font-weight: 400; }
/* indent child rows so their name aligns with the host name (after the arrow) */
#checks-table tbody tr:not(.group-row) td:first-child { padding-left: 34px; }

/* ---------- performance tab ---------- */

/* view selector: whole cluster vs. a single node */
.view-switch {
  display: flex; align-items: center; gap: 6px; flex-wrap: wrap;
  background: var(--bg2); border: 1px solid var(--border); border-radius: var(--radius);
  padding: 6px; margin-bottom: 4px;
}
.vs-btn {
  display: inline-flex; align-items: center; gap: 7px;
  background: none; border: 1px solid transparent; color: var(--fg-dim);
  border-radius: 8px; padding: 7px 13px; font-size: 13.5px; cursor: pointer;
  font-family: inherit;
}
.vs-btn:hover { color: var(--fg); background: var(--bg3); }
.vs-btn.active {
  color: var(--fg); background: var(--bg3); border-color: var(--accent); font-weight: 600;
}
.vs-btn .dot { width: 8px; height: 8px; box-shadow: none; }
.vs-btn.active .dot.on { box-shadow: 0 0 6px var(--ok); }
.vs-ic { color: var(--accent); font-size: 12px; }
.vs-sub { color: var(--fg-dim); font-size: 11.5px; font-weight: 400; }
.vs-sep { width: 1px; align-self: stretch; background: var(--border); margin: 2px 4px; }

.sec-h { font-size: 16px; margin: 26px 0 2px; letter-spacing: .3px; display: flex; align-items: center; gap: 8px; flex-wrap: wrap; }
.sec-sub { color: var(--fg-dim); font-size: 12.5px; margin-bottom: 14px; }
.tile .num small { font-size: 15px; font-weight: 500; color: var(--fg-dim); }
/* second label line of a tile ("of 185 GB") — its own line so the big
   figure above it never wraps */
.tile .lbl .tsub { display: block; opacity: .8; font-size: 12px; }
#perf-body .tiles { margin-bottom: 14px; }
#perf-body .card { margin-bottom: 14px; }
.card.wide { grid-column: 1/-1; }

/* utilization gauges + live trend side by side */
/* minmax(0,…) on the right column, not a bare 1fr. "1fr" means
   minmax(AUTO, 1fr), so the column can never be narrower than its content's
   min-content width - and the archive table in the storage tab has a nowrap
   output column carrying whole interface descriptions. The column grew to fit
   it and pushed the page wider than the window. With a zero minimum the
   column takes the space that is there and the table scrolls inside its own
   .tablewrap, which is what that wrapper is for. */
.perf-grid { display: grid; grid-template-columns: minmax(320px, 440px) minmax(0, 1fr); gap: 14px; }
@media (max-width: 940px) { .perf-grid { grid-template-columns: 1fr; } }
/* the gauge card is stretched to the height of the trend card next to it —
   keep its content vertically centred instead of top-aligned */
.perf-grid .card { display: flex; flex-direction: column; }

.gauges {
  display: flex; justify-content: space-around; gap: 8px; flex-wrap: wrap;
  margin-top: 6px; flex: 1; align-items: center;
}
.gauge { text-align: center; flex: 1 1 120px; max-width: 150px; }
.gauge svg { width: 100%; height: auto; display: block; }
.gauge .g-num { font-size: 24px; font-weight: 600; fill: var(--fg); }
.gauge .g-unit { font-size: 13px; fill: var(--fg-dim); }
.gauge .g-state { font-size: 10.5px; font-weight: 600; letter-spacing: .4px; text-transform: uppercase; }
.gauge .g-lbl { font-size: 13px; font-weight: 500; margin-top: -2px; }
.gauge .g-sub { font-size: 12px; color: var(--fg-dim); margin-top: 2px; }

/* horizontal meters (per node) */
.meter { min-width: 0; }
.m-top { display: flex; justify-content: space-between; align-items: baseline; font-size: 12px; gap: 8px; }
.m-val { font-variant-numeric: tabular-nums; font-weight: 600; font-size: 12.5px; }
.m-track { height: 8px; background: var(--bg3); border-radius: 4px; margin: 4px 0 3px; }
.m-fill { height: 100%; border-radius: 4px; transition: width .4s ease; }
.m-sub { font-size: 11.5px; white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }

/* per-node resource rows */
.node-res {
  display: grid; grid-template-columns: 230px 1fr; gap: 18px; align-items: center;
  padding: 12px 0; border-bottom: 1px solid var(--border);
}
.node-res:last-child { border-bottom: none; padding-bottom: 0; }
.node-res.offline { opacity: .55; }
/* the rows drill into the node view — the negative margin lets the hover
   surface reach into the card padding instead of hugging the text */
.node-res, .bar-row { cursor: pointer; border-radius: 8px; }
.node-res { padding: 12px 10px; margin: 0 -10px; }
.bar-row { padding: 4px 10px; margin: 0 -10px; }
.node-res:hover, .bar-row:hover { background: var(--bg3); }

/* per-volume meters in the node view */
.vol-grid {
  display: grid; grid-template-columns: repeat(auto-fit, minmax(230px, 1fr));
  gap: 16px 22px; margin-top: 10px;
}
.nr-title { font-size: 14px; font-weight: 600; display: flex; align-items: center; gap: 6px; flex-wrap: wrap; }
.nr-meta { font-size: 12px; margin-top: 2px; }
.nr-meters { display: grid; grid-template-columns: repeat(3, 1fr); gap: 18px; min-width: 0; }
@media (max-width: 760px) {
  .node-res { grid-template-columns: 1fr; gap: 8px; }
  .nr-meters { grid-template-columns: 1fr; gap: 10px; }
}

/* check load bars */
.bars { display: flex; flex-direction: column; gap: 9px; margin-top: 8px; }
.bar-row { display: grid; grid-template-columns: minmax(90px, 180px) 1fr 130px; align-items: center; gap: 12px; font-size: 13px; }
.br-name { overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.bar-track { height: 10px; background: var(--bg3); border-radius: 5px; }
.bar-fill { display: block; height: 100%; border-radius: 5px; background: var(--accent); transition: width .4s ease; }
.br-val { text-align: right; font-variant-numeric: tabular-nums; }

/* compact client agent list (cluster tab): built for hundreds of rows —
   one dense line each, multi column, scrolled inside the card */
#cluster-clients .card { margin-top: 14px; }
.agent-list {
  display: grid; grid-template-columns: repeat(auto-fill, minmax(290px, 1fr));
  gap: 2px 14px; margin-top: 8px;
  max-height: 420px; overflow-y: auto;
}
.agent-row {
  /* the last column holds the update button of an outdated agent or the ✕ of an
     offline one — never both, and it stays 0 wide without either */
  display: grid; grid-template-columns: 10px minmax(0, 1fr) auto auto;
  align-items: baseline; gap: 4px 8px;
  padding: 4px 8px; border-radius: 6px; cursor: pointer; font-size: 13px;
}
.agent-row:hover { background: var(--bg3); }
.agent-row.offline { opacity: .65; }
.agent-row .dot { width: 8px; height: 8px; box-shadow: none; align-self: center; }
.ar-name { font-weight: 500; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.ar-state { color: var(--fg-dim); font-size: 11.5px; text-align: right; }
.agent-row.offline .ar-state { color: var(--crit); }
.ar-meta {
  grid-column: 2 / -1; color: var(--fg-dim); font-size: 11px;
  overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
}
.agent-off { color: var(--warn); }
/* one-way link: the same orange the arrow in the topology uses */
.kv .v.oneway { color: var(--warn); cursor: help; }

/* removing an offline node: always offered on its card (they are the exception
   anyway), and only on hover in the dense agent list */
.node-forget {
  background: none; border: none; color: var(--fg-dim); cursor: pointer;
  font-size: 13px; line-height: 1; padding: 3px 7px; border-radius: 6px;
}
.node-forget:hover { background: color-mix(in srgb, var(--crit) 14%, transparent); color: var(--crit); }
.card h3 .node-forget { margin-left: auto; font-size: 14px; }
.agent-row .node-forget { grid-row: 1; grid-column: 4; align-self: center; visibility: hidden; }
.agent-row:hover .node-forget, .agent-row .node-forget:focus { visibility: visible; }
#cluster-clients .disc-filter { max-width: 340px; margin-top: 10px; }

/* backup tab */
.bk-nodes { display: flex; flex-direction: column; gap: 6px; margin-top: 6px; font-size: 13px; }
.bk-node { display: flex; align-items: center; gap: 7px; }
.bk-node .dot { width: 8px; height: 8px; box-shadow: none; }
.bk-node.dead { opacity: .7; }
/* the pointer from the tab to the settings section that now holds the
   retention - a sentence, not a button that looks like an action */
/* the readings beside the timezone field: same rhythm as the field's label
   and help text, so the two halves of the row read as one thing */
.gen-kv { align-content: start; row-gap: 6px; margin-top: 22px; }
.gen-kv .v { font-size: 13px; }

/* what the assistant is: three choices, each with the sentence that decides
   between them - a dropdown would hide two of the three behind a click */
.ai-modes { display: flex; flex-direction: column; gap: 8px; margin: 4px 0 16px; }
.ai-modes-h { font-size: 12.5px; color: var(--fg-dim); }
.ai-mode {
  display: grid; grid-template-columns: auto 1fr; gap: 2px 9px;
  align-items: start; padding: 9px 11px; cursor: pointer;
  border: 1px solid var(--border); border-radius: 10px; background: var(--bg);
}
.ai-mode:hover { border-color: var(--fg-dim); }
.ai-mode.on { border-color: var(--accent); }
.ai-mode input { margin-top: 2px; grid-row: 1 / span 2; }
.ai-mode-n { font-size: 13.5px; font-weight: 600; color: var(--fg); }
.ai-mode-h { font-size: 12px; color: var(--fg-dim); line-height: 1.45; }

/* The connection block, dead unless a model is actually going to be called.
   A fieldset because that is what the browser already knows how to switch
   off - every field, the select and both buttons at once, and no way to
   submit it by pressing return in one of them either.
   Its own chrome is removed: it is a grouping for the disabling, not a box
   somebody should see. */
.ai-conn { border: 0; margin: 0; padding: 0; min-width: 0; }
.ai-conn[disabled] { opacity: .45; }
.ai-conn-why { margin: 0 0 10px; }

/* the same for the access level: still a real setting while the assistant
   answers without a model, and nothing at all while it is off */
.ai-access.off { opacity: .45; }

/* the diagnosis under a failure: there to be read second, if at all */
.ai-detail .ai-text { color: var(--fg-dim); font-size: 11.5px; }

.bk-where { margin-top: 14px; }

/* the storage tab: the node cards stack in the left column, the archive
   search stands beside them - the same shape the backup tab has */
.st-grid > #storage-nodes { display: flex; flex-direction: column; gap: 14px; }
/* the search reaches the bottom of the numbers beside it: a short form next
   to a tall card leaves a hole under it that reads as something missing */
/* the switch between the two histories the storage tab holds: what the
   monitoring measured, and what was done to it */
.st-switch { display: flex; gap: 6px; margin-bottom: 12px; }
.st-tab {
  background: var(--bg3); color: var(--fg-dim); border: 1px solid var(--border);
  border-radius: 8px; padding: 5px 14px; font-size: 12.5px; cursor: pointer;
}
.st-tab:hover { color: var(--fg); }
.st-tab.active { background: var(--accent); border-color: var(--accent); color: #fff; }

/* one changed field, as "interval_sec 60 → 300" */
.cl-field { display: inline-block; margin-right: 12px; white-space: nowrap; }
.cl-field b { color: var(--fg); font-weight: 600; }
.cl-was { text-decoration: line-through; opacity: .7; }
.pill.el-error { background: rgba(251,80,98,.15); color: var(--crit); }
.pill.el-warning { background: rgba(255,180,46,.15); color: var(--warn); }
.pill.cl-created { background: rgba(51,207,144,.15); color: var(--ok); }
.pill.cl-changed { background: rgba(255,180,46,.15); color: var(--warn); }
.pill.cl-deleted { background: rgba(251,80,98,.15); color: var(--crit); }

.st-grid > #storage-search { display: flex; min-width: 0; }
/* min-width:0 for the same reason the grid column needs minmax(0,…): a flex
   item defaults to min-width:auto and refuses to shrink below its content,
   so the card would carry the wide table straight back out of the layout. */
.st-grid > #storage-search > .card { flex: 1; min-width: 0; }
.linkish {
  background: none; border: 0; padding: 0; font: inherit; font-size: 12.5px;
  color: var(--fg-dim); cursor: pointer; text-align: left;
  text-decoration: underline; text-decoration-color: var(--border);
  text-underline-offset: 3px;
}
.linkish:hover { color: var(--accent); text-decoration-color: var(--accent); }

.bk-wrap { max-height: 460px; overflow-y: auto; margin-top: 8px; }
.bk-table { font-size: 13px; }
.bk-table th { position: sticky; top: 0; background: var(--bg2); z-index: 1; }
.bk-table td { padding: 7px 10px; }
.badge.reason { background: var(--bg3); color: var(--fg-dim); }
.btn-mini {
  background: var(--bg3); color: var(--fg); border: 1px solid var(--border);
  border-radius: 8px; padding: 4px 12px; font-size: 12.5px; cursor: pointer;
}
.btn-mini:hover { border-color: var(--accent); color: var(--accent); }
.btn-mini:disabled { opacity: .5; cursor: default; }

/* notification tab: the contact list needs room for a destination and three
   actions, the settings are a handful of short numbers — the performance tab's
   narrow-left grid squeezed the wrong one of the two */
/* 50/50 — minmax(0,…) instead of a bare 1fr, otherwise a long destination
   (a webhook URL) pushes its column wider than half and the split tilts */
/* both cards are one row: the contacts card is stretched to the height of the
   alarm settings next to it instead of ending in mid air */
.nt-grid { display: grid; grid-template-columns: minmax(0, 1fr) minmax(0, 1fr); gap: 14px; align-items: stretch; }
.nt-grid > .card { display: flex; flex-direction: column; }
@media (max-width: 1040px) { .nt-grid { grid-template-columns: 1fr; } }
.nt-contacts td { vertical-align: middle; }
/* the destination is the one cell that can grow without limit (a webhook URL,
   a space ID) — it ellipsises and keeps the full value in the tooltip */
.nt-dest { max-width: 320px; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.nt-acts { text-align: right; white-space: nowrap; }
.badge.kind { background: var(--bg3); color: var(--fg-dim); text-transform: uppercase; letter-spacing: .4px; }
/* a send-attempt count does not need the width of a URL field */
.nt-set input[type=number] { max-width: 160px; }
.nt-set h4 { margin: 14px 0 8px; font-size: 13px; }

/* topology map (cluster tab, below the member cards) */
#cluster-topo .card { margin-top: 14px; }
.card-head { display: flex; justify-content: space-between; align-items: flex-start; gap: 14px; flex-wrap: wrap; }
.card-head button { background: var(--bg3); color: var(--fg); border: 1px solid var(--border); }
.card-head button.primary { background: var(--accent); border-color: var(--accent); color: #fff; }
/* top right of the topology card: take a node into the cluster by address */
.topo-actions { display: flex; align-items: center; gap: 10px; flex-wrap: wrap; justify-content: flex-end; }
.topo-join { display: flex; align-items: center; gap: 6px; }
.topo-join input {
  background: var(--bg); border: 1px solid var(--border); color: var(--fg);
  border-radius: 8px; padding: 6px 10px; font-size: 12.5px; width: 200px;
}
.topo-join input:focus { outline: none; border-color: var(--accent); }
.topo-join button { border-radius: 8px; padding: 6px 12px; font-size: 12.5px; white-space: nowrap; }
.topo-toggle {
  display: inline-flex; align-items: center; gap: 7px; cursor: pointer;
  background: var(--bg3); border: 1px solid var(--border); border-radius: 20px;
  padding: 5px 12px 5px 10px; font-size: 12.5px; color: var(--fg-dim); white-space: nowrap;
}
.topo-toggle:hover { color: var(--fg); }
.topo-toggle input { accent-color: var(--accent); cursor: pointer; }
/* capped width: stretched over a wide card the map would scale its labels
   up with it and swallow the card */
svg.topo {
  width: 100%; max-width: 900px; height: auto; display: block; margin: 4px auto 0;
  overflow: visible; /* safety net: a wide label may reach into the card padding */
}
.topo-node { cursor: pointer; }
.topo-node:hover circle { fill-opacity: .45; }
.topo-node.offline { opacity: .6; }
.topo-num { fill: var(--fg); font-size: 12px; font-weight: 600; font-variant-numeric: tabular-nums; }
.topo-lbl { fill: var(--fg); font-size: 12px; font-weight: 500; }
.topo-sub { fill: var(--fg-dim); font-size: 10.5px; }
/* the plate behind a node caption: it hides the mesh line that would otherwise
   run through the text, and reads like the role badges elsewhere */
.topo-plate { fill: var(--bg3); stroke: var(--border); stroke-width: 1; }
.topo-node:hover .topo-plate { stroke: var(--accent); }
.topo-legend {
  display: flex; gap: 16px; flex-wrap: wrap; align-items: center;
  font-size: 12px; color: var(--fg-dim); margin-top: 6px;
}
.topo-legend i { display: inline-block; width: 9px; height: 9px; border-radius: 50%; margin-right: 6px; }

/* live trend chart */
.trend-wrap { position: relative; margin-top: 6px; }
svg.trend { width: 100%; height: auto; display: block; overflow: visible; }
svg.trend .ax { fill: var(--fg-dim); font-size: 10px; }
.chart-empty { padding: 46px 10px; text-align: center; font-size: 13px; }
#trend-tip {
  position: absolute; transform: translate(-50%, -128%); pointer-events: none;
  background: var(--bg3); border: 1px solid var(--border); color: var(--fg);
  border-radius: 8px; padding: 5px 9px; font-size: 12px; line-height: 1.35;
  white-space: nowrap; box-shadow: 0 6px 16px rgba(0,0,0,.35);
}

/* toast */
#toast {
  position: fixed; bottom: 24px; left: 50%; transform: translateX(-50%);
  background: var(--bg3); border: 1px solid var(--border); color: var(--fg);
  padding: 10px 20px; border-radius: var(--radius); z-index: 40; font-size: 14px;
  box-shadow: 0 8px 24px rgba(0,0,0,.5);
}

@media (max-width: 700px) {
  header { flex-wrap: wrap; gap: 10px; }
  .grid3 { grid-template-columns: 1fr; }
}

/* storage tab */
.st-form label { display: block; margin-bottom: 10px; font-size: 13px; color: var(--fg-dim); }
.st-form label.row { display: flex; align-items: center; gap: 8px; color: var(--fg); }
.st-form input:not([type=checkbox]), .st-form select, .st-form textarea {
  display: block; width: 100%; margin-top: 4px;
  background: var(--bg); border: 1px solid var(--border); border-radius: 8px;
  color: var(--fg); padding: 8px 10px; font-size: 14px;
}
.st-form input:focus, .st-form select:focus { outline: 1px solid var(--accent); }
.st-form .modal-actions { margin-top: 6px; }
.st-form button { background: var(--bg3); color: var(--fg); border: 1px solid var(--border);
  border-radius: var(--radius); padding: 8px 16px; cursor: pointer; font-size: 14px; }
.st-form button.primary { background: var(--accent); border-color: var(--accent); color: #fff; font-weight: 600; }
.st-msg { font-size: 13px; margin-top: 10px; }
.st-msg.ok-msg { color: var(--ok); }
.st-kv { margin-top: 8px; }
/* the forecast sits under the node numbers, separated but not detached */
.st-fc { margin-top: 14px; border-top: 1px solid var(--border); padding-top: 10px; }
.st-fc-h { font-size: 13px; font-weight: 600; margin-bottom: 6px; }
.st-fc .kv { font-size: 12.5px; }
.warn-msg { color: var(--warn); }
/* Vertical rhythm of the role tabs: their sections are built one after the
   other, so the gap belongs to the container instead of to every block that
   happens to carry a margin. Without it two cards sit flush against each
   other wherever a block brings no margin of its own. */
#notify-body, #discovery-body, #storage-body, #settings-body, #backup-body {
  display: flex; flex-direction: column; gap: 14px;
}
#notify-body > .tiles, #discovery-body > .tiles, #storage-body > .tiles { margin-bottom: 0; }
/* the subtitle belongs to the heading above it, not to the next section */
#notify-body > .sec-h, #discovery-body > .sec-h,
#settings-body > .sec-h { margin-bottom: -8px; }
#notify-body > .sec-sub, #discovery-body > .sec-sub,
#settings-body > .sec-sub { margin-bottom: 0; }
.st-sub { font-size: 14px; margin-top: 16px; }
.st-path { max-width: 220px; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; direction: rtl; }
.shared-host { color: var(--warn); cursor: help; }
svg.topo .topo-host { fill: var(--fg-dim); font-size: 11px; font-weight: 600; letter-spacing: .3px; }

/* discovery tab */
.disc-form label { display: block; margin-bottom: 10px; font-size: 13px; color: var(--fg-dim); }
.disc-form label.row { display: flex; align-items: center; gap: 8px; color: var(--fg); }
.disc-form input:not([type=checkbox]), .disc-form select, .disc-form textarea {
  display: block; width: 100%; margin-top: 4px;
  background: var(--bg); border: 1px solid var(--border); border-radius: 8px;
  color: var(--fg); padding: 8px 10px; font-size: 14px;
}
.disc-form input:focus, .disc-form select:focus, .disc-form textarea:focus {
  outline: 1px solid var(--accent);
}
.disc-form button { background: var(--bg3); color: var(--fg); border: 1px solid var(--border);
  border-radius: var(--radius); padding: 8px 16px; cursor: pointer; font-size: 14px; }
.disc-form button.primary { background: var(--accent); border-color: var(--accent); color: #fff; font-weight: 600; }
.disc-form button.danger { background: color-mix(in srgb, var(--crit) 12%, transparent); color: var(--crit); border-color: transparent; }
.disc-msg { font-size: 13px; margin-top: 10px; }
.disc-msg.ok-msg { color: var(--ok); }
.disc-progress { margin-top: 12px; }
.disc-wrap { max-height: 420px; overflow-y: auto; margin-top: 8px; }
.disc-wrap td, .disc-wrap th { white-space: nowrap; }
.ports { white-space: normal !important; }
.port {
  display: inline-block; background: var(--bg3); border-radius: 6px;
  padding: 1px 7px; margin: 1px 2px 1px 0; font-size: 11.5px;
  font-family: var(--mono); color: var(--fg-dim);
}
.disc-h { font-size: 13px; margin: 10px 0 4px; }
#disc-diff-out { max-height: 220px; overflow-y: auto; font-size: 12px; }

/* discovery: devices that are already monitored */
.disc-wrap tr.mon td:first-child { box-shadow: inset 3px 0 0 var(--ok); }
.port.mon { border-color: var(--ok); color: var(--ok); }

/* discovery: only the fields of the selected SNMP version */
.snmp-box[data-snmpver=""] .snmp-2c,
.snmp-box[data-snmpver=""] .snmp-v3,
.snmp-box[data-snmpver="2c"] .snmp-v3,
.snmp-box[data-snmpver="3"] .snmp-2c { display: none; }
.snmp-box .grid2 { margin-bottom: 0; }

/* settings: credential fields inside the check form */
.cred-inline { margin: .4rem 0 .2rem; padding: .6rem .7rem; border: 1px solid var(--border);
  border-radius: 8px; background: var(--bg3); }
.cred-inline .help { margin: .3rem 0 0; }
#settings-btn { margin-left: .15rem; }

/* notification: the four "alarm on" boxes sit in one row */
.onbox { display: flex; flex-wrap: wrap; gap: .1rem .9rem; padding-top: .25rem; }
.onbox label.row { margin: 0; font-size: .82rem; }
.wide-field { grid-column: 1 / -1; }
#nt-contact textarea, #nt-settings textarea { width: 100%; font-family: var(--mono); font-size: .8rem; }

/* ---------- check history chart ---------- */
/* Drawn as one SVG figure; every colour comes from a class so both themes
   work without redrawing. The palette follows the printed look: a light grid,
   dim labels, the data in saturated lines over their own translucent fill. */
.ch-bar {
  display: flex; align-items: center; gap: 10px; flex-wrap: wrap;
  margin: 14px 0 2px;
}
.ch-ranges, .ch-units, .ch-tools { display: flex; gap: 4px; flex-wrap: wrap; }
.ch-tools { margin-left: auto; }
.ch-range, .ch-unit, .ch-tool {
  background: var(--bg3); border: 1px solid var(--border); color: var(--fg-dim);
  border-radius: 14px; padding: 3px 11px; font-size: 12px; cursor: pointer; line-height: 1.5;
}
.ch-range:hover, .ch-unit:hover, .ch-tool:hover { color: var(--fg); }
.ch-range.active, .ch-unit.active, .ch-tool.active {
  background: rgba(68, 147, 248, .15); color: var(--accent); border-color: var(--accent);
}
.ch-wrap { position: relative; margin-top: 4px; }
.ch-wrap.loading { opacity: .55; }         /* the old picture stays until the new one is there */
.ch-note { margin-top: 2px; font-size: 12px; }

#drawer svg.ch-svg { width: 100%; height: auto; display: block; margin: 0; }
.ch-svg { user-select: none; }
.ch-svg text { font-family: "Segoe UI", system-ui, sans-serif; }
.ch-title { font-size: 13px; font-weight: 600; fill: var(--fg); }
.ch-sub, .ch-ax { font-size: 11px; fill: var(--fg-dim); }
.ch-leg-t { font-size: 11px; fill: var(--fg); }
.ch-leg { cursor: pointer; }
.ch-leg.off { opacity: .38; }
.ch-leg.off .ch-leg-t { text-decoration: line-through; }
.ch-grid { stroke: var(--border); stroke-width: 1; opacity: .55; }
.ch-axis { stroke: var(--border); stroke-width: 1; }
/* every second day tinted and midnight marked: a week of data without
   counting hours */
.ch-band { fill: var(--fg); opacity: .035; }
.ch-day { stroke: var(--fg-dim); stroke-width: 1; stroke-dasharray: 3 4; opacity: .45; }
.ch-thr { stroke-width: 1; stroke-dasharray: 4 4; opacity: .85; }
.ch-thr.warn, .ch-thr-t.warn { stroke: var(--warn); fill: var(--warn); }
.ch-thr.crit, .ch-thr-t.crit { stroke: var(--crit); fill: var(--crit); }
.ch-thr-t { font-size: 10px; stroke: none; }
.ch-cross { stroke: var(--fg-dim); stroke-width: 1; stroke-dasharray: 2 3; }
.ch-sel { fill: var(--accent); fill-opacity: .13; stroke: var(--accent); stroke-width: 1; }
#ch-hit { cursor: crosshair; }
/* status strip under the axis — only drawn where something was not OK */
.ch-svg rect.s1 { fill: var(--warn); }
.ch-svg rect.s2 { fill: var(--crit); }
.ch-svg rect.s3 { fill: var(--unk); }
#ch-tip {
  position: absolute; transform: translate(-50%, 0); pointer-events: none;
  background: var(--bg3); border: 1px solid var(--border); color: var(--fg);
  border-radius: 8px; padding: 6px 10px; font-size: 12px; line-height: 1.5;
  white-space: nowrap; box-shadow: 0 6px 16px rgba(0,0,0,.35); z-index: 5;
}
#ch-tip i { display: inline-block; width: 8px; height: 8px; border-radius: 2px; margin-right: 6px; }

/* gaps: a stretch nothing was measured in. Hatched rather than blank — the
   difference between "the value was low" and "nobody was there to measure". */
.ch-hatch { stroke: var(--fg-dim); stroke-width: 1; opacity: .2; }
.ch-gapbar { fill: var(--fg-dim); opacity: .45; }
/* the explanation under a checkbox belongs to it, not to the next one */
.modal .help.indent { margin: -4px 0 12px 24px; }


/* replication rows: the state on the value side, the detail line under it */
.st-fc .st-repsub { font-size: 11.5px; margin-top: 1px; }


/* the contact editor opens under the list: it needs to read as its own block
   instead of growing out of the last table row */
#nt-contact {
  margin-top: 18px; padding-top: 14px; border-top: 1px solid var(--border);
}
#nt-contact h4 { margin: 0 0 12px; font-size: 14px; }

/* the per contact alarm rules: folded away until they are used */
.nt-rules { margin: 4px 0 12px; border-top: 1px solid var(--border); padding-top: 10px; }
.nt-rules summary { cursor: pointer; font-size: 13px; color: var(--fg); }
.nt-rules summary::marker { color: var(--fg-dim); }
.nt-rules .help { margin: 6px 0 10px; }

/* Configuration: both panels fold away. The tab carries two long forms and
   usually only one of them is the reason somebody opened it, so the heading
   is the switch — a <details>, which needs no script to work. */
.fold > summary {
  list-style: none; cursor: pointer; user-select: none;
  display: flex; align-items: center; gap: 8px;
  font-size: 17px; font-weight: 600; color: var(--fg);
}
.fold > summary::-webkit-details-marker { display: none; }
.fold > summary::before {
  content: "›"; display: inline-block; font-size: 20px; line-height: 1;
  color: var(--fg-dim); transition: transform .15s ease; transform: rotate(0deg);
}
.fold[open] > summary::before { transform: rotate(90deg); }
.fold > summary:hover::before { color: var(--accent); }
.fold[open] > summary { margin-bottom: 14px; }

/* mass edit: the list of what already exists, with a tick per row */
.bulk-toolbar { margin-top: 18px; }
.bulk-card { margin-top: 10px; }
.bulk-modes { margin: 10px 0 4px; }
.bulk-tablewrap { max-height: 420px; overflow: auto; margin-top: 8px; }
.bulk-table { width: 100%; font-size: 13px; }
.bulk-table th { position: sticky; top: 0; background: var(--bg2); z-index: 1; }
.bulk-table td, .bulk-table th { padding: 5px 8px; text-align: left; }
.bulk-table tbody tr:hover { background: var(--bg3); }
.bulk-table tr.picked { background: rgba(68, 147, 248, .10); }
.bulk-table td:not(.bulk-pick) { max-width: 240px; overflow: hidden;
  text-overflow: ellipsis; white-space: nowrap; }
.bulk-pick { width: 34px; text-align: center !important; }
.bulk-form { margin-top: 14px; border-top: 1px solid var(--border); padding-top: 12px; }
.bulk-fields { display: flex; flex-wrap: wrap; gap: 12px 16px; align-items: flex-start; }
.bulk-fields label, .bulk-fields .field { display: block; font-size: 12.5px; color: var(--fg-dim); }
.bulk-fields label > span, .bulk-fields .field > span { display: block; margin-bottom: 3px; }
.bulk-fields input:not([type=checkbox]), .bulk-fields select {
  background: var(--bg); border: 1px solid var(--border); border-radius: 8px;
  color: var(--fg); padding: 6px 8px; font-size: 13px; min-width: 120px;
}
.bulk-fields input[type=number] { width: 110px; min-width: 0; }
/* The switch and the four states on one line — a field narrow enough to wrap
   them turned the mask into a column of checkboxes. */
.bulk-fields .field { min-width: 260px; }
.bulk-inline { display: flex; align-items: center; gap: 10px; flex-wrap: nowrap; }
.bulk-fields .onbox { display: flex; flex-wrap: nowrap; gap: 9px; margin: 0; padding: 0; }
.bulk-fields .onbox label { display: flex; align-items: center; gap: 3px; color: var(--fg);
  font-size: 12.5px; white-space: nowrap; }
.bulk-actions { display: flex; align-items: center; gap: 10px; margin-top: 14px; flex-wrap: wrap; }
.bulk-withchecks { display: flex; align-items: center; gap: 6px; font-size: 13px; color: var(--fg); }
.bulk-actions .danger { background: var(--bg3); border: 1px solid var(--crit); color: var(--crit); }
.bulk-actions .danger:hover:not(:disabled) { background: var(--crit); color: #fff; }
.bulk-actions button:disabled { opacity: .45; cursor: default; }
.bulk-form.no-notify .bulk-fields label:has(select[name=notify_contact]),
.bulk-form.no-notify .bulk-fields .field:has(.bulk-notify-mode) { display: none; }
/* an inherited value is shown as what it is: the same name, but not this
   record's own decision */
.inherited { color: var(--fg-dim); font-style: italic; }

/* The editors have the width of the page — so their content uses it: two
   fields per row instead of one column of very wide inputs, which makes the
   card shorter without making anything harder to read. */
.config-grid .editor #param-fields {
  display: grid; grid-template-columns: repeat(auto-fit, minmax(230px, 1fr));
  gap: 0 12px; align-items: start;
}
.disc-cols {
  display: grid; grid-template-columns: repeat(auto-fit, minmax(280px, 1fr));
  gap: 0 18px; align-items: start;
}
.disc-cols .disc-h { margin-top: 0; }
.disc-cols .modal-actions { margin-bottom: 4px; }
@media (max-width: 760px) {
  .config-grid .grid2 { grid-template-columns: 1fr; }
}

/* update button in the cluster card: an action inside a value row, so it stays
   small and does not look like the card's own button */
.node-update {
  margin-left: 8px; padding: 1px 8px; font-size: 11.5px; line-height: 1.6;
  border-radius: 10px; background: rgba(68, 147, 248, .15);
  border: 1px solid var(--accent); color: var(--accent); cursor: pointer;
}
.node-update:hover:not(:disabled) { background: var(--accent); color: #fff; }
.node-update:disabled { opacity: .5; cursor: default; }

/* an update in flight takes the button's place. It keeps turning through the
   restart — for part of that the node is unreachable, and without this nothing
   on the screen would say why. */
.node-updating {
  display: inline-flex; align-items: center; gap: 6px;
  margin-left: 8px; font-size: 11.5px; color: var(--accent);
}
.node-updating.mini { margin: 0; gap: 5px; }
/* the short form in an agent row: it may shrink, but the ring beside it must
   not — a row with a long host name still has to say what is going on */
.mini-txt {
  font-size: 11px; min-width: 0;
  overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
}
.spin {
  width: 12px; height: 12px; flex: none; border-radius: 50%;
  border: 2px solid rgba(68, 147, 248, .25); border-top-color: var(--accent);
  animation: spin .8s linear infinite;
}
@keyframes spin { to { transform: rotate(360deg); } }
/* a spinning ring is a bad idea for some people, but the state still has to be
   visible — so it breathes instead of turning */
@media (prefers-reduced-motion: reduce) {
  .spin { animation: spin-pulse 1.4s ease-in-out infinite; }
}
@keyframes spin-pulse { 0%, 100% { opacity: 1; } 50% { opacity: .25; } }

/* uploading a build: a file, its version, one button */
.upload-form {
  display: flex; align-items: flex-end; gap: 12px; flex-wrap: wrap;
  margin-top: 14px; border-top: 1px solid var(--border); padding-top: 12px;
}
.upload-form label { display: block; font-size: 12.5px; color: var(--fg-dim); margin: 0; }
.upload-form label > span { display: block; margin-bottom: 3px; }
.upload-form input[type=text], .upload-form input:not([type=file]) {
  background: var(--bg); border: 1px solid var(--border); border-radius: 8px;
  color: var(--fg); padding: 6px 8px; font-size: 13px; width: 130px;
}
.upload-form input[type=file] { font-size: 12.5px; color: var(--fg); max-width: 320px; }
.upload-form .help { flex-basis: 100%; margin: 4px 0 0; }

/* a card that folds: the heading is the switch, the card keeps its padding */
details.card.fold > summary { font-size: 15px; font-weight: 600; margin: 0; }
details.card.fold[open] > summary { margin-bottom: 6px; }
/* the update button of an agent sits in a narrow grid cell: it must keep its
   own line rather than break into three */
/* the arrow variant: same size as the ✕ beside it, so a row with an update
   offer is exactly as tall and as wide as one without */
.node-update.mini {
  padding: 0; width: 18px; height: 18px; line-height: 1;
  font-size: 13px; border-radius: 4px;
}
.agent-row .node-update, .agent-row .node-updating {
  grid-row: 1; grid-column: 4; align-self: center; justify-self: end; margin: 0;
  min-width: 0; /* the name gives way first, but the text may shrink too */
}

/* discovery import: the parent picker sits next to the import button */
.disc-import { display: flex; align-items: flex-end; gap: 10px; flex-wrap: wrap; }
.disc-parent { display: flex; flex-direction: column; gap: 2px; font-size: 12px; color: var(--fg-dim); }
.disc-parent select { min-width: 180px; }

/* ---- palette preview (dashboard settings) ----
   The swatches carry the DASHBOARD colours, not the GUI ones, and always the
   dark set: that is what a wallboard shows. Hardcoded rather than tokenised on
   purpose — this box is a picture of another page's palette, so it must not
   follow this page's. */
.pal-sw {
  width: 22px; height: 22px; border-radius: 4px; flex: none;
  border: 1px solid rgba(255, 255, 255, .18);
}
.pal-preview[data-pal="signal"] .ok { background: #33cf90; }
.pal-preview[data-pal="signal"] .warn { background: #ffb42e; }
.pal-preview[data-pal="signal"] .crit { background: #fb5062; }
.pal-preview[data-pal="signal"] .unknown { background: #a78bfa; }
.pal-preview[data-pal="cvd"] .ok { background: #12b3a0; }
.pal-preview[data-pal="cvd"] .warn { background: #ffbe3d; }
.pal-preview[data-pal="cvd"] .crit { background: #ff5f8f; }
.pal-preview[data-pal="cvd"] .unknown { background: #8b5cf6; }
.pal-preview[data-pal="contrast"] .ok { background: #3ee08f; }
.pal-preview[data-pal="contrast"] .warn { background: #ffc233; }
.pal-preview[data-pal="contrast"] .crit { background: #ff5f6d; }
.pal-preview[data-pal="contrast"] .unknown { background: #b98cff; }
.pal-preview[data-pal="soft"] .ok { background: #63b98f; }
.pal-preview[data-pal="soft"] .warn { background: #d9a35a; }
.pal-preview[data-pal="soft"] .crit { background: #e0737f; }
.pal-preview[data-pal="soft"] .unknown { background: #9d8df1; }

/* ---- settings: a nav down the side, one section at a time ----
   Four unrelated jobs live in this tab. Stacked they were a page nobody read
   to the end; as sections the page is only ever as long as the one thing
   somebody came for. The nav is vertical and not a second row of tabs under
   the main one: two horizontal rows read as one broken navigation. */
.set-layout {
  display: grid; grid-template-columns: 210px minmax(0, 1fr);
  gap: 16px; align-items: start;
}
.set-nav { display: flex; flex-direction: column; gap: 4px; position: sticky; top: 12px; }
.set-tab {
  display: block; width: 100%; text-align: left; cursor: pointer;
  padding: 9px 12px; border-radius: var(--radius);
  background: none; border: 1px solid transparent; color: var(--fg-dim);
  font: inherit; font-size: 13.5px;
}
.set-tab:hover { background: var(--bg2); color: var(--fg); }
.set-tab.active {
  background: var(--bg2); border-color: var(--border); color: var(--fg);
  /* the accent edge, not a filled block: the selected entry has to be obvious
     without turning a quiet sidebar into the loudest thing on the page */
  box-shadow: inset 2px 0 0 var(--accent);
}
.set-tab .st-name { display: block; font-weight: 600; }
/* what the section would say if it were open — so nobody has to click all
   four to find out where the work is */
.set-tab .st-note { display: block; font-size: 11.5px; color: var(--fg-dim); margin-top: 1px; }
.set-tab.active .st-note { color: var(--accent); }
.set-pane { min-width: 0; }

/* a named group inside a form: the fields under it answer one question */
.set-group {
  font-size: 12px; font-weight: 600; letter-spacing: .07em; text-transform: uppercase;
  color: var(--fg-dim); margin: 22px 0 8px;
  padding-bottom: 5px; border-bottom: 1px solid var(--border);
}
.set-group:first-of-type { margin-top: 4px; }

/* The panel list gets its column names once at the top instead of repeating
   "W" and "H" on every row. Only in the wide layout — the narrow one below
   stacks the row into four lines, where a header could not line up with
   anything. */
.panel-head {
  display: grid; align-items: end; gap: 8px; margin-bottom: 6px;
  grid-template-columns:
    26px minmax(120px, 190px) minmax(0, 1fr) minmax(0, 1fr) minmax(0, 150px) 68px 68px 32px;
  font-size: 11px; letter-spacing: .06em; text-transform: uppercase; color: var(--fg-dim);
}
@media (min-width: 1251px) {
  .panel-row .p-num > span { display: none; }
  .panel-row .p-num { grid-template-columns: minmax(0, 1fr); }
}
@media (max-width: 1250px) { .panel-head { display: none; } }

@media (max-width: 900px) {
  .set-layout { grid-template-columns: minmax(0, 1fr); }
  .set-nav { position: static; flex-direction: row; flex-wrap: wrap; }
  .set-tab { width: auto; }
  .set-tab .st-note { display: none; }
}

/* A form field is as wide as it is readable, not as wide as the window: on a
   27" screen a port number in a 640px box looks like a mistake. The panel list
   below is exempt — it is a table and wants every pixel. */
.dash-form > .grid2, .dash-form > label.row, .dash-form > #dashboard-url { max-width: 1000px; }
/* the palette preview is a field beside the select, not a note under it: same
   label, same line, same height as the box it belongs to */
.pal-field .pal-preview {
  display: flex; align-items: center; gap: 8px;
  margin-top: 4px; min-height: 38px;
}

/* ---- downtime ----
   A clock, drawn rather than typed: as an emoji it is a different picture on
   every system and a box wherever no emoji font is installed — the same reason
   the logo is an SVG.

   Both states are outlines rather than one filled and one hollow shape: a
   filled disc would need its hands cut out in the row colour, and the rows
   behind this mark are not all the same colour. So the running window is the
   coloured one with a tinted face, and the announced one is a grey outline —
   which is also the pair that survives being looked at from an arm's length. */
.dt-mark {
  display: inline-flex; vertical-align: -2px; margin-left: 5px;
  width: 14px; height: 14px;
}
.dt-mark svg { width: 100%; height: 100%; }
.dt-mark .face { fill: none; stroke: currentColor; stroke-width: 1.4; }
.dt-mark .hands {
  fill: none; stroke: currentColor; stroke-width: 1.5;
  stroke-linecap: round; stroke-linejoin: round;
}
.dt-mark.active { color: var(--accent); }
.dt-mark.active .face { fill: color-mix(in srgb, var(--accent) 22%, transparent); }
.dt-mark.planned { color: var(--fg-dim); }
.dt-when { white-space: nowrap; }
.dt-when.active { color: var(--accent); }
.dt-reason { font-size: 11.5px; max-width: 260px; }

/* the fold in the two editors */
.dt-fold { margin: 12px 0 4px; }
.dt-fold > summary { font-size: 13.5px; }
/* its own grid rather than grid3: the host dialog is narrow, and three fixed
   columns pushed the reason out of the box entirely */
.dt-grid {
  display: grid; gap: 10px; margin-top: 4px;
  grid-template-columns: repeat(auto-fit, minmax(170px, 1fr));
}
.dt-actions { display: flex; align-items: flex-start; gap: 12px; margin-top: 8px; }
.dt-actions .help { margin: 0; flex: 1; }
/* the three boxes of the mass edit sit on the line of their mode select */
.bulk-dt { flex-wrap: wrap; gap: 6px; }
.bulk-dt input { min-width: 0; }
.bulk-dt input[type="datetime-local"] { flex: 0 1 190px; }
.bulk-dt input[maxlength="200"] { flex: 1 1 180px; }
/* The window gets a line of its own, in both modes. .bulk-fields is a FLEX
   container, so the grid-column that used to stand here did nothing at all:
   the field simply flowed on, and with three fields in front of it (hosts) it
   landed beside them while with seven (checks) it wrapped below. Taking the
   full width forces the break either way, so it is found in the same place. */
.bulk-dt-field { flex: 1 0 100%; }
/* a box that does nothing until the mode beside it says so has to look like
   it: three enabled-looking date fields under "leave alone" read as a window
   somebody already typed */
.bulk-dt input:disabled { opacity: .4; }

/* ---- downtime history ----
   A search, not a list: three months of a busy installation is a few hundred
   lines, and what somebody arrives with is "what did we announce over db01 in
   March". */
/* Its own section, not a footnote of the mass edit above it: the cards in this
   tab carry their own spacing (the toolbar 18px, the bulk card 10px), and
   without one this card sat flush against the block above and read as part of
   it. */
#dt-history-card { margin-top: 18px; }
.dth-form { margin-top: 12px; }
.dth-fields {
  display: grid; gap: 10px;
  grid-template-columns: repeat(auto-fit, minmax(160px, 1fr));
}
.dth-actions { display: flex; align-items: center; gap: 12px; margin-top: 12px; flex-wrap: wrap; }
.dth-actions .help { margin: 0; }
#dt-history-body { margin-top: 12px; }
.dth-table td { vertical-align: top; }
/* the three verbs, told apart at a glance: an announcement is neutral, a
   change is amber, a cancellation is the one that ends something */
.dth-act {
  font-size: 11.5px; font-weight: 600; letter-spacing: .04em;
  padding: 2px 7px; border-radius: 3px; white-space: nowrap;
}
.dth-act.set { background: color-mix(in srgb, var(--accent) 16%, transparent); color: var(--accent); }
.dth-act.changed { background: color-mix(in srgb, var(--warn) 16%, transparent); color: var(--warn); }
.dth-act.removed { background: color-mix(in srgb, var(--fg-dim) 18%, transparent); color: var(--fg-dim); }

/* ---------- the AI assistant dock ----------

   It sits above everything in the bottom right corner and is absent from the
   page entirely until the assistant is switched on. Collapsed it is one
   circle; open it is a small terminal that never grows past a third of the
   viewport - it is a corner, not a second application. */
#ai-dock { position: fixed; right: 18px; bottom: 18px; z-index: 60;
  display: flex; flex-direction: column; align-items: flex-end; gap: 10px; }
#ai-button { width: 46px; height: 46px; border-radius: 50%; padding: 0; line-height: 0;
  display: flex; align-items: center; justify-content: center;
  border: 1px solid var(--border); background: var(--accent); color: #fff; cursor: pointer;
  box-shadow: 0 4px 14px rgba(0,0,0,.35); transition: transform .12s ease; }
#ai-button svg { width: 26px; height: 26px; display: block; }
#ai-button:hover { transform: scale(1.06); }
#ai-dock.open #ai-button { background: var(--bg2); color: var(--fg); }
#ai-panel { width: min(430px, calc(100vw - 36px)); max-height: min(46vh, 480px);
  display: flex; flex-direction: column; overflow: hidden;
  background: var(--bg2); border: 1px solid var(--border); border-radius: 10px;
  box-shadow: 0 10px 34px rgba(0,0,0,.45); }
.ai-head { display: flex; align-items: center; gap: 8px; padding: 8px 10px;
  border-bottom: 1px solid var(--border); }
.ai-head .ai-title { font-weight: 600; font-size: 13px; }
.ai-head .ai-model { flex: 1; font-size: 11.5px; color: var(--fg-dim);
  overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.ai-log { flex: 1; overflow-y: auto; padding: 10px; font-size: 12.5px; line-height: 1.45;
  display: flex; flex-direction: column; gap: 8px; }
.ai-row { display: flex; gap: 7px; align-items: flex-start; }
.ai-who { color: var(--fg-dim); font-family: var(--mono, monospace); flex: none; }
/* the answer keeps the line breaks it was written with, and wraps a long
   word instead of widening the panel */
.ai-text { white-space: pre-wrap; overflow-wrap: anywhere; }
.ai-row.ai-you .ai-text { color: var(--fg); }
.ai-row.ai-argus .ai-text { color: var(--fg-dim); }
.ai-row.ai-error .ai-text { color: var(--crit, #e5534b); }
.ai-pending { color: var(--fg-dim); font-style: italic;
  display: flex; align-items: center; gap: 8px; }
.ai-pending-text { flex: 1 1 auto; }

/* three dots that breathe, so a long wait is visibly a wait and not a page
   that stopped. The motion is the only thing saying "still here" in the
   seconds where nothing else changes. */
.ai-dots { display: inline-flex; gap: 3px; flex: 0 0 auto; }
.ai-dots i { width: 4px; height: 4px; border-radius: 50%; background: currentColor;
  opacity: .25; animation: ai-blink 1.2s ease-in-out infinite; }
.ai-dots i:nth-child(2) { animation-delay: .2s; }
.ai-dots i:nth-child(3) { animation-delay: .4s; }
@keyframes ai-blink { 40% { opacity: 1; } }

/* A wallboard runs for weeks and somebody may have asked for less motion. The
   seconds keep counting either way, so nothing is lost. */
@media (prefers-reduced-motion: reduce) {
  .ai-dots i { animation: none; opacity: .6; }
}

.ai-stop { flex: 0 0 auto; font-size: 11px; font-style: normal;
  padding: 2px 8px; border-radius: 6px; cursor: pointer;
  color: var(--fg-dim); background: transparent; border: 1px solid var(--border); }
.ai-stop:hover { color: var(--fg); border-color: var(--fg-dim); }
.ai-cli { display: flex; align-items: center; gap: 6px; padding: 8px 10px;
  border-top: 1px solid var(--border); }
.ai-cli .ai-prompt { color: var(--accent); font-family: var(--mono, monospace); }
.ai-cli input { flex: 1; min-width: 0; background: transparent; border: 0; outline: 0;
  color: var(--fg); font-family: var(--mono, monospace); font-size: 12.5px; }
.ai-switch { font-size: 14px; }
.ai-fold { margin-top: 10px; }
/* the jump a help answer offers: a button, because it is shorter than the
   sentence describing the route to it */
.ai-goto { margin-top: 6px; padding: 3px 10px; font-size: 12px; cursor: pointer;
  border: 1px solid var(--accent); border-radius: 5px;
  background: transparent; color: var(--accent); }
.ai-goto:hover { background: var(--accent); color: #fff; }
/* the access level sits with the switch, not in the folded model form: it is
   a decision about what the assistant may see, not a connection detail */
.ai-access { display: block; margin-top: 12px; max-width: 460px; }
.ai-access > span:first-child { display: block; margin-bottom: 3px;
  font-size: 12.5px; color: var(--fg-dim); }

/* a change the assistant proposed: a box, not a sentence — it lists what would
   happen and carries the two buttons that decide it */
.ai-proposal { border: 1px solid var(--warn, #d9a441); border-radius: 8px;
  padding: 9px 10px; margin: 2px 0; font-size: 12.5px; }
.ai-prop-head { font-weight: 600; margin-bottom: 6px; color: var(--warn, #d9a441); }
.ai-proposal ul { margin: 0 0 6px; padding-left: 18px; }
.ai-proposal li { margin: 2px 0; overflow-wrap: anywhere; }
.ai-prop-actions { display: flex; gap: 8px; margin-top: 8px; }
.ai-prop-actions button { padding: 3px 10px; font-size: 12px; cursor: pointer; }
.ai-prop-done { margin-top: 8px; white-space: pre-wrap; overflow-wrap: anywhere; }
/* a snapshot taken before an assistant change is marked in the list: it is the
   one somebody reaches for when a change has to go back */
.badge.reason-ai { border-color: var(--warn, #d9a441); color: var(--warn, #d9a441); }
