/* ============================================================================
   Clearview "Console" — Batch Tools
   ----------------------------------------------------------------------------
   Requires tokens/tokens.css + css/clearview-refresh.css + css/media.css
   (app shell). Namespaced `cvb-`.

   THE DIAGNOSIS
   This page is two products bolted together: a machine REGISTRY (inventory
   CRUD) and a fleet OPERATIONS launcher (ping, iPerf, profile migration, file
   transfer, watch agents). The seam shows.

   Symptoms in the current build:
   - The "Tools" tab bar is four tabs that each contain one <select> and one
     "Open Tool" button (batch-tools.js:370). A tab metaphor costs a click and
     adds no information — every tab leads to a modal anyway.
   - You choose a TOOL first, then choose machines inside the modal. So the
     table you are looking at plays no part in the operation you run on it.
   - Five icon-only row actions carry title= and nothing else, which is why
     there is a five-item legend above the table (:450) whose entire job is to
     explain them.
   - Results have no home: iPerf batches, capture compares and migrations each
     land in their own modal or a display:none div.

   THE RESPONSE
   1. Selection-first. Pick machines in the table; the operations light up in a
      bulk bar. Same model as the OBS Monitor, so the two fleet pages behave
      alike.
   2. RUNS are a first-class object — one operation across N machines, with a
      per-machine outcome and retry-failed. At fleet scale partial failure is
      normal, and it currently has nowhere to be shown.
   3. The legend dies; row actions get real labels and tooltips.
   ============================================================================ */

/* ================================================================== registry */

/* table-layout:fixed makes the table exactly fill its wrapper — column widths
   come from the <colgroup> renderBatchTable() emits, and the table can never
   overflow its container, so there is no horizontal (or knock-on vertical)
   scrollbar. Long cell text is clipped with an ellipsis; the Profile column is
   additionally middle-truncated in JS so the _MAIN/_BACKUP role suffix stays
   visible even when abbreviated. */
.cvb-tbl { width: 100%; table-layout: fixed; border-collapse: separate; border-spacing: 0; }
.cvb-tbl-wrap { overflow-x: auto; overflow-y: visible; }
.cvb-tbl th {
  position: sticky; top: 0; z-index: var(--cv-z-sticky);
  height: var(--cv-row); padding: 0 var(--cv-space-2);
  background: var(--cv-bg-raised);
  border-bottom: 1px solid var(--cv-border);
  font-size: 9px; font-weight: var(--cv-weight-semibold);
  letter-spacing: var(--cv-tracking-caps); text-transform: uppercase;
  /* --cv-text-muted on --cv-bg-raised is 4.25:1 — under AA for 9px caps. */
  color: var(--cv-text-secondary); text-align: left; white-space: nowrap;
  cursor: pointer; user-select: none;
}
.cvb-tbl th:hover { color: var(--cv-text); }
.cvb-tbl th[aria-sort] { color: var(--cv-accent); }
.cvb-tbl th .cv-i { opacity: 0.4; }
.cvb-tbl th[aria-sort] .cv-i { opacity: 1; }
.cvb-tbl td {
  height: var(--cv-row-dense); padding: 0 var(--cv-space-2);
  border-bottom: 1px solid var(--cv-border-subtle);
  white-space: nowrap; overflow: hidden; text-overflow: ellipsis;
}
.cvb-tbl tbody tr:hover td { background: var(--cv-bg-hover); }
.cvb-tbl tbody tr[aria-selected="true"] td { background: var(--cv-accent-wash); }
.cvb-tbl .cvb-num {
  font-family: var(--cv-font-mono); font-variant-numeric: tabular-nums;
  text-align: right;
}
.cvb-tbl td.cvb-fit { width: 1%; }
.cvb-tbl td.cvb-fit:has(> .cv-cellcheck), .cvb-tbl th.cvb-fit:has(> .cv-cellcheck) { padding: 0; }

.cvb-rail-online  { box-shadow: inset 3px 0 0 var(--cv-success); }
.cvb-rail-offline { box-shadow: inset 3px 0 0 var(--cv-danger); }
.cvb-rail-unknown { box-shadow: inset 3px 0 0 var(--cv-neutral); }
.cvb-rail-slow    { box-shadow: inset 3px 0 0 var(--cv-warning); }

/* Group rows in the table when grouping is on — same shape as the OBS Monitor's,
   so the two fleet pages read alike. */
.cvb-tbl tr.cvb-grp td {
  height: var(--cv-row);
  background: var(--cv-bg-raised);
  border-top: 1px solid var(--cv-border);
  border-bottom: 1px solid var(--cv-border);
  font-size: 9px; font-weight: var(--cv-weight-semibold);
  letter-spacing: var(--cv-tracking-caps); text-transform: uppercase;
  color: var(--cv-text-secondary);
}
.cvb-tbl tr.cvb-grp:first-child td { border-top: none; }

/* Latency reads as a bar first, a number second — a long bar in a column of
   short ones is the machine about to drop a transfer. */
.cvb-ping { display: inline-flex; align-items: center; gap: 5px; justify-content: flex-end; }
.cvb-ping .cv-meter { width: 30px; height: 5px; flex: none; }

.cvb-type {
  padding: 0 4px; border-radius: var(--cv-radius-sm);
  font-family: var(--cv-font-mono); font-size: 8px;
  background: var(--cv-bg-active); color: var(--cv-text-muted);
}
.cvb-type[data-t="OBS"]   { background: var(--cv-accent-wash); color: var(--cv-accent); }
.cvb-type[data-t="Pearl"] { background: var(--cv-info-wash);   color: var(--cv-info); }

/* ===================================================================== runs
   A run is one operation across N machines. It replaces four separate result
   surfaces — the iPerf batch modal, the capture-compare display:none div, the
   migration matrix result and the transfer status poll — with one object that
   can report partial failure, which is the normal outcome at fleet scale. */
.cvb-runs { display: grid; gap: var(--cv-space-1); }

.cvb-run {
  border: 1px solid var(--cv-border);
  border-radius: var(--cv-radius);
  background: var(--cv-bg-surface);
  overflow: hidden;
}
.cvb-run[data-state="running"] { border-color: var(--cv-accent-edge); }
.cvb-run[data-state="failed"]  { border-color: var(--cv-danger-edge); }
.cvb-run[data-state="partial"] { border-color: var(--cv-warning-edge); }

.cvb-run-head {
  display: grid;
  grid-template-columns: 18px 18px minmax(0, 1fr) 150px auto auto;
  gap: var(--cv-space-2); align-items: center;
  width: 100%;
  min-height: var(--cv-row); padding: 0 var(--cv-space-2);
  border: none; background: none; color: inherit; font: inherit;
  text-align: left; cursor: pointer;
}
.cvb-run-head:hover { background: var(--cv-bg-hover); }
.cvb-run-op { font-size: var(--cv-text-xs); font-weight: var(--cv-weight-medium); }
.cvb-run-sub { font-size: var(--cv-text-2xs); color: var(--cv-text-muted); }
.cvb-run-when {
  font-family: var(--cv-font-mono); font-variant-numeric: tabular-nums;
  font-size: var(--cv-text-2xs); color: var(--cv-text-muted);
}
.cvb-run-body { border-top: 1px solid var(--cv-border-subtle); background: var(--cv-bg-raised); }

.cvb-res {
  display: grid; grid-template-columns: 16px minmax(0, 1fr) auto auto;
  gap: var(--cv-space-2); align-items: center;
  min-height: var(--cv-row-dense); padding: 0 var(--cv-space-2);
  font-size: var(--cv-text-2xs);
  border-bottom: 1px solid var(--cv-border-subtle);
}
.cvb-res:last-child { border-bottom: none; }
.cvb-res[data-ok="false"] { color: var(--cv-danger); }
.cvb-res-detail {
  font-family: var(--cv-font-mono); font-variant-numeric: tabular-nums;
  color: var(--cv-text-muted);
}
.cvb-res[data-ok="false"] .cvb-res-detail { color: var(--cv-danger); }

/* Outcome ratio: 22/26 done, 3 failed — legible before opening the run. */
.cvb-tally { display: inline-flex; align-items: center; gap: var(--cv-space-2); font-size: var(--cv-text-2xs); }
.cvb-tally b {
  font-family: var(--cv-font-mono); font-variant-numeric: tabular-nums;
  font-weight: var(--cv-weight-normal);
}
.cvb-tally .ok   { color: var(--cv-success); }
.cvb-tally .bad  { color: var(--cv-danger); }
.cvb-tally .wait { color: var(--cv-text-muted); }

/* ================================================================== launcher
   The tools that are NOT scoped to a selection — watch agents, scheduled
   capture jobs, importing. A menu, not a tab bar: they open a dialog either
   way, so the tabs were pure overhead. */
.cvb-tools { min-width: 250px; }
.cvb-tools .cv-menu-item { align-items: flex-start; }
.cvb-tools .cv-menu-item i {
  display: block; font-style: normal;
  font-size: 9px; color: var(--cv-text-disabled);
}
.cvb-tools .cv-menu-item > span { display: grid; gap: 1px; }

/* Partial outcome: some machines succeeded, some did not. Distinct from both
   "all good" and "all failed", because at fleet scale it is the common case. */
.cvb-fill-partial { background: var(--cv-warning); }

/* ========================================================= profile assignment
   Each machine carries the profile it should receive, chosen in the row itself.
   This replaces the profile x machine matrix (batch-tools.js:2553) whose sticky
   first column is applied by looping over cells and setting four style
   properties in JS (:2654) — at 28 machines and a dozen profiles that grid is a
   wall of checkboxes, and it cannot show you WHY a pairing was chosen.

   Three ways to fill it, because operators arrive with different information:
   auto-match by name similarity, search, or pick from the list. */
/* The Profile column is the widest column (see the <colgroup> in
   renderBatchTable) so the whole name usually fits. When a name is too long
   for the column, batchProfCell() middle-truncates it in JS — keeping the head
   and the _MAIN/_BACKUP/_ISO role suffix, never end-truncating the suffix away
   — and the full name stays in the button's tooltip/aria-label. This replaces
   the old "table scrolls horizontally so the name never abbreviates" approach,
   which produced an unwanted horizontal scrollbar. */

.cvb-prof {
  display: inline-flex; align-items: center; gap: 4px;
  max-width: 100%; min-width: 0;
  padding: 1px 4px; margin: 0 -4px;
  border: 1px solid transparent; border-radius: var(--cv-radius-sm);
  background: none; color: inherit; font: inherit; cursor: pointer;
  transition: background var(--cv-dur-fast) var(--cv-ease), border-color var(--cv-dur-fast) var(--cv-ease);
}
.cvb-prof > span { overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.cvb-prof .cv-i { width: 10px; height: 10px; opacity: 0.35; flex: none; }
.cvb-prof:hover { background: var(--cv-bg-active); border-color: var(--cv-border-strong); }
.cvb-prof:hover .cv-i { opacity: 1; }
.cvb-prof[aria-expanded="true"] { background: var(--cv-accent-wash); border-color: var(--cv-accent-edge); color: var(--cv-accent); }
.cvb-prof[data-set="false"] > span { color: var(--cv-text-disabled); font-style: italic; }

/* Confidence is shown, not hidden. An auto-match that was a guess should say so
   — the operator is about to push this profile onto a live machine. */
.cvb-conf {
  padding: 0 3px; border-radius: var(--cv-radius-sm);
  font-size: 8px; font-weight: var(--cv-weight-semibold);
  letter-spacing: var(--cv-tracking-caps); text-transform: uppercase;
  flex: none;
}
.cvb-conf[data-c="exact"]  { background: var(--cv-success-wash); color: var(--cv-success); }
.cvb-conf[data-c="likely"] { background: var(--cv-warning-wash); color: var(--cv-warning); }
.cvb-conf[data-c="weak"]   { background: var(--cv-danger-wash);  color: var(--cv-danger); }

.cvb-profmenu { min-width: 300px; max-height: 340px; display: flex; flex-direction: column; }
.cvb-profmenu-search { padding: var(--cv-space-1); border-bottom: 1px solid var(--cv-border-subtle); }
.cvb-profmenu-list { overflow-y: auto; padding: var(--cv-space-1); }
.cvb-profmenu .cv-menu-item { font-family: var(--cv-font-mono); font-size: var(--cv-text-2xs); }
.cvb-profmenu .cv-menu-item[aria-checked="true"] { color: var(--cv-accent); }
.cvb-profmenu-score {
  margin-left: auto; flex: none;
  font-family: var(--cv-font-mono); font-size: 8px; color: var(--cv-text-disabled);
}

/* A link-derived assignment is data, not a guess — it reads as the accent, not
   as a confidence warning. */
.cvb-conf[data-c="linked"] { background: var(--cv-accent-wash); color: var(--cv-accent); }
