/* Copyright (C) 2026 Lidor
 * SPDX-License-Identifier: AGPL-3.0-or-later
 */

/*
 * SimpleVirt web UI stylesheet — production dark theme.
 *
 * Design system v2 (production-grade): a 4-layer dark scaffold differentiated
 * by surface, not shadow; one accent hue; green/amber/red reserved for status.
 * The UI is written to a CSP that forbids inline styles (style-src 'self', no
 * 'unsafe-inline'), so every rule lives here — JS only toggles class names (plus
 * CSSOM width/background for meters, which that CSP permits). The controller's
 * own header (internal/commandserver/middleware.go) is looser today and still
 * allows 'unsafe-inline'.
 */

/* ---------- brand webfont (self-hosted, air-gap-safe) -------------------- */
/* Inter — a variable face (one file, weights 100-900) so the product renders in
   its own brand type on every OS instead of macOS San Francisco / Windows Segoe,
   the single most visible "internal tool" tell. Self-hosted (default-src 'self'
   allows it) so it works with no internet on the appliance. */
@font-face {
  font-family: "Inter";
  font-style: normal;
  font-weight: 100 900;
  font-display: swap;
  src: url("../fonts/InterVariable.woff2") format("woff2");
}
@font-face {
  font-family: "Inter";
  font-style: italic;
  font-weight: 100 900;
  font-display: swap;
  src: url("../fonts/InterVariable-Italic.woff2") format("woff2");
}
/* IBM Plex Mono — the one monospace face, for machine identifiers an operator
   copies (UUID, fsid, IP, MAC, path, WWN). Self-hosted (default-src 'self',
   air-gap-safe) so identifiers render in the product's own mono on every OS
   instead of the system monospace. Headings and the dominant metric stay Inter:
   hierarchy comes from size and weight, not a second display face. */
@font-face {
  font-family: "IBM Plex Mono";
  font-style: normal;
  font-weight: 400 600;
  font-display: swap;
  src: url("../fonts/IBMPlexMono.woff2") format("woff2");
}

/* ---------- tokens ------------------------------------------------------- */

:root {
  /* Surface ladder — 2026-09 near-black refactor. The canvas is a nearly neutral
     graphite-black (a whisper of cool, NOT navy), and every plane above it is a
     small, deliberate step — surfaces are told apart by a 1px border and spacing,
     not by pouring blue into boxes. Chrome (top bar, primary nav) recedes at or
     below the canvas; the inventory pane lifts a hair; panels sit just above the
     canvas so a card reads flat-but-present with its hairline, never as a glowing
     blue tile. "If everything is blue, nothing is SimpleVirt blue" — so almost every
     visible pixel here is neutral. */
  /* True near-black, hue-neutral (R≈G≈B). The old surfaces carried a blue cast
     (#0b0f14 etc.) that read as navy rather than black; neutralised + darkened a
     step so "black" reads as black and the SimpleVirt blue accent pops against it. */
  --bg: #0a0c10;          /* app canvas — rich deep charcoal (a whisper of cool), NOT pure black, so the surface reads with depth instead of flat */
  --bg-nav: #070809;      /* primary navigation — recedes below the canvas so only content lifts */
  --bg-rail: #070809;     /* chrome rail alias (primary nav, tasks dock) */
  --bg-inventory: #0e1014;/* inventory navigator — lifts a hair above the shell */
  --bg-panel: #14161c;    /* panel/card/table surface — a clear, deliberate step off the canvas so a card reads as a premium object, not a flat patch */
  --bg-raised: #14161c;   /* alias: most cards/panels/tables resolve here */
  --bg-elevated: #1d212a; /* genuinely raised: modals, dropdowns, popovers — a clear step above panels */
  --bg-hover: #21252f;    /* row/nav/control hover — a clear luminance lift */
  --bg-inset: #0c0d12;    /* inputs, meter tracks, table headers — recessed */
  --bg-selected: #172032; /* selected row/nav — a cool accent-washed charcoal */
  --folder-band: #171920; /* VM-folder group header: a raised divider band */

  /* Borders carry the structure now that surfaces are flat — thin and neutral. */
  --border-subtle: #1f212a; /* hairline within a surface (row separators) */
  --border: #2b2f3a;        /* the default 1px around panels/controls — a touch stronger so a card reads as a distinct object */
  --border-strong: #3b4150;
  --border-focus: #474d5b;
  /* A crisper edge used ONLY on lifting content panels (cards, bands) so their
     leading edge catches light against the near-black canvas, while --border stays
     the quiet control edge and --border-subtle stays the recessive row divider.
     Dark-theme value; the light block re-declares it to the light panel edge. */
  --border-panel: #323745;
  /* Neutral structural keyline for card heads and KPI leading bars. Blue is
     reserved for STATUS plates and INTERACTION only — a decorative blue bar on
     every card diluted both. Resolves per-theme via --border-strong. */
  --keyline: var(--border-strong);

  /* Three deliberately SEPARATED levels (enterprise-maturity pass): primary is
     near-white; secondary was #9da7b3, brightened to #b2bbc7 so it reads clearly
     on the dark canvas (the "secondary text is almost invisible" complaint) while
     staying a clear step below primary; muted sits a clear step below secondary
     so a dense page has a real 3-level type-colour hierarchy, not two greys that
     blur together. All three still clear AA on --bg. */
  --text: #e9edf2;        /* primary — object names, values (bright, near-neutral) */
  --text-dim: #a5afba;    /* secondary — metadata, labels, descriptions */
  --text-muted: #838e9b;  /* muted — least-important detail; brightened to hold AA on the richer panel surface */
  --text-disabled: #48515c;

  /* The interaction blue. The trusted default hue for serious infrastructure
     tooling, now unified with the topbar's blue cast, instead of
     the old indigo that read startup-premium rather than serious-infra. Deep
     enough that white button text clears AA-large and link text on the dark
     canvas clears AA. */
  --accent: #438bff;        /* SimpleVirt brand blue — selection keyline, links, primary */
  --accent-hover: #5c9bff;  /* lighter for hover on the near-black canvas */
  --accent-press: #2f74e6;
  /* Solid-fill accent for primary buttons: white on #2563eb clears WCAG AA
     (~4.9:1) at body weight; the brighter #438bff carries keylines, links, focus
     and tints where it sits on the near-black canvas and can shine. */
  --accent-solid: #2563eb;
  --accent-solid-hover: #1d4ed8;
  --accent-tint: rgba(67, 139, 255, 0.14);
  /* The focus ring is a NON-TEXT indicator, so WCAG 2.1 SC 1.4.11 wants 3:1
     against what it sits on. At 55% alpha this blue composited to 2.5:1 on the
     near-black panel (#0b0f14): the ring was drawn on every control in the
     product and could be made out on none of them. Opaque it is 5.9:1. */
  --focus-ring: #438bff;
  /* Ice/cyan is the SECONDARY brand colour, used very sparingly — the live-console
     LED, a single telemetry sparkline. Never a status colour, never a second accent
     sprayed across the UI. */
  --cyan: #66c7f4;
  --cyan-tint: rgba(102, 199, 244, 0.12);
  /* No accent glow on application surfaces — SimpleVirt is static and expensive, not
     neon. A transparent no-op shadow (valid inside a comma-separated box-shadow
     list, unlike `none`) so anything still referencing it simply stays flat. */
  --glow-accent: 0 0 transparent;

  /* Category keyline (dark) — NEUTRALIZED in the 2026-09 redesign. Colour is now
     reserved for STATUS (green/amber/red) and INTERACTION (blue) only; a card no
     longer carries a decorative violet/rose/cyan hue "by what it is". Compute,
     memory, storage and network are told apart by their ICON and label, the way a
     serious control plane does it — not by painting each tile a different colour,
     which is what made the grid read as a decorated dashboard template. All five
     tokens resolve to one quiet cool-steel keyline so nothing that referenced them
     breaks; they simply stop shouting. */
  --cat: #6b7683;        /* one neutral category keyline — icon + label tell compute/memory/storage/network apart, not colour */

  /* Status — desaturated, enterprise. Health reads without glowing; amber means
     attention; red is reserved for genuinely serious conditions; grey is
     neutral/offline/unconfigured. Tints are faint — a keyline or dot, never a
     flooded panel. */
  --ok: #46a25b;
  --ok-tint: rgba(70, 162, 91, 0.13);
  --warn: #cf9b3a;
  --warn-tint: rgba(207, 155, 58, 0.13);
  --warn-fg: #1a1204;
  --err: #df544e;
  --err-tint: rgba(223, 84, 78, 0.13);
  --info: #438bff;
  --grey: #6d7985;
  --muted-chip-fg: #8a95a1;              /* "Checking / Unknown" chip text — tuned ≥4.5:1 on its own tint */
  --muted-chip-bg: rgba(110, 118, 129, 0.12);
  --track: #0e131a;
  --row-hover: rgba(255, 255, 255, 0.028);

  /*
   * Top bar — the brand surface. Deliberately NOT --bg-rail: the bar is the one
   * chrome element that stays the same deep navy in both themes, so SimpleVirt
   * Command is recognizable at a glance rather than dissolving into a white
   * strip under the light theme. The hue carries the shield's blue cast, and
   * these tokens are re-declared identically in the light block below — the bar
   * opts out of theming on purpose. Its controls (admin name, Log out, refresh,
   * theme toggle) need their own text/hover tokens because the generic
   * .btn-ghost resolves to --text-dim, which is dark-on-dark here.
   */
  --topbar-bg: #06090d; /* nearly black — one restrained bottom border */
  --topbar-text: #e9edf2;
  --topbar-text-dim: #8a95a1;
  --topbar-border: #1b2530;
  --topbar-hover: rgba(255, 255, 255, 0.055);
  --topbar-brand: #5c9bff;
  --topbar-wordmark: #2b7bf5; /* the "Virt" of the wordmark: the logo's blue, 4.9:1 on the near-black bar */

  /* corner radii — one scale (control < card < panel < hero) */
  --r-xs: 4px;
  --r-sm: 6px;
  --r-md: 6px;   /* card radius restrained to the enterprise 4-6px band (was 8px)
                    so panels read as rack-device plates, not SaaS bubbles */
  --r-lg: 8px;   /* modals — kept in the enterprise band, no giant SaaS curves */
  --r-pill: 9999px;
  /* Status geometry is SQUARE, and that is the SimpleVirt identity decision. A
     fully-rounded tinted chip with a dot in it is the common house style of
     virtualization consoles; it is what makes a screenshot read as generic.
     SimpleVirt states its status the way a rack
     device does: a hard-edged plate with a colour keyline down its leading edge.
     Same information, same colours, same semantics — different voice. */
  --r-status: 3px;
  /* back-compat aliases — old role names map onto the scale */
  --r-control: var(--r-sm);
  --r-card: var(--r-md);
  --r-modal: var(--r-lg);
  --radius: var(--r-md);

  --sidebar-w: 212px;
  --rail-w: 320px;        /* live inventory rail width (was a 320px literal) */

  /* System font first: it is hinted for each OS and renders crisp at standard
     DPI, where self-hosted Inter looked soft/blurry. Inter stays available (the
     @font-face above) as an opt-in for a high-DPI/brand build. */
  --font: "Inter", -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, Helvetica, Arial, sans-serif;
  /* Display stack for names, section heads and the dominant metric. One family:
     Inter carries both body and display, so hierarchy reads from size and weight
     rather than a second face — the "two-typeface dashboard" tell is gone. */
  --font-display: "Inter", -apple-system, "Segoe UI", Roboto, sans-serif;
  --mono: "IBM Plex Mono", ui-monospace, SFMono-Regular, Menlo, Consolas, "Liberation Mono", monospace;

  /* 4px spacing scale — one rhythm for padding/gap/margin */
  --sp-px: 2px;
  --sp-1: 4px;
  --sp-2: 8px;
  --sp-3: 12px;
  --sp-4: 16px;
  --sp-5: 20px;
  --sp-6: 24px;
  --sp-8: 32px;
  --sp-12: 48px;
  /* One shared card inset so every card surface breathes identically (was split
     between .card at 16/20 and .cmd-card at 12/16, which read as two kits). */
  --card-pad: var(--sp-4);

  /* modular type scale (13px base), weights, line-heights, caps tracking */
  --fs-10: 10px;   /* micro tags (e.g. networking neighbor kind/source) — below the caption floor */
  --fs-25: 11px;   /* sub-caption: fact-strip micro-labels, compact plates */
  --fs-50: 12px;   /* micro-labels, status plates, metadata */
  --fs-75: 13px;   /* body, tables, secondary metadata — the workhorse size */
  --fs-100: 14px;  /* section title, panel body emphasis */
  --fs-200: 15px;
  --fs-300: 16px;  /* subhead */
  --fs-400: 18px;
  --fs-500: 20px;  /* object / page title — semibold, carries the weight */
  --fs-600: 24px;  /* the important metric figure */
  --fs-700: 28px;
  --fs-800: 34px;  /* hero figures — calmed down from the dashboard 40px */
  --lh-tight: 1.2;
  --ls-tight: -0.01em; /* display headings + hero numbers */
  --lh-snug: 1.35;
  --lh-normal: 1.5;
  --fw-regular: 400;
  --fw-medium: 500;
  --fw-semibold: 600;
  --fw-bold: 700;
  --ls-caps: 0; /* labels are sentence case — no tracking */ /* one tracking for every uppercase micro-label — a touch open reads intentional at 11-12px */

  /* motion — one duration + easing vocabulary */
  --dur-1: 90ms;   /* press */
  --dur-2: 140ms;  /* hover / control state */
  --dur-3: 200ms;  /* overlay / toast enter */
  --dur-4: 260ms;  /* content entrance */
  --ease-standard: cubic-bezier(0.2, 0, 0, 1);
  --ease-entrance: cubic-bezier(0.16, 1, 0.3, 1);
  --ease-exit: cubic-bezier(0.4, 0, 1, 1);

  /* on-accent / on-danger foreground, so solid controls are themable */
  --accent-fg: #ffffff;
  --danger-fg: #ffffff;

  /* skeleton shimmer sheen (theme-aware) */
  --skeleton-sheen: rgba(255, 255, 255, 0.06);

  /* elevation ladder — two-stop (contact + ambient); overlays only */
  --shadow-xs: 0 1px 2px rgba(0, 0, 0, 0.4);
  --shadow-sm: 0 1px 2px rgba(0, 0, 0, 0.4), 0 3px 8px rgba(0, 0, 0, 0.45);
  --shadow-md: 0 6px 16px -2px rgba(0, 0, 0, 0.5), 0 2px 4px -1px rgba(0, 0, 0, 0.42);
  --shadow-lg: 0 12px 32px -8px rgba(0, 0, 0, 0.55), 0 4px 8px -2px rgba(0, 0, 0, 0.4);
  --shadow-xl: 0 24px 64px -16px rgba(0, 0, 0, 0.7), 0 2px 8px rgba(0, 0, 0, 0.35);
  --shadow: var(--shadow-xl); /* back-compat alias */

  /* The MIDDLE of the elevation ladder, which the product was missing: resting
     content panels. A 1px top inner-highlight (a machined light-catching edge,
     never a glow — alpha kept ≤0.05) plus a real contact shadow that seats the
     panel on the canvas. This is the dark-theme depth that makes a card read as a
     crafted plate rather than a region painted on black. Light theme owns its own
     card shadow, so these are applied under a dark-only guard. */
  /* Resting panels get ONLY a machined inset top-highlight + their hairline (no
     drop-shadow): shadows are reserved for true overlays (menus/modals/toasts). */
  --shadow-rest: inset 0 1px 0 rgba(255, 255, 255, 0.045);
  --shadow-rest-hover: inset 0 1px 0 rgba(255, 255, 255, 0.07);
  /* Bottom rung: a carved/pressed-in surface (recessed stat tiles, meter tracks). */
  --shadow-inset: inset 0 1px 2px rgba(0, 0, 0, 0.35);
}

/*
 * Light theme. The topbar toggle (and, in the preview, the artifact viewer)
 * stamps data-theme="light" on <html>; every surface/text/status token is
 * remapped so all components restyle through the same variables. Neutrals
 * carry a slight cool bias toward the accent so they read as chosen, not
 * default grey.
 */
:root[data-theme="light"] {
  /* A soft light-grey canvas (~L0.88) so the white rails/cards read as distinct
     raised surfaces. The three neutral surface roles stay clearly separated —
     inset (recessed) < canvas < hover (interactive) < white (raised) — the same
     "surfaces come forward" logic as dark, inverted for light. */
  /* A genuinely present neutral canvas (~L0.90) — deepened from #eff1f4 so a white
     card/rail reads as a clearly RAISED surface, not a panel that dissolves into an
     almost-white page. Depth = canvas darker than surface + a deliberate card edge. */
  --bg: #e7eaf0;        /* light neutral workspace */
  --bg-rail: #ffffff;
  --bg-raised: #ffffff;
  /* These four tokens were introduced by the dark near-black refactor; they MUST be
     remapped for light too or they fall through to the dark near-black values and
     render a dark nav/inventory/panel on a white page. */
  --bg-nav: #ffffff;    /* primary navigation — white chrome rail on the grey canvas */
  --bg-inventory: #ffffff; /* inventory navigator */
  --bg-panel: #ffffff;  /* panels/cards/tables surface */
  --bg-elevated: #ffffff; /* modals, dropdowns, popovers */
  --bg-hover: #dce1e8;  /* interactive: a clear step over the canvas */
  --bg-inset: #eef0f4;  /* recessed (inputs, meter tracks) — see the light th override for the table header band */
  --bg-selected: #dbe6fb; /* selected row/nav: a soft accent wash, not a saturated block */
  --folder-band: #e2e5ec; /* VM-folder group header: a clear gray divider band */

  --border-subtle: #dfe2e8; /* hairline within a surface (row separators) */
  --border: #c4c9d1;    /* default edge — strong enough that a white card reads as a card */
  --border-strong: #aeb6c2;
  --border-focus: #8b97a9;
  --border-panel: #d4d8e0; /* light panel edge — the white card already lifts via its shadow */

  /* Three clearly-separated readable levels on white (all clear AA): object names,
     then metadata/labels, then still-readable muted detail. No near-invisible grey. */
  --text: #10141b;
  --text-dim: #3c4655;    /* secondary — metadata, descriptions, labels */
  --text-muted: #4d5766;  /* muted — less-important detail, still readable (~7:1 on white) */
  --text-disabled: #949cab;

  /* SIMPLEVIRT INDIGO, and this token is the single biggest reason a screenshot of
     this product used to read as generic. #2f6fd6 was, to within a few
     degrees of hue, the default blue every enterprise console reaches
     for. Moving to a deeper indigo keeps the authority a control plane needs
     while giving SimpleVirt its own identity. Contrast on white is 7.3:1, so links
     and primary buttons stay AA at body size. */
  --accent: #1f6feb;
  --accent-hover: #195fce;
  --accent-press: #154fab;
  --accent-tint: rgba(31, 111, 235, 0.11);
  /* 42% alpha composited to 1.8:1 on white — see the dark token. Opaque: 4.6:1. */
  --focus-ring: #1f6feb;
  --cyan: #0891b2;
  --cyan-tint: rgba(8, 145, 178, 0.1);
  --glow-accent: 0 2px 8px -2px rgba(31, 111, 235, 0.28);
  /* Primary-button fill. Defined for light too (not only dark) so the CTA tracks
     the light --accent family instead of falling through to the dark #2563eb —
     the generic blue the accent comment above says the product moved off. */
  --accent-solid: #1f6feb;
  --accent-solid-hover: #195fce;

  /* Category keyline (light) — NEUTRALIZED to match the dark redesign: colour is
     reserved for status/interaction, so category tiles are told apart by icon +
     label, not a decorative hue. One muted cool-steel, readable on white for a
     3px keyline and a glyph. */
  --cat: #5b6b85;

  /* Darkened from #1a8f4c: at 11px on its own 14% tint that measured 3.11:1,
     below the 4.5:1 WCAG 2.2 AA floor for small text. Every ok-toned badge,
     dot and meter derives from this token, so one change fixes all of them. */
  --ok: #10683a;
  --ok-tint: rgba(16, 104, 58, 0.14);
  --warn: #986510;
  --warn-fg: #ffffff;
  --warn-tint: rgba(168, 111, 24, 0.16);
  --err: #cc3a30;
  --err-tint: rgba(204, 58, 48, 0.13);
  /* Text on an accent tint (the info badge, banner, pill and "expected" lines) measured 3.4:1
     with the brand blue. This deeper blue keeps the hue and reads 5.5:1 on the tint. */
  --info: #0b4fb3;
  --grey: #616b78;
  --muted-chip-fg: #525c69; /* light-theme muted-chip text; bg inherits the base tint (keeps AA) */
  --track: #c9cfd9;
  --row-hover: rgba(16, 20, 27, 0.05);

  /* elevation ladder — light (softer, cooler ambient) */
  --shadow-xs: 0 1px 2px rgba(16, 24, 40, 0.06);
  --shadow-sm: 0 1px 2px rgba(16, 24, 40, 0.06), 0 2px 6px rgba(16, 24, 40, 0.07);
  --shadow-md: 0 4px 12px -2px rgba(16, 24, 40, 0.1), 0 2px 4px -1px rgba(16, 24, 40, 0.06);
  --shadow-lg: 0 12px 28px -8px rgba(16, 24, 40, 0.14), 0 4px 8px -2px rgba(16, 24, 40, 0.08);
  --shadow-xl: 0 24px 56px -16px rgba(16, 24, 40, 0.2), 0 2px 8px rgba(16, 24, 40, 0.08);
  --shadow: var(--shadow-xl);

  /* skeleton sheen — dark ripple over light surfaces */
  --skeleton-sheen: rgba(15, 23, 42, 0.05);

  /* The top bar does not follow the theme — see the token block in :root. Held
     at the same values so the brand surface is identical in light and dark. */
  --topbar-bg: #06090d; /* nearly black — one restrained bottom border */
  --topbar-text: #e9edf2;
  --topbar-text-dim: #8a95a1;
  --topbar-border: #1b2530;
  --topbar-hover: rgba(255, 255, 255, 0.055);
  --topbar-brand: #5c9bff;
  --topbar-wordmark: #2b7bf5; /* the "Virt" of the wordmark: the logo's blue, 4.9:1 on the near-black bar */
}

/* In light mode the white cards/tiles get real separation from the grey canvas —
   a soft two-stop float (overlays excepted) so panels read as raised. */
:root[data-theme="light"] .card,
:root[data-theme="light"] .cmd-kpi,
:root[data-theme="light"] .kpi-tile,
:root[data-theme="light"] .table-wrap,
:root[data-theme="light"] .gauge-card,
:root[data-theme="light"] .rbars,
:root[data-theme="light"] .host-identity,
:root[data-theme="light"] .upload-progress {
  box-shadow: 0 1px 2px rgba(16, 20, 27, 0.05), 0 2px 6px rgba(16, 20, 27, 0.06);
}

* {
  box-sizing: border-box;
}

html,
body {
  margin: 0;
  padding: 0;
}

body {
  background: var(--bg);
  color: var(--text);
  font-family: var(--font);
  font-size: var(--fs-100);
  line-height: 1.5;
  min-height: 100vh;
  /* Native subpixel rendering — crisper than grayscale antialiasing, which
     rendered the webfont thin/blurry. font-optical-sizing/feature-settings
     removed for the same reason. */
}

h1,
h2,
h3 {
  margin: 0;
  font-weight: 600;
}

button {
  font-family: inherit;
}

.hidden {
  display: none !important;
}

.num,
.cell-mono,
.rbar-pct,
.stat-value,
.gauge-pct,
.kpi-stat {
  font-variant-numeric: tabular-nums;
}

:focus-visible {
  outline: 2px solid var(--focus-ring);
  outline-offset: 2px;
}

/* ---------- app shell (fixed grid; only content scrolls) ---------------- */

.shell {
  display: grid;
  grid-template-columns: 1fr;
  grid-template-rows: 1fr;
  height: 100vh;
  overflow: hidden;
}

.shell.authed {
  grid-template-columns: var(--sidebar-w) 1fr;
  grid-template-rows: auto auto 1fr auto;
  grid-template-areas:
    "topbar  topbar"
    "banner  banner"
    "sidebar main"
    "dock    dock";
}

.shell.authed .topbar { grid-area: topbar; }
.shell.authed .banner { grid-area: banner; }
.shell.authed .sidebar { grid-area: sidebar; }
.shell.authed .main { grid-area: main; }
.shell.authed .tasks-dock { grid-area: dock; }

/* ---------- top bar ------------------------------------------------------ */

.topbar {
  display: flex;
  align-items: center;
  gap: 18px;
  padding: 0 var(--sp-4);
  height: 52px;
  background: var(--bg-rail);
  border-bottom: var(--sp-px) solid var(--border);
}

.brand {
  display: flex;
  align-items: center;
  gap: var(--sp-2);
  font-size: var(--fs-300);
  font-weight: 700;
  letter-spacing: 0.02em;
}

.brand-mark {
  color: var(--accent);
  width: 22px;
  height: 22px;
  flex: 0 0 22px;
  display: block;
}
/* The topbar is a FIXED dark surface: --topbar-bg is #0b1220 in BOTH themes.
   --accent, however, follows the page theme, and in light theme that is the deep
   #1f6feb — which on a near-black bar is a visibly different blue from the mark in
   the browser tab and on the login canvas. The
   same mark must not be two colours depending on a theme the bar does not
   follow. Anything sitting on this bar takes the dark-surface accent. */
.cmd-topbar .brand-mark { color: var(--topbar-brand); }

.brand-name {
  color: var(--text);
}

/* The console bar's mark is a bare <span> with no SVG, so it is painted as a
   gradient chip. It needs its own class: while it shared .brand-mark with the
   SVG lockup above, this rule came second at equal specificity and silently won
   everywhere, shrinking the real mark to a 14px block. */
.brand-chip {
  width: 14px;
  height: 14px;
  border-radius: 4px;
  background: linear-gradient(135deg, var(--accent), #7bc4ff);
  display: inline-block;
}
/* The console bar's brand mark: the same shield the app uses, so a popped-out
   console reads as SimpleVirt rather than a generic tab. */
.brand-chip-mark { color: var(--accent); flex: none; }

/* The host a single-host local datastore belongs to, shown dim next to its name
   so several same-named "default" pools read as distinct rows. */
.ds-host-sub { color: var(--text-muted); font-size: var(--fs-50); }

/* ---------- VM list: summary strip, OS glyph, host ref, cards ----------- *
 * The fleet's largest table gets an at-a-glance header and a leading guest-OS
 * mark per row, and an opt-in cards view — all identity-preserving (square
 * plates, neutral mix, blue only for links/selected). */

/* Guest-OS glyph leading the name cell (and the card). Neutral, flex:none so it
   never pushes the name; sized to the row. */
.cmd-vm-os {
  display: inline-flex;
  align-items: center;
  flex: none;
  color: var(--text-dim);
  margin-right: 6px;
  vertical-align: middle;
}
.cmd-vm-os svg { width: 15px; height: 15px; }
/* Host as a status dot + link, so host health is scannable inline and one click
   deep. The dot carries the host's tone; the link is the only interaction colour. */
.cmd-vm-hostref { display: inline-flex; align-items: center; gap: 6px; min-width: 0; }
.cmd-vm-hostref .dot { flex: none; }

/* View toggle (Table | Cards): the shared .segmented, but inline and pushed to
   the toolbar's right, not full-width. */
.cmd-view-seg { margin: 0 0 0 auto; width: max-content; }
.cmd-view-seg .seg-btn { flex: 0 0 auto; padding: 5px var(--sp-3); }

/* Fleet summary strip above the table: the power-state mix on the left, allocated
   vCPU / memory / host span as tiles on the right. */
.cmd-vmsum {
  display: grid;
  grid-template-columns: minmax(0, 1.3fr) minmax(0, 2fr);
  gap: var(--sp-5);
  align-items: center;
  background:
    linear-gradient(180deg, color-mix(in srgb, var(--bg-raised) 55%, transparent), transparent 90px),
    var(--bg-panel);
  border: 1px solid var(--border);
  border-radius: var(--r-lg);
  padding: var(--sp-4) var(--sp-5);
  margin-bottom: var(--sp-4);
  box-shadow: var(--shadow-rest);
}
.cmd-vmsum-mix { min-width: 0; display: flex; flex-direction: column; gap: var(--sp-2); }
.cmd-vmsum-label { font-size: var(--fs-50); letter-spacing: var(--ls-caps); color: var(--text-muted); font-weight: 600; }
.cmd-vmsum-none { color: var(--text-dim); font-size: var(--fs-100); }
.cmd-vmsum-note { margin: 0; font-size: var(--fs-50); color: var(--text-muted); }
.cmd-vmsum-tiles { display: grid; grid-template-columns: repeat(auto-fit, minmax(96px, 1fr)); gap: var(--sp-4); }
.cmd-vmsum-tile { min-width: 0; }
.cmd-vmsum-val { font-size: var(--fs-500); font-weight: var(--fw-bold); letter-spacing: var(--ls-tight); line-height: 1.1; color: var(--text); font-variant-numeric: tabular-nums; margin-top: 2px; }
.cmd-vmsum-sub { font-size: var(--fs-50); color: var(--text-dim); line-height: var(--lh-snug); }
@media (max-width: 900px) {
  .cmd-vmsum { grid-template-columns: 1fr; gap: var(--sp-4); }
}

/* Cards view — a responsive grid of VM tiles, same data and verbs as the rows. */
.cmd-vm-cards { display: grid; grid-template-columns: repeat(auto-fill, minmax(264px, 1fr)); gap: var(--sp-3); }
.cmd-vmcard {
  display: flex;
  flex-direction: column;
  gap: var(--sp-3);
  background: var(--bg-panel);
  border: 1px solid var(--border);
  border-radius: var(--r-md);
  padding: var(--sp-3) var(--sp-4);
  box-shadow: var(--shadow-rest);
  transition: border-color var(--dur-2) var(--ease-standard), box-shadow var(--dur-2) var(--ease-standard);
}
.cmd-vmcard:hover { border-color: var(--border-strong); box-shadow: var(--shadow-hover, var(--shadow-rest)); }
.cmd-vmcard-lost { opacity: 0.72; }
.cmd-vmcard-head { display: flex; align-items: flex-start; gap: var(--sp-2); }
.cmd-vmcard-os { margin-right: 0; margin-top: 2px; }
.cmd-vmcard-os svg { width: 18px; height: 18px; }
.cmd-vmcard-title { display: flex; align-items: center; gap: var(--sp-2); flex-wrap: wrap; min-width: 0; flex: 1; }
.cmd-vmcard-name { font-weight: 600; font-size: var(--fs-100); }
.cmd-vmcard-state { margin-left: auto; flex: none; }
.cmd-vmcard-facts { display: grid; grid-template-columns: 1fr 1fr; gap: var(--sp-1) var(--sp-3); margin: 0; }
.cmd-vmcard-facts dt { font-size: var(--fs-50); letter-spacing: var(--ls-caps); color: var(--text-muted); }
.cmd-vmcard-facts dd { margin: 0; font-size: var(--fs-75); color: var(--text); font-variant-numeric: tabular-nums; min-width: 0; overflow: hidden; text-overflow: ellipsis; }
.cmd-vmcard-foot { display: flex; align-items: center; justify-content: flex-end; gap: var(--sp-2); border-top: 1px solid var(--border-subtle); padding-top: var(--sp-2); }
.cmd-vmcard-acts { display: flex; align-items: center; gap: var(--sp-2); }
.cmd-vm-cards-cap { grid-column: 1 / -1; padding-top: var(--sp-2); }

/* VM folders on the VMs tab: section headers group VMs; a per-row picker moves
   one between folders. A folder is a view — nothing here powers a VM on or off. */
.cmd-vms-table { width: 100%; }
/* A folder is a quiet band, not a loud row: the accent keyline marks it as a
   grouping (the same motif as cards and bands), the count is a small pill, and
   the Rename/Delete verbs stay hidden until the band is hovered so a list of
   folders reads as names, not a wall of buttons. */
.cmd-vms-table .cmd-folder-head td {
  background: var(--folder-band);
  border-top: var(--sp-px) solid var(--border);
  border-bottom: var(--sp-px) solid var(--border);
  padding: var(--sp-2) var(--sp-3);
}
.cmd-folder-head-cell { display: flex; align-items: center; gap: var(--sp-2); }
.cmd-folder-head-cell::before {
  content: "";
  width: 3px;
  align-self: stretch;
  min-height: 15px;
  border-radius: 1px;
  background: var(--accent);
  flex: none;
}
.cmd-folder-glyph { color: var(--text-dim); display: inline-flex; flex: none; }
.cmd-folder-name { font-weight: 700; font-size: var(--fs-75); letter-spacing: 0.02em; color: var(--text); }
.cmd-folder-count {
  color: var(--text-dim);
  font-size: var(--fs-50);
  font-variant-numeric: tabular-nums;
  background: var(--bg-raised);
  border: 1px solid var(--border);
  border-radius: var(--r-pill);
  padding: var(--sp-px) var(--sp-2);
}
.cmd-folder-controls {
  margin-left: auto;
  display: inline-flex;
  gap: var(--sp-1);
  opacity: 0;
  transition: opacity var(--dur-2) var(--ease-standard);
}
.cmd-folder-head:hover .cmd-folder-controls,
.cmd-folder-controls:focus-within { opacity: 1; }
.cmd-folder-controls .btn {
  height: 24px;
  padding: 0 var(--sp-2);
  font-size: var(--fs-75);
  background: transparent;
  border-color: transparent;
  color: var(--text-dim);
}
.cmd-folder-controls .btn:hover { background: var(--row-hover); border-color: var(--border-strong); color: var(--text); }
.cmd-folder-controls .btn-danger { color: var(--err); }
.cmd-folder-empty { color: var(--text-muted); font-size: var(--fs-75); padding: var(--sp-2) var(--sp-3) var(--sp-2) var(--sp-6); font-style: italic; }

/* Per-row folder picker: a quiet inline control that only draws a border when
   the row is hovered or it is being used — so a table of them is calm text, not
   a column of loud dropdowns. */
.cmd-folder-pick {
  border: 1px solid transparent;
  background: transparent;
  color: var(--text-dim);
  font-size: var(--fs-50);
  padding: var(--sp-px) 6px;
  border-radius: 5px;
  cursor: pointer;
  max-width: 140px;
  transition: background var(--dur-2) var(--ease-standard), border-color var(--dur-2) var(--ease-standard), color var(--dur-2) var(--ease-standard);
}
tr:hover .cmd-folder-pick { color: var(--text); }
.cmd-folder-pick:hover,
.cmd-folder-pick:focus {
  border-color: var(--border-strong);
  background: var(--bg-inset);
  color: var(--text);
}
.cmd-folder-pick { max-width: 140px; font-size: var(--fs-75); padding: var(--sp-px) 6px; }

.crumbs {
  display: flex;
  align-items: center;
  gap: 6px;
  font-size: var(--fs-75);
  color: var(--text-muted);
  overflow: hidden;
}

.crumb {
  background: none;
  border: none;
  color: var(--text-dim);
  font-size: var(--fs-75);
  padding: var(--sp-px) var(--sp-1);
  border-radius: 4px;
  cursor: pointer;
  white-space: nowrap;
}

.crumb:hover {
  color: var(--text);
}

.crumb.current {
  color: var(--text);
  font-weight: 600;
  cursor: default;
}

.crumb-sep {
  color: var(--text-disabled);
  user-select: none;
}

.topbar-right {
  margin-left: auto;
  display: flex;
  align-items: center;
  gap: var(--sp-3);
}

.theme-toggle {
  padding: var(--sp-1) var(--sp-2);
}

.theme-ic {
  width: 15px;
  height: 15px;
  display: block;
}

.theme-ic-moon { display: none; }
:root[data-theme="light"] .theme-ic-sun { display: none; }
:root[data-theme="light"] .theme-ic-moon { display: block; }

.refresh-note {
  font-size: var(--fs-50);
  color: var(--text-muted);
  font-variant-numeric: tabular-nums;
}

.current-user {
  color: var(--topbar-text-dim);
  font-size: var(--fs-75);
}

/* Identity chip in the top-right — an initials avatar anchors 'signed in as…'
   and balances the brand mark on the left. */
.cmd-user {
  display: inline-flex;
  align-items: center;
  gap: var(--sp-2);
}
.cmd-user-avatar {
  width: 28px;
  height: 28px;
  border-radius: 50%;
  display: grid;
  place-items: center;
  font-size: var(--fs-50);
  font-weight: 700;
  line-height: 1;
  letter-spacing: 0.01em;
  /* A solid, saturated brand orb reads as a real avatar; the old washed
     translucent gradient over the dark topbar looked faded and cheap. White
     initials over a blue that deepens toward the bottom keeps them legible. */
  color: #fff;
  background: linear-gradient(
    150deg,
    color-mix(in srgb, var(--topbar-brand) 82%, white),
    color-mix(in srgb, var(--topbar-brand) 70%, #0b1220)
  );
  border: 1px solid color-mix(in srgb, var(--topbar-brand) 55%, white 12%);
  box-shadow:
    0 1px 2px rgba(0, 0, 0, 0.4),
    inset 0 1px 0 rgba(255, 255, 255, 0.2);
}
.cmd-user-avatar:empty {
  display: none;
}

/* ---------- banner ------------------------------------------------------- */

.banner {
  background: var(--err-tint);
  color: var(--err);
  border-bottom: var(--sp-px) solid color-mix(in srgb, var(--err) 35%, transparent);
  padding: var(--sp-2) var(--sp-5);
  font-size: var(--fs-75);
  text-align: center;
}

/* ---------- sidebar navigator ------------------------------------------- */

.sidebar {
  background: var(--bg-rail);
  border-right: var(--sp-px) solid var(--border);
  padding: var(--sp-2) 0 var(--sp-3);
  overflow-y: auto;
  display: flex;
  flex-direction: column;
}

.side-host {
  display: flex;
  align-items: center;
  gap: var(--sp-2);
  padding: 10px var(--sp-4) var(--sp-3);
  border-bottom: var(--sp-px) solid var(--border);
  margin-bottom: var(--sp-2);
}

.side-host .brand-mark {
  width: 24px;
  height: 24px;
  flex: 0 0 24px;
}

.side-host-name {
  font-size: var(--fs-100);
  font-weight: 600;
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}

.side-host-sub {
  display: flex;
  align-items: center;
  gap: var(--sp-2);
  font-size: var(--fs-50);
  color: var(--text-muted);
  margin-top: var(--sp-px);
}

.side-section {
  font-size: var(--fs-50);
  font-weight: 500;

  letter-spacing: 0.05em;
  color: var(--text-muted);
  padding: 10px var(--sp-5) 6px;
}

.side-btn {
  display: flex;
  align-items: center;
  gap: var(--sp-3);
  background: none;
  border: none;
  border-left: var(--sp-px) solid transparent;
  color: var(--text-dim);
  text-align: left;
  font-size: var(--fs-100);
  padding: var(--sp-2) var(--sp-5);
  cursor: pointer;
  font-family: inherit;
  transition: background var(--dur-2) var(--ease-standard), color var(--dur-2) var(--ease-standard);
}

.side-ic {
  flex: 0 0 16px;
  opacity: 0.8;
}

.side-btn span {
  flex: 1 1 auto;
}

.side-count {
  font-size: var(--fs-50);
  color: var(--text-muted);
  font-variant-numeric: tabular-nums;
  min-width: 16px;
  text-align: right;
}

.side-btn:hover {
  color: var(--text);
  background: var(--bg-hover);
}

.side-btn:hover .side-ic {
  opacity: 1;
}

.side-btn.active {
  color: var(--text);
  background: var(--accent-tint);
  border-left-color: var(--accent);
  font-weight: 600;
}

.side-btn.active .side-ic {
  opacity: 1;
  color: var(--accent);
}

/* ---- sidebar VM quick-access list (shown under Virtual Machines) ---- */
.side-vms {
  display: flex;
  flex-direction: column;
  gap: var(--sp-px);
  margin: var(--sp-px) 0 6px;
  max-height: 42vh;
  overflow-y: auto;
}
.side-vm {
  display: flex;
  align-items: center;
  gap: var(--sp-2);
  width: 100%;
  text-align: left;
  padding: 6px var(--sp-3) 6px 34px;
  background: none;
  border: none;
  border-left: var(--sp-px) solid transparent;
  color: var(--text-dim);
  font: inherit;
  font-size: var(--fs-75);
  cursor: pointer;
}
.side-vm:hover {
  background: var(--bg-hover);
  color: var(--text);
}
.side-vm.active {
  color: var(--text);
  background: var(--accent-tint);
  border-left-color: var(--accent);
  font-weight: 600;
}
.side-vm-name {
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}
.side-vms-empty {
  padding: var(--sp-1) var(--sp-3) 6px 34px;
  font-size: var(--fs-75);
  color: var(--text-muted);
}

/* ---------- main content ------------------------------------------------- */

.main {
  overflow-y: auto;
  padding: var(--sp-6);
}

.view-header {
  display: flex;
  align-items: center;
  justify-content: space-between;
  margin-bottom: var(--sp-5);
  gap: var(--sp-4);
}

.view-title {
  font-size: var(--fs-500);
  font-weight: 600;
  letter-spacing: 0.01em;
}

.section-title {
  font-size: var(--fs-200);
  font-weight: 600;
  margin: var(--sp-5) 0 var(--sp-2);
}

.section-title:first-child {
  margin-top: 0;
}

.loading {
  color: var(--text-dim);
  padding: var(--sp-8) 0;
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: var(--sp-3);
}

.loading::before {
  content: "";
  width: 22px;
  height: 22px;
  border-radius: 50%;
  border: 2.5px solid var(--border-strong);
  border-top-color: var(--accent);
  animation: spin 0.7s linear infinite;
}

.empty {
  color: var(--text-dim);
  padding: var(--sp-8) 0;
  text-align: center;
}

@keyframes spin {
  to { transform: rotate(360deg); }
}

/* inline spinner utility (buttons, headers) */
.spinner {
  display: inline-block;
  width: 15px;
  height: 15px;
  border-radius: 50%;
  border: 2px solid var(--border-strong);
  border-top-color: var(--accent);
  animation: spin 0.7s linear infinite;
  vertical-align: -3px;
}

/* button in-flight state: hide the label, show a centered spinner */
.btn.is-loading {
  color: transparent !important;
  position: relative;
  pointer-events: none;
}

.btn.is-loading::after {
  content: "";
  position: absolute;
  top: 50%;
  left: 50%;
  width: 15px;
  height: 15px;
  margin: calc(-1 * var(--sp-2)) 0 0 calc(-1 * var(--sp-2));
  border-radius: 50%;
  border: 2.5px solid var(--border-focus);
  border-top-color: var(--accent);
  animation: spin 0.6s linear infinite;
}

.btn-primary.is-loading::after,
.btn-danger.is-loading::after,
.btn-danger-solid.is-loading::after {
  border-color: rgba(255, 255, 255, 0.28);
  border-top-color: #fff;
}

/* empty state block */
.empty-state {
  max-width: 340px;
  margin: var(--sp-12) auto;
  text-align: center;
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: var(--sp-2);
}

.empty-state .empty-ic {
  width: 48px;
  height: 48px;
  display: flex;
  align-items: center;
  justify-content: center;
  border-radius: var(--r-lg);
  background: var(--bg-inset);
  border: 1px solid var(--border);
  color: var(--text-muted);
  margin-bottom: var(--sp-1);
}

.empty-state .empty-ic svg {
  width: 24px;
  height: 24px;
}

/* A first-run landing wants more room than an in-card empty slot. The h3/p
   selectors are doubled (.empty-state.empty-state--wide) so they outrank the
   base .empty-state h3/p rules that follow, regardless of source order. */
.empty-state--wide {
  max-width: 520px;
  margin-top: 88px;
}
.empty-state.empty-state--wide h3 {
  font-size: var(--fs-500);
  letter-spacing: -0.01em;
}
.empty-state.empty-state--wide p {
  font-size: var(--fs-200);
  line-height: 1.6;
}

.empty-state h3 {
  font-size: var(--fs-200);
  font-weight: 600;
}

.empty-state p {
  margin: 0 0 var(--sp-2);
  font-size: var(--fs-100);
  color: var(--text-dim);
}

/* ---------- sticky detail sub-header ------------------------------------- */

.detail-head {
  display: flex;
  align-items: center;
  gap: var(--sp-3);
  margin-bottom: 18px;
  flex-wrap: wrap;
  position: sticky;
  top: calc(-1 * var(--sp-6));
  background: var(--bg);
  padding: var(--sp-2) 0;
  z-index: 5;
}

.detail-title {
  font-size: var(--fs-400);
  font-weight: 600;
}

.action-bar {
  display: flex;
  gap: var(--sp-2);
  margin-left: auto;
  flex-wrap: wrap;
}

/* ---------- host summary: identity + KPI tiles + gauges ----------------- */

.host-identity {
  display: flex;
  align-items: center;
  gap: 14px;
  margin-bottom: var(--sp-4);
}

.host-glyph {
  font-size: var(--fs-700);
  color: var(--accent);
  line-height: 1;
}

.host-glyph .brand-mark {
  width: 34px;
  height: 34px;
}

.host-name {
  font-size: var(--fs-300);
  font-weight: 700;
}

.host-sub {
  color: var(--text-dim);
  font-size: var(--fs-75);
  margin-top: var(--sp-px);
}

.host-accel {
  margin-left: auto;
}

/* A quiet neutral chip with a status dot — not a saturated status pill. */
.host-accel.badge {
  background: var(--bg-hover);
  border-color: var(--border-strong);
  color: var(--text-dim);
  font-weight: 500;
  height: 22px;
  padding: 0 var(--sp-3);
}

.host-accel.badge-ok::before { background: var(--ok); }
.host-accel.badge-warn::before { background: var(--warn); }

.kpi-row {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(150px, 1fr));
  gap: var(--sp-3);
  margin-bottom: var(--sp-4);
}

.kpi-tile {
  background: var(--bg-raised);
  border: 1px solid var(--border);
  border-radius: var(--r-card);
  padding: 14px var(--sp-4);
}

.kpi-title {
  font-size: var(--fs-50);
  font-weight: 600;

  letter-spacing: var(--ls-caps);
  color: var(--text-dim);
}

.kpi-stat {
  font-size: var(--fs-600);
  font-weight: 700;
  line-height: 1.15;
  margin-top: 6px;
}

.kpi-sub {
  font-size: var(--fs-75);
  color: var(--text-dim);
  margin-top: var(--sp-px);
}

.kpi-stat.stat-warn { color: var(--warn); }
.kpi-stat.stat-err { color: var(--err); }
.kpi-stat.stat-ok { color: var(--ok); }

/* reusable capacity bars (host / vm / datastore) */
.rbars {
  display: flex;
  flex-direction: column;
  gap: 18px;
  margin-bottom: var(--sp-4);
}

.rbar-head {
  display: flex;
  justify-content: space-between;
  font-size: var(--fs-75);
  margin-bottom: 6px;
}

.rbar-label {
  font-weight: 600;
}

.rbar-pct {
  color: var(--text-dim);
  font-family: var(--mono);
}

.rbar-track {
  height: 8px;
  border-radius: var(--r-pill);
  background: var(--bg-inset);
  border: 1px solid var(--border);
  overflow: hidden;
}

.rbar-fill {
  height: 100%;
  border-radius: var(--r-pill);
  background: var(--accent);
  transition: width 0.4s ease;
}

.rbar-legend {
  display: flex;
  gap: var(--sp-5);
  margin-top: 6px;
  font-size: var(--fs-75);
  color: var(--text-dim);
}

.rbar-cap {
  margin-left: auto;
}

.info-grid {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(300px, 1fr));
  gap: var(--sp-4);
}

/* ---------- legacy conic gauges (still used where present) -------------- */

.gauge-row {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(210px, 1fr));
  gap: var(--sp-4);
  margin-bottom: var(--sp-4);
}

/* ---------- cards -------------------------------------------------------- */

.card-grid {
  display: grid;
  grid-template-columns: repeat(auto-fill, minmax(320px, 1fr));
  gap: var(--sp-4);
}

/* A card is a SURFACE, not a container with a drawn edge. The 1px box around
   every panel — on a light canvas, with a matching box around every table and
   every section — is the single most recognisable trait of the heavily-boxed
   enterprise look, and it makes a dense page read as a pile of unrelated rectangles. SimpleVirt
   separates surfaces by elevation and whitespace instead: a hairline that only
   asserts itself where two surfaces actually meet, plus the softest shadow in
   the ladder. Same grouping, far fewer lines. */
.card {
  /* Flat command surface: a solid near-canvas fill and one restrained hairline.
     No gradient, no glow, no top-highlight — surfaces separate by border and
     whitespace, so a dense page reads as one continuous console, not a pile of
     floating instrument plates. */
  background: var(--bg-panel);
  border: 1px solid var(--border);
  border-radius: var(--r-card);
  padding: var(--card-pad);
}
/* Light mode: flat white plate with a crisp edge — dark's gradient/glow would
   read as grime on white. */
:root[data-theme="light"] .card {
  background: var(--bg-raised);
  box-shadow: var(--shadow-sm), 0 0 0 1px var(--border);
}
:root[data-theme="light"] .table-wrap {
  box-shadow: var(--shadow-sm), 0 0 0 1px var(--border);
}
/* Dark theme: lift content panels off the near-black canvas with a crisper edge +
   the resting-panel shadow (machined top-highlight + contact shadow). This is the
   middle elevation tier the product was missing, and the single biggest fix for the
   "flat / looks simple" read. Scoped to dark; light keeps its own shadow above. */
:root:not([data-theme="light"]) .card {
  border-color: var(--border-panel);
  box-shadow: var(--shadow-rest);
}
/* Content cards respond to hover like the KPI tiles do (a 1px lift + a deeper
   shadow), instead of only swapping a border colour and feeling inert. */
:root:not([data-theme="light"]) .cmd-card:hover {
  border-color: var(--border-strong);
  box-shadow: var(--shadow-rest-hover);
  transform: translateY(-1px);
}
@media (prefers-reduced-motion: reduce) {
  .cmd-card:hover { transform: none; }
}

.card-title {
  font-size: var(--fs-75);
  font-weight: 600;

  letter-spacing: 0.02em;
  color: var(--text-dim);
  margin-bottom: var(--sp-3);
}

.stat-row .stat-label {
  color: var(--text-dim);
}

.stat-row .stat-value {
  font-size: var(--fs-75);
}

.meter {
  /* Slim, lightly-rounded channel that echoes the square status-plate identity
     (--r-status), not a pill. A pressed-in track reads as a carved gauge. */
  height: 6px;
  border-radius: var(--r-status);
  background: var(--track);
  border: 0;
  box-shadow: var(--shadow-inset);
  overflow: hidden;
  margin: var(--sp-2) 0 var(--sp-1);
}

.meter-fill {
  height: 100%;
  border-radius: var(--r-status);
  /* Utilization is a "how full" reading, not an interaction — so a normal meter is
     NEUTRAL steel, and colour appears only when a threshold is crossed (warn/crit).
     This pulls most of the decorative blue off dashboards. A true progress/in-flight
     meter opts back into the accent with .meter-accent. */
  background: var(--grey);
  transition: width var(--dur-4) var(--ease-standard), background var(--dur-3) var(--ease-standard);
}

.meter-fill.meter-warn { background: var(--warn); }
.meter-fill.meter-crit { background: var(--err); }
.meter-fill.meter-ok { background: var(--ok); }
.meter-fill.meter-accent { background: var(--accent); }

/* ---------- ISO upload progress ----------------------------------------- */

.upload-progress {
  background: var(--bg-raised);
  border: 1px solid var(--border);
  border-radius: var(--r-card);
  padding: var(--sp-3) var(--sp-4);
  margin-bottom: var(--sp-4);
}

.upload-label {
  font-size: var(--fs-100);
  color: var(--text-dim);
}

/* Live file-upload bar: percent, bytes, speed and time-left for a datastore upload. */
.cmd-up {
  background: var(--bg-raised);
  border: 1px solid var(--border);
  border-radius: var(--r-card);
  padding: 10px 14px;
  margin-bottom: var(--sp-4);
  display: flex;
  flex-direction: column;
  gap: 7px;
}
.cmd-up-head { display: flex; justify-content: space-between; align-items: baseline; gap: 12px; }
.cmd-up-name {
  font-size: var(--fs-100); color: var(--text); font-weight: 500;
  overflow: hidden; text-overflow: ellipsis; white-space: nowrap; min-width: 0;
}
.cmd-up-pct { font-size: var(--fs-100); color: var(--accent-hover); font-weight: 600; font-variant-numeric: tabular-nums; flex-shrink: 0; }
.cmd-up-track { height: 6px; border-radius: var(--r-pill); background: var(--border); overflow: hidden; }
.cmd-up-fill { height: 100%; width: 0; background: var(--accent); border-radius: var(--r-pill); transition: width var(--dur-3) var(--ease-standard); }
.cmd-up-stat { font-size: var(--fs-75); color: var(--text-muted); font-variant-numeric: tabular-nums; }
.cmd-up.is-done .cmd-up-fill { background: var(--ok); }
.cmd-up.is-done .cmd-up-pct { color: var(--ok); }
.cmd-up.is-err .cmd-up-fill { background: var(--err); }
.cmd-up.is-err .cmd-up-name,
.cmd-up.is-err .cmd-up-stat { color: var(--err); }
/* "Still reading…" hint under the skeleton while a slow shared-storage listing attaches. */
.cmd-files-slow { padding: 10px 2px 2px; font-size: var(--fs-75); color: var(--text-muted); }

/* ---------- status dots -------------------------------------------------- */

/* THE status mark, and there is only one.
 *
 * This was a round bead. The badges and object pills moved to a squared leading
 * keyline, which left the product speaking two status languages at once — a bar
 * on a badge, a circle in the tree, a circle in the task list, a circle floating
 * beside a hostname on the Connections card. Two marks for one meaning is what
 * reads as unfinished, and the loose circle next to a link read worst of all.
 *
 * Now every status indicator in the product is this mark, at the same width, in
 * the same colour vocabulary. It is deliberately NOT round: round beads are the
 * house style of every other console in this category. */
.dot {
  display: inline-block;
  width: 3px;
  height: 10px;
  border-radius: 1px;
  background: var(--grey);
  flex: 0 0 3px;
  position: relative;
  box-shadow: none;
}

.dot-ok { background: var(--ok); }
.dot-warn { background: var(--warn); }
.dot-err { background: var(--err); }
.dot-muted { background: var(--grey); }
:root[data-theme="light"] .dot { box-shadow: inset 0 0 0 1px rgba(0, 0, 0, 0.26); }

.dot-pulse {
  animation: dot-pulse 2s ease-in-out infinite;
}

/* A calm breath (not a hard blink) for a genuinely transient "reconnecting" host. */
@keyframes dot-pulse {
  0%, 100% { opacity: 1; }
  50% { opacity: 0.5; }
}

.dot-label {
  display: inline-flex;
  align-items: center;
  gap: var(--sp-2);
}

/* ---------- badges ------------------------------------------------------- */

.badge {
  display: inline-flex;
  align-items: center;
  gap: var(--sp-2);
  height: 22px;
  font-size: var(--fs-50);
  font-weight: 600;
  /* Uppercase with tracking reads as a STATE CODE rather than as prose. It is
     the second half of the identity: "ONLINE" is an instrument reading,
     "online" in a rounded bubble is a web app. */

  letter-spacing: var(--ls-caps);
  font-variant-numeric: tabular-nums;
  padding: 0 var(--sp-2) 0 var(--sp-2);
  border-radius: var(--r-status);
  border: 1px solid transparent;
  white-space: nowrap;
}

/* The keyline. It replaces the status dot: same colour, same meaning, but a
   vertical bar on the leading edge instead of a bead floating in a bubble. */
.badge::before {
  content: "";
  width: 3px;
  height: 12px;
  border-radius: 1px;
  background: currentColor;
  box-shadow: none;
}

.badge-ok {
  color: var(--ok);
  background: var(--ok-tint);
  border-color: color-mix(in srgb, var(--ok) 30%, transparent);
}

.badge-muted {
  color: var(--muted-chip-fg); /* the neutral "Checking / Unknown" badge — one tokenized muted grey, theme-aware */
  background: var(--muted-chip-bg);
  border-color: color-mix(in srgb, var(--grey) 30%, transparent);
}

.badge-warn {
  color: var(--warn);
  background: var(--warn-tint);
  border-color: color-mix(in srgb, var(--warn) 30%, transparent);
}

/* A badge that is itself an action (e.g. "No uplink — assign one"): button reset so a
   <button> carrying .badge classes reads as a clickable chip, with a clear hover. */
.cmd-badge-action {
  cursor: pointer;
  font: inherit;
  line-height: inherit;
}
.cmd-badge-action:hover { filter: brightness(1.12); }
.cmd-badge-action:focus-visible { outline: 2px solid var(--focus-ring); outline-offset: 1px; }

.badge-err {
  color: var(--err);
  background: var(--err-tint);
  border-color: color-mix(in srgb, var(--err) 30%, transparent);
}

/* EXPERIMENTAL (preview) capabilities. It is deliberately the BRAND tone, not an
   amber warning: a preview is not a fault, and painting nine shipped-but-unsupported
   capabilities in the same colour as a degraded host would teach an operator to
   ignore amber. It reads as "this is different", which is exactly the claim. */
.badge-preview {
  color: var(--accent);
  background: var(--accent-tint);
  border-color: color-mix(in srgb, var(--accent) 30%, transparent);
}

/* The panel a DISABLED preview shows instead of its body — one composed block,
   used identically by every flagged surface so the treatment never varies. */
.cmd-preview-notice {
  max-width: 62ch;
  padding: var(--sp-4);
  border-radius: var(--r-card);
  background: var(--bg-raised);
  box-shadow: 0 0 0 1px color-mix(in srgb, var(--border) 55%, transparent);
}
.cmd-preview-head {
  display: flex;
  align-items: center;
  gap: var(--sp-2);
  margin-bottom: var(--sp-3);
  flex-wrap: wrap;
}
.cmd-preview-title {
  font-size: var(--fs-100);
  font-weight: 600;
  color: var(--text);
}
.cmd-preview-msg {
  margin: 0 0 var(--sp-2);
  color: var(--text-dim);
  line-height: 1.55;
}
.cmd-preview-msg:last-child { margin-bottom: 0; }
/* The config key is an instruction to copy, so it is set in the mono face. */
.cmd-preview-how { font-family: var(--mono); font-size: var(--fs-50); }

/* The same EXPERIMENTAL mark at chooser scale — inside a tab button or a sub-rail
   entry, where a full .badge (22px, bordered, keylined) would deform the control.
   Deliberately the same accent tone and the same word, so it reads as one idiom. */
.cmd-exp-tag {
  font-size: var(--fs-10);
  font-weight: 600;

  letter-spacing: 0.06em;
  color: var(--accent);
  background: var(--accent-tint);
  border-radius: var(--r-sm);
  padding: 1px 4px;
  margin-left: var(--sp-2);
  white-space: nowrap;
}

/* ---------- tables ------------------------------------------------------- */

/* Matches .card: a hairline that reads as an edge without drawing a second box
   around a table that already sits on a surface. */
.table-wrap {
  background: var(--bg-raised);
  border: 1px solid transparent;
  border-radius: var(--r-card);
  box-shadow: 0 0 0 1px color-mix(in srgb, var(--border) 55%, transparent);
  overflow-x: auto;
}
/* A table nested inside a card is already on a raised surface — it must not
   restate the edge, or the de-boxing is undone one level down. */
.card .table-wrap,
.cmd-net-section .table-wrap {
  box-shadow: none;
  border-radius: var(--r-sm);
}

/* Fleet networking view: labeled sub-tables inside each host card. */
.cmd-net-section {
  margin-top: var(--sp-4);
}
.cmd-net-section:first-of-type {
  margin-top: var(--sp-3);
}
.cmd-net-title {
  font-size: var(--fs-75);
  font-weight: 700;
  letter-spacing: 0.06em;

  color: var(--text-dim);
  margin: 0 0 var(--sp-2);
}

/* Logical Distributed Switch: the glyph in a switch card's title, the VLAN
   port-group chips, and the repeatable port-group editor rows. */
.cmd-lds-glyph {
  width: 1.05em;
  height: 1.05em;
  vertical-align: -2px;
  margin-right: var(--sp-2);
  color: var(--accent);
}
.cmd-pg-chips {
  display: flex;
  flex-wrap: wrap;
  gap: 6px;
}
.cmd-pg-chip {
  display: inline-flex;
  align-items: center;
  padding: var(--sp-px) var(--sp-2);
  border-radius: 999px;
  font-size: var(--fs-50);
  background: var(--accent-tint);
  color: var(--accent);
  border: 1px solid var(--border);
}
.cmd-inline-badge {
  margin-left: var(--sp-2);
}
.cmd-slot-gap {
  color: var(--text-muted);
}
.cmd-stretch-req {
  display: flex;
  align-items: flex-start;
  gap: var(--sp-3);
  padding: var(--sp-3) 0;
  border-top: 1px solid var(--border);
}
.cmd-stretch-glyph {
  flex: 0 0 auto;
  margin-top: 2px;
}
.cmd-stretch-req-body {
  flex: 1 1 auto;
  min-width: 0;
}
.cmd-stretch-req-head {
  display: flex;
  align-items: center;
  gap: var(--sp-2);
}
.cmd-stretch-req-title {
  font-weight: 600;
}
.cmd-stretch-remedy {
  color: var(--accent);
}
/* Section subtitle under a findings-group head (e.g. "SimpleVirt will configure…"): reads as an
   explanation of the group, set just under the heading and above the first row. */
.cmd-stretch-group-sub {
  margin: calc(var(--sp-1) * -1) 0 var(--sp-2);
}
/* Stretched Cluster productized UI */
.cmd-stretch-topo {
  display: flex;
  justify-content: center;
  padding: var(--sp-2) 0;
  max-width: 360px;
  margin: 0 auto;
}
.cmd-stretch-sites {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(180px, 1fr));
  gap: var(--sp-3);
  margin-bottom: var(--sp-3);
}
.cmd-stretch-site {
  margin: 0;
}
.cmd-stretch-incident {
  border-left: 3px solid var(--warn);
}
/* headline: state chip + verdict badge side by side */
.cmd-stretch-headline-badges {
  display: inline-flex;
  align-items: center;
  gap: var(--sp-2);
  flex-wrap: wrap;
}
/* site card extras: named hosts + the witness role tag */
.cmd-stretch-site-hosts {
  font-variant-numeric: tabular-nums;
  opacity: 0.9;
}
.cmd-stretch-site-role {
  margin-top: 6px;
}
.cmd-stretch-site-witness {
  border-left: 2px solid color-mix(in srgb, var(--info) 45%, transparent);
}
/* readiness-at-a-glance category grid */
.cmd-stretch-cats {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(200px, 1fr));
  gap: var(--sp-2) var(--sp-3);
}
.cmd-stretch-cat {
  padding: var(--sp-2) 0;
  border-top: 1px solid var(--border);
}
.cmd-stretch-cat-head {
  display: flex;
  align-items: center;
  gap: var(--sp-2);
}
.cmd-stretch-cat-name {
  font-weight: 600;
  flex: 1 1 auto;
  min-width: 0;
}
/* guided next-steps */
.cmd-stretch-steps {
  display: flex;
  flex-direction: column;
  gap: 6px;
}
.cmd-stretch-step {
  display: flex;
  align-items: center;
  gap: var(--sp-3);
  width: 100%;
  padding: var(--sp-2) var(--sp-3);
  background: var(--panel, transparent);
  border: 1px solid var(--border);
  border-radius: var(--radius, 8px);
  color: var(--text);
  cursor: pointer;
  text-align: left;
  font: inherit;
  transition: background var(--dur-2) var(--ease-standard), border-color var(--dur-2) var(--ease-standard);
}
.cmd-stretch-step:hover {
  border-color: var(--accent);
  background: var(--accent-tint);
}
.cmd-stretch-step.is-done {
  opacity: 0.72;
}
.cmd-stretch-step-num {
  flex: 0 0 auto;
}
.cmd-stretch-step-label {
  flex: 1 1 auto;
  min-width: 0;
}
.cmd-stretch-step-go {
  flex: 0 0 auto;
  color: var(--text-muted);
  font-size: 1.1rem;
  line-height: 1;
}
/* cluster-header persistent topology context line */
.cmd-obj-sub-stack {
  display: inline-flex;
  flex-direction: column;
  gap: 1px;
}
/* object identity: type/context line + topology line under the cluster name */
.cmd-obj-typeline { font-size: var(--fs-75); font-weight: 600; letter-spacing: 0.01em; color: var(--text-dim); }
.cmd-obj-topoline { font-size: var(--fs-75); color: var(--text-muted); }
.cmd-obj-typeline.cmd-stretch-tone-configured { color: var(--text-dim); }
.cmd-obj-typeline.cmd-stretch-tone-ready { color: var(--info); }
.cmd-obj-typeline.cmd-stretch-tone-enabled { color: var(--ok); }
.cmd-obj-typeline.cmd-stretch-tone-degraded { color: var(--warn); }
.cmd-obj-stretch-context {
  font-size: var(--fs-50, 0.78rem);
  letter-spacing: 0.01em;
  color: var(--text-muted);
}
.cmd-stretch-tone-ready .cmd-obj-stretch-context,
.cmd-obj-stretch-context.cmd-stretch-tone-ready { color: var(--info); }
.cmd-stretch-tone-enabled { color: var(--ok); }
.cmd-stretch-tone-degraded { color: var(--warn); }
.cmd-stretch-tone-ready { color: var(--info); }
/* inventory-tree identity chips (stretch ⇄ marker + site labels A·B·W) */
.cmd-tree-tag {
  flex: 0 0 auto;
  margin-left: 6px;
  padding: 0 6px;
  height: 15px;
  display: inline-flex;
  align-items: center;
  justify-content: center;
  font-size: var(--fs-10);
  font-weight: 700;
  line-height: 1;
  border-radius: 4px;
  border: 1px solid transparent;
  letter-spacing: 0.02em;
}
.cmd-tree-stretch { font-size: var(--fs-50); }
.cmd-tree-stretch-configured { color: var(--text-muted); background: color-mix(in srgb, var(--text-muted) 16%, transparent); }
.cmd-tree-stretch-ready { color: var(--info); background: var(--accent-tint); }
.cmd-tree-stretch-enabled { color: var(--ok); background: var(--ok-tint); }
.cmd-tree-stretch-degraded { color: var(--warn); background: color-mix(in srgb, var(--warn) 18%, transparent); }
.cmd-tree-site { color: var(--text); background: color-mix(in srgb, var(--border) 55%, transparent); }
.cmd-tree-site-a { color: var(--info); background: var(--accent-tint); }
.cmd-tree-site-b { color: var(--info); background: var(--accent-tint); }
.cmd-tree-site-w { color: var(--text-muted); background: color-mix(in srgb, var(--text-muted) 16%, transparent); }

/* ============================================================================
   Shared design language — status chips, usage bars, site tags, check rows.
   ============================================================================ */
.cmd-dim { color: var(--text-muted); }
.cmd-status-chip { min-width: 74px; justify-content: center; }
/* site role tags (host table + cards): A/B informational blue, W neutral */
.cmd-site-tag {
  display: inline-flex; align-items: center; padding: 1px var(--sp-2); border-radius: var(--r-sm);
  font-size: var(--fs-50); font-weight: 600; letter-spacing: 0.02em; white-space: nowrap;
}
.cmd-site-a, .cmd-site-b { color: var(--info); background: var(--accent-tint); }
.cmd-site-w { color: var(--text-dim); background: color-mix(in srgb, var(--grey) 16%, transparent); }
/* compact utilisation bar for table cells */
.cmd-usagecell { display: inline-flex; align-items: center; gap: var(--sp-2); min-width: 92px; }
.cmd-usagebar { position: relative; width: 54px; height: 6px; border-radius: 3px; background: var(--bg-inset); overflow: hidden; flex: none; }
.cmd-usagefill { position: absolute; inset: 0 auto 0 0; border-radius: 3px; transition: width var(--dur-3) var(--ease-standard); }
.cmd-usagefill.u-ok { background: var(--ok); }
.cmd-usagefill.u-warn { background: var(--warn); }
.cmd-usagefill.u-err { background: var(--err); }
.cmd-usagepct { font-variant-numeric: tabular-nums; font-size: var(--fs-75); color: var(--text-dim); min-width: 34px; }
/* check rows — the shared preflight/validation line used by Witness, Networking,
   Capacity and the witness-role card. label on the left, a status token on the right. */
.cmd-chk { display: flex; align-items: center; gap: var(--sp-3); padding: 9px var(--sp-1); border-top: 1px solid var(--border-subtle); }
.cmd-chk:first-child { border-top: 0; }
.cmd-chk-label { flex: 1 1 auto; min-width: 0; color: var(--text-dim); }
.cmd-chk-sub { color: var(--text-muted); font-size: var(--fs-75); }
.cmd-chk-status { display: inline-flex; align-items: center; gap: var(--sp-2); font-size: var(--fs-75); font-weight: 600; white-space: nowrap; }
.cmd-chk-ok { color: var(--ok); }
.cmd-chk-bad { color: var(--err); }
.cmd-chk-warn { color: var(--warn); }
.cmd-chk-na { color: var(--text-muted); font-weight: 500; }
.cmd-chk-pending { color: var(--text-muted); }
.cmd-chk-blocker { color: var(--err); }
.cmd-chk-glyph { width: 16px; text-align: center; }
/* a small spinner dot for the "Checking…" state (restrained, no giant spinner) */
.cmd-chk-spin { width: 12px; height: 12px; border-radius: 50%; border: 2px solid color-mix(in srgb, var(--info) 35%, transparent); border-top-color: var(--info); animation: cmd-spin 0.7s linear infinite; }
@keyframes cmd-spin { to { transform: rotate(360deg); } }
@media (prefers-reduced-motion: reduce) { .cmd-chk-spin { animation: none; } }

/* ============================================================================
   Stretched Cluster — flagship landing, wizard steps, activation, dashboard.
   ============================================================================ */
/* landing hero: title row + state badge + one-line pitch (compact, not a giant hero) */
.cmd-stretch-hero { display: flex; flex-direction: column; gap: var(--sp-2); margin-bottom: var(--sp-5); }
.cmd-stretch-hero-head { display: flex; align-items: center; justify-content: space-between; gap: var(--sp-3); flex-wrap: wrap; }
.cmd-stretch-hero-title { font-size: var(--fs-400); font-weight: 700; letter-spacing: var(--ls-tight); }
.cmd-stretch-hero-sub { color: var(--text-dim); max-width: 64ch; line-height: 1.55; }
/* finished landing: one card, balanced two-column body, footer with the CTA */
.cmd-stretch-landing-card { padding: var(--sp-5) var(--sp-6) var(--sp-4); }
.cmd-stretch-landing { display: grid; grid-template-columns: 1.15fr 1fr; gap: var(--sp-6); align-items: center; margin: var(--sp-5) 0; }
.cmd-stretch-landing-topo { display: flex; justify-content: center; align-items: center; }
.cmd-stretch-landing-foot { display: flex; align-items: center; justify-content: space-between; gap: var(--sp-4); padding-top: var(--sp-4); border-top: 1px solid var(--border-subtle); flex-wrap: wrap; }
@media (max-width: 900px) { .cmd-stretch-landing { grid-template-columns: 1fr; } }
/* headline badges wrapper (state chip + verdict) */
.cmd-stretch-headline-badges { display: inline-flex; align-items: center; gap: var(--sp-2); flex-wrap: wrap; }
/* the topology figure: elegant, restrained. witness on top, two sites below. */
.cmd-stretch-topo2 { display: flex; justify-content: center; padding: var(--sp-4) 0 var(--sp-2); }
.cmd-stretch-topo2 svg { width: 100%; max-width: 460px; height: auto; }
/* "SimpleVirt will validate" list */
.cmd-stretch-validate { background: var(--bg-raised); border: 1px solid var(--border); border-radius: var(--r-card); padding: var(--sp-4) var(--sp-5); }
.cmd-stretch-validate-title { font-size: var(--fs-50); letter-spacing: 0.06em; color: var(--text-muted); margin-bottom: var(--sp-3); }
.cmd-stretch-validate-item { display: flex; align-items: center; gap: var(--sp-3); padding: 7px 0; color: var(--text-dim); }
.cmd-stretch-validate-item .cmd-vck { color: var(--accent); font-weight: 700; flex: none; }
/* host-selection cards (Site A/B steps) */
.cmd-stretch-hostpick { display: grid; grid-template-columns: repeat(auto-fill, minmax(240px, 1fr)); gap: var(--sp-3); }
.cmd-stretch-hostcard {
  text-align: left; background: var(--bg-raised); border: 1px solid var(--border); border-radius: var(--r-card);
  padding: var(--sp-3) var(--sp-4); cursor: pointer; color: var(--text); font: inherit;
  transition: border-color var(--dur-2) var(--ease-standard), background var(--dur-2) var(--ease-standard), box-shadow var(--dur-2) var(--ease-standard);
}
.cmd-stretch-hostcard:hover { border-color: var(--border-strong); background: var(--bg-hover); }
.cmd-stretch-hostcard.selected { border-color: var(--accent); box-shadow: inset 0 0 0 1px var(--accent); background: var(--accent-tint); }
.cmd-stretch-hostcard.is-disabled { opacity: 0.5; cursor: not-allowed; }
.cmd-stretch-hostcard-head { display: flex; align-items: center; justify-content: space-between; gap: var(--sp-2); margin-bottom: var(--sp-2); }
.cmd-stretch-hostcard-name { font-weight: 600; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.cmd-stretch-hostcard-spec { display: grid; grid-template-columns: auto 1fr; gap: 3px var(--sp-3); font-size: var(--fs-75); }
.cmd-stretch-hostcard-spec dt { color: var(--text-muted); }
.cmd-stretch-hostcard-spec dd { color: var(--text-dim); margin: 0; text-align: right; font-variant-numeric: tabular-nums; }
/* comparison table (Site A vs Site B) */
.cmd-stretch-cmp { width: 100%; border-collapse: collapse; margin-top: var(--sp-3); font-size: var(--fs-100); }
.cmd-stretch-cmp th, .cmd-stretch-cmp td { padding: 7px var(--sp-3); text-align: right; border-top: 1px solid var(--border-subtle); font-variant-numeric: tabular-nums; }
.cmd-stretch-cmp th:first-child, .cmd-stretch-cmp td:first-child { text-align: left; color: var(--text-muted); font-weight: 500; }
.cmd-stretch-cmp thead th { border-top: 0; color: var(--text-dim); font-weight: 600; }
/* architecture choice cards (reuse cmd-ha-mode look, but a touch stronger) */
.cmd-stretch-arch-note { font-size: var(--fs-75); color: var(--text-muted); margin-top: var(--sp-2); }
/* capacity simulation panels */
.cmd-stretch-sim { background: var(--bg-raised); border: 1px solid var(--border); border-radius: var(--r-card); padding: var(--sp-4); }
.cmd-stretch-sim + .cmd-stretch-sim { margin-top: var(--sp-3); }
.cmd-stretch-sim-head { display: flex; align-items: center; justify-content: space-between; gap: var(--sp-2); margin-bottom: var(--sp-2); }
.cmd-stretch-sim-title { font-weight: 600; }
.cmd-stretch-sim-verdict { display: flex; align-items: center; gap: var(--sp-2); margin-top: var(--sp-3); padding-top: var(--sp-3); border-top: 1px solid var(--border-subtle); font-weight: 600; }
/* review as an infrastructure change plan */
.cmd-stretch-plan { background: var(--bg-raised); border: 1px solid var(--border); border-radius: var(--r-card); overflow: hidden; }
.cmd-stretch-plan-row { display: grid; grid-template-columns: 140px 1fr; gap: var(--sp-3); padding: var(--sp-3) var(--sp-4); border-top: 1px solid var(--border-subtle); }
.cmd-stretch-plan-row:first-child { border-top: 0; }
.cmd-stretch-plan-k { font-size: var(--fs-50); letter-spacing: 0.05em; color: var(--text-muted); padding-top: 2px; }
.cmd-stretch-plan-v { color: var(--text); }
.cmd-stretch-plan-foot { display: flex; align-items: center; justify-content: space-between; gap: var(--sp-3); padding: var(--sp-4); background: var(--bg-inset); border-top: 1px solid var(--border); flex-wrap: wrap; }
/* Review · Readiness — the "N requirements need attention" heading + the auto-config rows */
.cmd-stretch-readiness-count { margin: 0 0 var(--sp-2); font-weight: var(--fw-semibold); color: var(--warn); }
.cmd-stretch-ready .cmd-stretch-sim-verdict { margin-bottom: var(--sp-2); }
.cmd-stretch-auto-row { display: flex; align-items: center; justify-content: space-between; gap: var(--sp-3); padding: var(--sp-3) 0; border-top: 1px solid var(--border-subtle); }
.cmd-stretch-auto-row:first-of-type { border-top: 0; }
.cmd-stretch-auto-k { color: var(--text); }
/* probe freshness note ("Validated 12s ago") under the connectivity checks */
.cmd-stretch-fresh { font-variant-numeric: tabular-nums; }
/* activation execution progress */
.cmd-stretch-exec { background: var(--bg-raised); border: 1px solid var(--border); border-radius: var(--r-card); padding: var(--sp-4) var(--sp-5); }
.cmd-stretch-exec-stage { display: flex; align-items: center; gap: var(--sp-3); padding: 8px 0; color: var(--text-muted); transition: color var(--dur-3) var(--ease-standard); }
.cmd-stretch-exec-stage.done { color: var(--text-dim); }
.cmd-stretch-exec-stage.active { color: var(--text); }
.cmd-stretch-exec-glyph { width: 18px; text-align: center; flex: none; }
.cmd-stretch-exec-stage.done .cmd-stretch-exec-glyph { color: var(--ok); }
.cmd-stretch-exec-stage.active .cmd-stretch-exec-glyph { color: var(--info); }
/* operational dashboard (protected) */
.cmd-stretch-dashrow { display: grid; grid-template-columns: 1fr auto; gap: var(--sp-3); align-items: center; padding: 10px var(--sp-1); border-top: 1px solid var(--border-subtle); }
.cmd-stretch-dashrow:first-child { border-top: 0; }
.cmd-stretch-dashrow-k { color: var(--text-dim); }
.cmd-stretch-dashrow-v { display: inline-flex; align-items: center; gap: var(--sp-2); font-weight: 600; font-variant-numeric: tabular-nums; }
/* degraded banner */
.cmd-stretch-degraded { border-left: 3px solid var(--warn); }

.cmd-stretch-advanced {
  margin-top: var(--sp-3);
}
.cmd-stretch-advanced > summary {
  cursor: pointer;
  color: var(--text-muted);
  font-size: 0.85rem;
  padding: var(--sp-2) 0;
  user-select: none;
}
.cmd-stretch-advanced > summary:hover {
  color: var(--text);
}
.cmd-pg-editor {
  display: flex;
  flex-direction: column;
  gap: var(--sp-2);
}
.cmd-pg-row {
  display: grid;
  grid-template-columns: 1fr 0.5fr 0.9fr 1fr 0.9fr auto auto;
  gap: var(--sp-2);
  align-items: center;
}
.cmd-pg-rate {
  display: grid;
  grid-template-columns: 1fr 1fr;
  gap: 4px;
}
.cmd-pg-row-wrap {
  display: flex;
  flex-direction: column;
  gap: var(--sp-2);
}
/* Service adapters declared on one distributed network. Indented and ruled so the
   per-host address list reads as belonging to the network above it. */
.cmd-pg-adapters {
  margin-left: var(--sp-3);
  padding-left: var(--sp-3);
  border-left: 2px solid var(--border);
}
.cmd-adapter-editor {
  display: flex;
  flex-direction: column;
  gap: var(--sp-3);
}
.cmd-adapter-row {
  display: flex;
  flex-direction: column;
  gap: var(--sp-2);
  padding: var(--sp-2) 0;
}
.cmd-adapter-head {
  display: grid;
  grid-template-columns: 1fr 0.9fr auto auto;
  gap: var(--sp-2);
  align-items: center;
}
.cmd-adapter-hosts {
  display: grid;
  grid-template-columns: repeat(auto-fill, minmax(220px, 1fr));
  gap: var(--sp-2);
}
.cmd-adapter-host {
  display: flex;
  flex-direction: column;
  gap: 4px;
  font-size: var(--fs-50);
  color: var(--text-muted);
}
.cmd-pg-head {
  display: grid;
  grid-template-columns: 1fr 0.5fr 0.9fr 1fr 0.9fr auto auto;
  gap: var(--sp-2);
  margin-bottom: 6px;
  font-size: var(--fs-50);
  font-weight: 600;
  letter-spacing: 0.04em;

  color: var(--text-muted);
}
/* Per-host uplink cards inside the LDS wizard. */
.cmd-perhost {
  display: flex;
  flex-direction: column;
  gap: 10px;
  margin-top: var(--sp-1);
}
.cmd-same-toggle {
  margin-bottom: var(--sp-3);
}

/* Inside a dialog, section titles become dividers so a long form reads as
   grouped sections instead of one flat wall of fields. */
.modal-body .cmd-net-title {
  margin: 22px 0 var(--sp-3);
  padding-top: 18px;
  border-top: var(--sp-px) solid var(--border);
}
.modal-body .cmd-net-title:first-child {
  margin-top: 0;
  padding-top: 0;
  border-top: none;
}
.cmd-net-table {
  width: 100%;
}

/* Datacenter -> Datastores: columns size to content and the Capacity column (the
   fullness meter) takes the slack, so a row reads as a storage console. The
   Identity (UUID) is truncated with its full value on hover, not left to stretch. */
.cmd-dc-ds .cmd-net-table th,
.cmd-dc-ds .cmd-net-table td { white-space: nowrap; }
.cmd-dc-ds .cmd-net-table th:nth-child(5),
.cmd-dc-ds .cmd-net-table td:nth-child(5) { width: 100%; min-width: 180px; }
.cmd-dc-ds .cmd-ds-host { color: var(--text-dim); }
.cmd-dc-ds .cmd-ds-id { max-width: 16ch; overflow: hidden; text-overflow: ellipsis; }
.cmd-dc-ds .cmd-ds-id .cell-mono { display: inline-block; max-width: 16ch; overflow: hidden; text-overflow: ellipsis; vertical-align: bottom; }
.cmd-ds-cap { vertical-align: middle; }
.cmd-ds-cap .meter { margin: 0 0 3px; }
.cmd-ds-cap-sub { font-size: var(--fs-50); color: var(--text-dim); font-variant-numeric: tabular-nums; white-space: nowrap; }

table {
  width: 100%;
  border-collapse: collapse;
  font-size: var(--fs-100);
}

thead th {
  position: sticky;
  top: 0;
  z-index: 1;
}

/* A filled grey header band is the other half of the borrowed look. The column
   labels are already uppercase and tracked; they do not also need a slab of
   grey behind them to be legible as headers. A hairline underneath does the
   same job and lets the table sit ON the card rather than inside a second box. */
th {
  text-align: left;
  font-size: var(--fs-50);
  font-weight: 600;

  letter-spacing: 0.06em;
  color: var(--text-muted);
  /* A subtly RECESSED header band (inset, not a bright grey slab): enough tonal
     separation to read as "these are column headers" in an enterprise inventory
     table, without the heavy grey header slab the charter warns against. */
  background: var(--bg-inset);
  box-shadow: inset 0 -1px 0 var(--border);
  height: 34px;
  padding: 0 var(--sp-4);
  border-bottom: var(--sp-px) solid var(--border);
}

td {
  padding: 0 var(--sp-4);
  height: 38px;
  /* Quieter than the header rule — row separators should be felt, not seen. */
  border-bottom: 1px solid var(--border-subtle);
  vertical-align: middle;
  /* Infra tables are full of figures (vCPU, memory, %) — align them. */
  font-variant-numeric: tabular-nums;
}

.table-compact td {
  height: 32px;
}

tr:last-child td {
  border-bottom: none;
}

tbody tr:hover {
  background: var(--row-hover);
}

tbody tr.row-click {
  cursor: pointer;
}

.num-cell {
  text-align: right;
  font-variant-numeric: tabular-nums;
}

.cell-actions {
  text-align: right;
  white-space: nowrap;
}

/* Dense row-verb buttons: compact, and the non-destructive ones read as quiet
   ghost verbs so a row of 5-6 actions isn't a wall of boxes.
   Destructive (Delete / Power off) keep their danger color. */
.cell-actions .btn {
  height: 26px;
  padding: 0 var(--sp-2);
  font-size: var(--fs-75);
  margin-left: var(--sp-1);
}
.cell-actions .btn:not(.btn-danger):not(.btn-danger-solid):not(.btn-primary) {
  background: transparent;
  border-color: transparent;
  color: var(--text-dim);
}
.cell-actions .btn:not(.btn-danger):not(.btn-danger-solid):not(.btn-primary):hover:not(:disabled) {
  background: var(--row-hover);
  border-color: var(--border-strong);
  color: var(--text);
}

.cell-name {
  font-weight: 600;
}

/* Monospace is not decoration here. ens33, vss2, vmk0, 10.160.99.161/24 and
   /var/lib/simplevirt are Linux identifiers; in a proportional face they read as a
   SaaS dashboard, in monospace they read as a systems tool — and an octet or an
   interface index actually becomes scannable down a column. Slightly tighter
   than body text because monospace runs wide. */
.cell-mono {
  font-family: var(--mono);
  font-size: var(--fs-75);
  font-variant-numeric: slashed-zero tabular-nums;
  letter-spacing: -0.01em;
  color: var(--text);
}

/* ---------- buttons ------------------------------------------------------ */

/* One canonical button system (the old file had three conflicting .btn blocks).
   Fixed height, token-driven motion, a calm flat primary, and a single press. */
.btn {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  gap: var(--sp-2);
  height: 32px;
  padding: 0 var(--sp-3);
  border: 1px solid var(--border-strong);
  border-radius: var(--r-control);
  background: var(--bg-hover);
  color: var(--text);
  font-size: var(--fs-100);
  font-weight: var(--fw-medium);
  line-height: 1;
  cursor: pointer;
  white-space: nowrap;
  user-select: none;
  /* A faint lit top edge on filled controls, matching the primary button, so the
     whole button set shares one surface language (ghost opts out below). */
  box-shadow: inset 0 1px 0 rgba(255, 255, 255, 0.04);
  transition: background var(--dur-2) var(--ease-standard),
    border-color var(--dur-2) var(--ease-standard),
    color var(--dur-2) var(--ease-standard),
    transform var(--dur-1) var(--ease-standard);
}

.btn:hover:not(:disabled) {
  border-color: var(--border-focus);
}

.btn:active:not(:disabled) {
  transform: scale(0.97);
}

.btn:focus-visible {
  outline: 2px solid var(--focus-ring);
  outline-offset: 2px;
}

.btn:disabled {
  opacity: 0.45;
  cursor: not-allowed;
  transform: none;
}

.btn-primary {
  background: var(--accent-solid);
  border-color: var(--accent-solid);
  color: var(--accent-fg);
  font-weight: var(--fw-semibold);
  /* Presence via an accent glow + a lit top edge, kept on the solid AA-contrast
     fill so white button text stays legible. */
  box-shadow: var(--glow-accent), inset 0 1px 0 rgba(255, 255, 255, 0.18);
}

/* Hover DARKENS (never lightens) so white text stays ≥AA through every state. */
.btn-primary:hover:not(:disabled) {
  background: var(--accent-solid-hover);
  border-color: var(--accent-solid-hover);
}

.btn-primary:active:not(:disabled) {
  background: var(--accent-press);
  border-color: var(--accent-press);
}

.btn-primary:focus-visible,
.btn-danger-solid:focus-visible {
  outline-offset: 3px;
}

/* A destructive action should read AS an action, not as a ghost link. A persistent
   faint danger tint + an --err-weighted edge gives it affordance; the final-confirm
   button stays .btn-danger-solid. */
.btn-danger {
  background: var(--err-tint);
  border-color: color-mix(in srgb, var(--err) 40%, transparent);
  color: var(--err);
}

.btn-danger:hover:not(:disabled) {
  background: color-mix(in srgb, var(--err) 18%, transparent);
  border-color: var(--err);
}

.btn-danger-solid {
  background: var(--err);
  border-color: var(--err);
  color: var(--danger-fg);
  font-weight: var(--fw-semibold);
}

.btn-danger-solid:hover:not(:disabled) {
  background: color-mix(in srgb, var(--err) 85%, #000);
  border-color: color-mix(in srgb, var(--err) 85%, #000);
}

.btn-ghost {
  background: transparent;
  border-color: transparent;
  color: var(--text-dim);
  box-shadow: none;
}

.btn-ghost:hover:not(:disabled) {
  color: var(--text);
  background: var(--row-hover);
  border-color: transparent;
}

.btn-sm,
.btn-small {
  height: 28px;
  padding: 0 10px;
  font-size: var(--fs-75);
}

/* ---------- forms -------------------------------------------------------- */

.form-field {
  margin-bottom: 14px;
}

.form-field label {
  display: block;
  font-size: var(--fs-75);
  font-weight: 600;
  color: var(--text-dim);
  margin-bottom: var(--sp-1);
}

/* Multi-uplink checkbox picker (create/edit logical switch). */
.cmd-uplink-list {
  display: flex;
  flex-direction: column;
  gap: var(--sp-1);
  margin: var(--sp-1) 0 var(--sp-2);
}
.cmd-uplink-list label.cmd-uplink-row {
  display: flex;
  align-items: center;
  gap: var(--sp-2);
  font-size: var(--fs-100);
  font-weight: 400;
  color: var(--text);
  cursor: pointer;
  margin-bottom: 0;
}
.cmd-uplink-list input {
  width: auto;
  margin: 0;
}

.form-field input[type="text"],
.form-field input[type="password"],
.form-field input[type="number"],
.form-field select,
.form-field textarea {
  width: 100%;
  background: var(--bg-inset);
  border: 1px solid var(--border-strong);
  border-radius: var(--r-control);
  color: var(--text);
  font-family: inherit;
  font-size: var(--fs-100);
  height: 34px;
  padding: 0 var(--sp-3);
  transition: border-color var(--dur-2) var(--ease-standard), box-shadow var(--dur-2) var(--ease-standard), background var(--dur-2) var(--ease-standard);
}

/* Textareas share the input look but grow (the 34px fixed height above is for
   single-line controls). */
.form-field textarea {
  height: auto;
  min-height: 76px;
  padding: var(--sp-2) var(--sp-3);
  line-height: 1.5;
  resize: vertical;
}

.form-field input:hover:not(:focus):not(:disabled),
.form-field select:hover:not(:focus):not(:disabled),
.form-field textarea:hover:not(:focus):not(:disabled) {
  border-color: var(--border-focus);
}

/* Disabled controls must READ as non-editable: the rule above sets an explicit
   background/color that overrides the browser's default disabled dimming, so without
   this a locked field looked fully editable (and brightened on hover). Dim it and
   show not-allowed; no hover/focus affordance applies. */
.form-field input:disabled,
.form-field select:disabled,
.form-field textarea:disabled {
  background: var(--bg-panel);
  color: var(--text-muted);
  border-color: var(--border);
  cursor: not-allowed;
  opacity: 0.65;
}

.form-field input::placeholder,
.form-field textarea::placeholder {
  color: var(--text-muted);
}

.form-field input:focus,
.form-field select:focus,
.form-field textarea:focus {
  outline: none;
  border-color: var(--accent);
  box-shadow: 0 0 0 2px var(--focus-ring);
}

/* Per-field validation (PatternFly FormGroup / Cloudscape FormField): a red
   border + tint on the offending control. Add .is-invalid to the .form-field. */
.form-field.is-invalid input,
.form-field.is-invalid select,
.form-field.is-invalid textarea {
  border-color: var(--err);
}
.form-field.is-invalid input:focus,
.form-field.is-invalid select:focus,
.form-field.is-invalid textarea:focus {
  box-shadow: 0 0 0 2px var(--err-tint);
}

.form-hint {
  font-size: var(--fs-75);
  color: var(--text-dim);
  margin: var(--sp-1) 0 0;
}
/* A hint that has become a correction: the value the operator is looking at is
   not one the form will accept. Colour only — it stays a hint, not an alarm. */
.form-hint.is-error {
  color: var(--err);
}
/* A setting that is saved but not yet in effect (a CPU/memory change written to
   a running VM's config, which applies on its next boot). */
.form-hint.is-pending {
  color: var(--warn);
  background: var(--warn-tint);
  border-radius: var(--r-control);
  padding: 6px 10px;
}
/* A cautionary hint: a chosen configuration is accepted but carries a trade-off
   the operator should see (e.g. a 2-host datastore's reduced write resilience). */
.form-hint.is-warn {
  color: var(--warn);
  background: var(--warn-tint);
  border-radius: var(--r-control);
  padding: 6px 10px;
}

.form-check {
  display: flex;
  align-items: center;
  gap: var(--sp-2);
  margin-bottom: 14px;
  font-size: var(--fs-100);
  cursor: pointer;
}

.form-check input {
  accent-color: var(--accent);
  width: 15px;
  height: 15px;
}

.form-error {
  background: var(--err-tint);
  border: 1px solid var(--err);
  color: var(--err);
  border-radius: var(--r-control);
  padding: var(--sp-2) var(--sp-3);
  font-size: var(--fs-100);
  margin: 0 0 14px;
  white-space: pre-wrap;
  overflow-wrap: break-word;
}

.form-row {
  display: grid;
  grid-template-columns: 1fr 1fr;
  gap: var(--sp-3);
}
/* Three fields that belong together and read as one value — sockets × cores ×
   threads. Without this they inherit the two-column grid and the third wraps
   underneath the first, which reads as an unrelated field. */
.form-row-3 {
  grid-template-columns: 1fr 1fr 1fr;
}

/* The footer wraps deliberately: when the buttons (e.g. Cancel + Evacuate + Shut
   down & enter maintenance) are wider than the modal, they flow onto additional
   right-aligned rows rather than overflowing and clipping. Buttons keep their
   natural width (never shrink a label into "…") and never trigger horizontal
   scroll. On a genuinely narrow modal each button drops to its own row. */
.form-actions {
  display: flex;
  flex-wrap: wrap;
  justify-content: flex-end;
  align-items: center;
  gap: 10px;
  margin-top: var(--sp-5);
}
.form-actions > .btn { flex: 0 0 auto; }
/* When the row wraps, the buttons stack full-width so the footer reads as a clean
   vertical action list instead of a ragged right-aligned staircase. */
@media (max-width: 420px) {
  .form-actions { justify-content: stretch; }
  .form-actions > .btn { flex: 1 1 100%; }
}

/* ---------- Add hosts (bulk) modal rows -------------------------------- */
.cmd-hostrows-label {
  display: block;
  font-size: var(--fs-75);
  font-weight: 600;
  color: var(--text-muted);
  margin: 14px 0 6px;
}
.cmd-hostrows {
  display: flex;
  flex-direction: column;
  gap: var(--sp-2);
}
.cmd-hostrow {
  display: flex;
  align-items: center;
  gap: 10px;
}
.cmd-hostrow .form-field {
  margin: 0;
}
.cmd-hostrow-addr {
  flex: 1 1 55%;
}
.cmd-hostrow-name {
  flex: 1 1 35%;
}
.cmd-hostrow-status {
  flex: 0 0 auto;
  min-width: 96px;
  font-size: var(--fs-75);
  color: var(--text-muted);
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
  max-width: 220px;
}
.cmd-hostrow-pairing { color: var(--accent); }
.cmd-hostrow-added { color: var(--ok); }
.cmd-hostrow-added::before { content: "✓ "; }
.cmd-hostrow-failed { color: var(--err); white-space: normal; }
.cmd-hostrow-x {
  flex: 0 0 auto;
  line-height: 1;
}

/* ---------- login (two-zone brand + auth) -------------------------------- */
/* DARK-committed: the --lg-* tokens are explicit, not theme tokens, so the first
   rendered frame is already the finished dark screen. Zone A is the SimpleVirt brand
   over a composed virtualization schematic; Zone B is the auth workspace. No cluster,
   host or VM data appears anywhere on this screen. */
.login-root {
  --lg-bg: #14161b;
  --lg-panel: #1a1d23;
  --lg-input: #121419;
  --lg-input-border: #333a45;
  --lg-hair: #262a32;
  --lg-text: #edeff2;
  --lg-text-2: #b6bdc9;
  --lg-muted: #868f9c;
  --lg-brand: #2d82f7;
  --lg-primary: #2563eb;
  --lg-primary-hover: #1d4ed8;
  --lg-ring: #5c9bff;
  --lg-ok: #4cd79f;
  --lg-warn: #f0b43c;
  --lg-err: #f07167;
  position: absolute;
  inset: 0;
  overflow-y: auto;
  background: var(--lg-bg);
  color: var(--lg-text);
  font-family: var(--font);
  font-size: var(--fs-100);
  line-height: 1.5;
}
.login-page {
  box-sizing: border-box;
  min-height: 100%;
  display: grid;
  grid-template-columns: minmax(0, 1.28fr) minmax(0, 1fr);
}

/* ---- Zone A: brand + schematic ---- */
.login-zone-a {
  position: relative;
  display: flex;
  flex-direction: column;
  box-sizing: border-box;
  padding: 44px clamp(34px, 4vw, 76px);
  overflow: hidden;
  background:
    radial-gradient(52% 44% at 22% 30%, rgba(45, 130, 247, 0.10) 0%, transparent 72%),
    linear-gradient(180deg, #15181e 0%, var(--lg-bg) 100%);
}
.login-zone-a::before {
  content: "";
  position: absolute;
  inset: 0;
  pointer-events: none;
  background-image: linear-gradient(var(--lg-hair) 1px, transparent 1px), linear-gradient(90deg, var(--lg-hair) 1px, transparent 1px);
  background-size: 46px 46px;
  -webkit-mask-image: radial-gradient(80% 70% at 30% 40%, #000 0%, transparent 78%);
  mask-image: radial-gradient(80% 70% at 30% 40%, #000 0%, transparent 78%);
  opacity: 0.4;
}
.login-brand { position: relative; z-index: 1; display: flex; align-items: center; gap: 11px; }
.login-mark { display: block; width: 30px; height: 30px; flex: none; }
/* Brand mark — the SimpleVirt isometric stack (matches the logo): a blue control
   plane floating over two slate cubes, with blue connector nodes. */
.login-mk-stick { stroke: var(--lg-brand); fill: var(--lg-brand); stroke-width: 1.7; stroke-linecap: round; }
.login-mk-top  { fill: var(--lg-brand); }   /* control plane — top face */
.login-mk-edge { fill: #125cd8; }           /* control plane — depth edge */
.login-mk-ut   { fill: #5c6e8a; }           /* upper cube — top face */
.login-mk-ul   { fill: #3a4860; }           /* upper cube — left face */
.login-mk-ur   { fill: #4a5a74; }           /* upper cube — right face */
.login-mk-lt   { fill: #465670; }           /* lower cube — top face */
.login-mk-ll   { fill: #2a364c; }           /* lower cube — left face */
.login-mk-lr   { fill: #38465e; }           /* lower cube — right face */
.login-brand-name { font-family: var(--font-display); font-size: var(--fs-300); font-weight: 600; letter-spacing: 0.01em; color: var(--lg-text); }
.login-brand-virt { color: var(--lg-brand); }
.login-brand-cmd { font-weight: 400; color: var(--lg-text-2); }

.login-hero { position: relative; z-index: 1; margin-top: auto; max-width: 440px; }
.login-tagline {
  margin: 0;
  font-family: var(--font-display);
  font-size: 44px;
  line-height: 1.04;
  font-weight: 600;
  letter-spacing: -0.03em;
  color: var(--lg-text);
}
.login-tagline-2 { display: block; color: var(--lg-text); }
.login-lede { margin: 18px 0 0; max-width: 400px; font-size: var(--fs-200); line-height: 1.6; color: var(--lg-muted); }

.login-ill { position: relative; z-index: 1; display: block; width: 100%; max-width: 560px; height: auto; margin: 30px 0 auto; }
.login-ill-label { fill: #5f6773; font-family: var(--font); font-size: 11px; letter-spacing: 0.02em; }
.login-ill-vm { fill: #1d212a; stroke: #2f3540; stroke-width: 1; }
.login-ill-vm.is-on { fill: rgba(45, 130, 247, 0.16); stroke: #2d82f7; }
.login-ill-vm-mark { fill: #3a4150; }
.login-ill-vm-mark.is-on { fill: var(--lg-ring); }
.login-ill-link { stroke: #2a2f38; stroke-width: 1; }
.login-ill-hyper { fill: #232834; stroke: #343a45; stroke-width: 1; }
.login-ill-hyper-key { fill: rgba(45, 130, 247, 0.16); stroke: #2d82f7; stroke-width: 1; }
.login-ill-mark { fill: var(--lg-ring); }
.login-ill-infra { fill: #181b21; stroke: #2a2f38; stroke-width: 1; }
.login-ill-module { fill: #20242e; }

/* ---- Zone B: auth workspace ---- */
.login-zone-b {
  display: flex;
  flex-direction: column;
  box-sizing: border-box;
  padding: 36px clamp(32px, 3.4vw, 60px) 30px;
  background: var(--lg-panel);
  border-inline-start: 1px solid var(--lg-hair);
}
.login-b-top { display: flex; justify-content: flex-end; }
.login-lang { display: inline-flex; align-items: center; gap: 6px; font-size: var(--fs-75); color: var(--lg-muted); }
.login-lang-ic { width: 15px; height: 15px; }
.login-b-mid { flex: 1 0 auto; display: flex; align-items: center; }
.login-auth { width: 100%; max-inline-size: 360px; margin-inline: auto; }
.login-auth-title { margin: 0; font-family: var(--font-display); font-size: var(--fs-600); font-weight: 600; letter-spacing: -0.01em; color: var(--lg-text); }
.login-auth-sub { margin: 6px 0 0; font-size: var(--fs-75); color: var(--lg-muted); }

.login-form { display: flex; flex-direction: column; gap: 15px; margin-top: 26px; }
.login-field { display: flex; flex-direction: column; gap: 7px; }
.login-label { font-size: var(--fs-75); font-weight: 500; color: var(--lg-text-2); }
.login-inp-wrap { position: relative; }
.login-inp-ic { position: absolute; inset-inline-start: 13px; top: 15px; width: 16px; height: 16px; color: var(--lg-muted); pointer-events: none; }
.login-inp-wrap:focus-within .login-inp-ic { color: var(--lg-ring); }
.login-input {
  display: block;
  box-sizing: border-box;
  width: 100%;
  height: 46px;
  margin: 0;
  padding: 0 14px 0 40px;
  background: var(--lg-input);
  border: 1px solid var(--lg-input-border);
  border-radius: var(--r-sm);
  color: var(--lg-text);
  font-family: var(--font);
  font-size: var(--fs-200);
}
.login-input::placeholder { color: var(--lg-muted); opacity: 1; }
.login-input:hover { border-color: #434b57; }
.login-input:focus { outline: none; border-color: var(--lg-ring); box-shadow: 0 0 0 3px color-mix(in srgb, var(--lg-ring) 26%, transparent); }
.login-input:-webkit-autofill { -webkit-text-fill-color: var(--lg-text); caret-color: var(--lg-text); box-shadow: inset 0 0 0 40px var(--lg-input); }
.login-input:disabled { opacity: 0.6; }
.login-inp-wrap-pass .login-input { padding-inline-end: 52px; }
.login-eye {
  position: absolute;
  inset-inline-end: 2px;
  top: 1px;
  display: inline-flex;
  align-items: center;
  justify-content: center;
  width: 44px;
  height: 44px;
  margin: 0;
  padding: 0;
  background: none;
  border: none;
  border-radius: var(--r-sm);
  color: var(--lg-muted);
  cursor: pointer;
}
.login-eye:hover { color: var(--lg-text-2); background: rgba(255, 255, 255, 0.06); }
.login-eye[aria-pressed="true"] { color: var(--lg-ring); }
.login-eye:focus-visible { outline: 2px solid var(--lg-ring); outline-offset: -3px; }
.login-eye-ic { display: block; width: 17px; height: 17px; }
.login-caps { margin: 2px 0 0; font-size: var(--fs-50); color: var(--lg-warn); }

.login-submit {
  display: flex;
  align-items: center;
  justify-content: center;
  gap: 9px;
  width: 100%;
  height: 48px;
  margin: 8px 0 0;
  padding: 0;
  background: var(--lg-primary);
  border: none;
  border-radius: var(--r-sm);
  color: #fff;
  font-family: var(--font);
  font-size: var(--fs-200);
  font-weight: 500;
  cursor: pointer;
}
.login-submit:hover { background: var(--lg-primary-hover); }
.login-submit:active { transform: translateY(1px); }
.login-submit:focus-visible { outline: 2px solid var(--lg-ring); outline-offset: 3px; }
.login-submit:disabled { cursor: progress; }
.login-submit-spin { display: none; width: 16px; height: 16px; flex: none; border: 2px solid rgba(255, 255, 255, 0.5); border-top-color: #fff; border-radius: var(--r-pill); }
.login-submit.is-loading .login-submit-spin { display: inline-block; }


.login-help { margin: 16px 0 0; font-size: var(--fs-50); color: var(--lg-muted); }

/* ---- error (inside the form) ---- */
.login-error {
  display: flex;
  align-items: flex-start;
  gap: 10px;
  padding: 11px 13px;
  background: color-mix(in srgb, var(--lg-err) 10%, transparent);
  border: 1px solid color-mix(in srgb, var(--lg-err) 34%, transparent);
  border-radius: var(--r-sm);
}
.login-error.hidden { display: none; }
.login-err-ic { width: 16px; height: 16px; color: var(--lg-err); flex: none; margin-top: 2px; }
.login-err-text { display: flex; flex-direction: column; gap: 1px; min-width: 0; }
.login-err-title { font-size: var(--fs-75); font-weight: 500; color: #f4b0ab; }
.login-err-detail { font-size: var(--fs-75); color: var(--lg-muted); }

/* ---- footer ---- */
.login-b-foot { display: flex; align-items: center; gap: 12px; font-size: var(--fs-75); }
.login-foot-link { color: var(--lg-muted); text-decoration: none; cursor: pointer; }
.login-foot-link:hover { color: var(--lg-text-2); }
.login-foot-dot { width: 3px; height: 3px; border-radius: var(--r-pill); background: var(--lg-input-border); }

/* ---- motion: only when welcome ---- */
@keyframes login-spin { to { transform: rotate(360deg); } }
@keyframes login-key { 0%, 100% { opacity: 0.6; } 50% { opacity: 1; } }
@media (prefers-reduced-motion: no-preference) {
  .login-submit-spin { animation: login-spin 0.7s linear infinite; }
  .login-ill-hyper-key { animation: login-key 4.5s ease-in-out infinite; }
  .login-input { transition: border-color var(--dur-2) var(--ease-standard), box-shadow var(--dur-2) var(--ease-standard); }
  .login-submit, .login-eye, .login-foot-link { transition: background-color var(--dur-2) var(--ease-standard), color var(--dur-2) var(--ease-standard); }
}
@media (prefers-reduced-motion: reduce) {
  .login-root *, .login-root *::before, .login-root *::after { animation: none !important; transition: none !important; }
}

/* ---- responsive ---- */
@media (max-width: 1024px) {
  .login-page { grid-template-columns: minmax(0, 1fr); }
  .login-zone-a { padding: 30px clamp(22px, 6vw, 44px) 26px; }
  .login-zone-a::before { mask-image: none; -webkit-mask-image: none; opacity: 0.25; }
  .login-hero { margin-top: 20px; }
  .login-tagline { font-size: 34px; }
  .login-lede { display: none; }
  .login-ill { display: none; }
  .login-zone-b { border-inline-start: none; border-top: 1px solid var(--lg-hair); }
  .login-b-mid { padding: 8px 0 24px; }
  .login-auth { max-inline-size: 420px; }
}
@media (max-width: 480px) {
  .login-zone-a { padding: 24px 20px 20px; }
  .login-tagline { font-size: 28px; }
  .login-zone-b { padding: 22px 20px; }
}

/* ---------- detail view -------------------------------------------------- */

.detail-grid {
  display: grid;
  grid-template-columns: 160px 1fr;
  gap: 6px var(--sp-4);
  margin: 0;
}

.detail-grid dt {
  color: var(--text-dim);
  font-size: var(--fs-100);
  padding: var(--sp-px) 0;
}

.detail-grid dd {
  margin: 0;
  padding: var(--sp-px) 0;
  font-size: var(--fs-100);
  overflow-wrap: anywhere;
}

.detail-grid dd.mono {
  font-family: var(--mono);
  font-size: var(--fs-75);
}

.detail-section {
  margin-top: 22px;
}

.detail-section h3 {
  font-size: var(--fs-75);
  color: var(--text-dim);

  letter-spacing: 0.04em;
  margin-bottom: 10px;
}

.section-head {
  display: flex;
  align-items: center;
  justify-content: space-between;
  margin-bottom: 10px;
}

.section-head h3 {
  margin-bottom: 0;
}

/* ---------- tabs --------------------------------------------------------- */

.tabs {
  display: flex;
  gap: var(--sp-px);
  border-bottom: var(--sp-px) solid var(--border);
  margin-bottom: 18px;
}
/* Administration sub-tab strip: same look, tighter. */
.tabs-sub {
  margin-bottom: 14px;
}
/* Card header row: title on the left, a status badge on the right. */
.card-head {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: var(--sp-3);
}
/* Certificate PEM textareas + fingerprint. */
.cert-ta {
  width: 100%;
  min-height: 118px;
  resize: vertical;
  background: var(--bg-inset);
  border: 1px solid var(--border-strong);
  border-radius: 8px;
  color: var(--text);
  font-family: ui-monospace, SFMono-Regular, Menlo, monospace;
  font-size: var(--fs-75);
  line-height: 1.45;
  padding: 10px var(--sp-3);
  box-sizing: border-box;
}
.cert-ta:focus {
  outline: none;
  border-color: var(--accent);
  box-shadow: 0 0 0 3px var(--focus-ring);
}
.cert-fp {
  font-family: ui-monospace, SFMono-Regular, Menlo, monospace;
  font-size: var(--fs-50);
  word-break: break-all;
  color: var(--text-dim);
}
.row-system td {
  /* the system vmk0 / logicalSwitch-0 rows: a faint accent rail on the left */
  box-shadow: inset 2px 0 0 var(--accent);
}

.tab-btn {
  background: none;
  border: none;
  border-bottom: var(--sp-px) solid transparent;
  color: var(--text-dim);
  font-size: var(--fs-100);
  padding: var(--sp-2) var(--sp-4);
  cursor: pointer;
  font-family: inherit;
  margin-bottom: calc(-1 * var(--sp-px));
  transition: color var(--dur-2) var(--ease-standard);
}

.tab-btn:hover {
  color: var(--text);
}

.tab-btn.active {
  color: var(--text);
  border-bottom-color: var(--accent);
  font-weight: 600;
}

/* ---------- toolbar ------------------------------------------------------ */

.toolbar {
  display: flex;
  gap: var(--sp-2);
  align-items: center;
  margin-bottom: var(--sp-4);
  flex-wrap: wrap;
  row-gap: var(--sp-2);
}

.toolbar-spacer {
  margin-left: auto;
}

/* #18 — the fleet bulk-action bar. Shown only when VMs are checked, it sits above
   the table and OUTSIDE the reconciled tbodies, so a selection never rebuilds a
   row. The accent keyline distinguishes it from the neutral toolbar above it. */
.cmd-bulkbar {
  display: flex;
  align-items: center;
  gap: var(--sp-3);
  flex-wrap: wrap;
  row-gap: var(--sp-2);
  margin-bottom: var(--sp-3);
  padding: var(--sp-2) var(--sp-3);
  border: 1px solid var(--accent);
  border-radius: var(--r-sm);
  background: var(--accent-tint);
  box-shadow: var(--shadow-sm);
}
.cmd-bulkbar-left { display: flex; align-items: center; gap: var(--sp-2); flex-wrap: wrap; }
.cmd-bulkbar-count { font-size: var(--fs-75); font-weight: 600; color: var(--text); }
.cmd-bulkbar-ops { display: flex; align-items: center; gap: var(--sp-2); flex-wrap: wrap; margin-left: auto; }
.cmd-bulkbar-note { font-size: var(--fs-50); color: var(--text-muted); font-style: italic; }

/* The per-row selection checkbox lives in the name cell (no extra column), set off
   from the name so the two do not crowd. */
.cmd-vm-select { margin-right: var(--sp-2); vertical-align: middle; cursor: pointer; }
.cmd-vm-namecell .cmd-vm-link { vertical-align: middle; }

/* The bulk-progress dialog: one live row per VM, plus an honest summary line. */
.cmd-bulk-progress { display: flex; flex-direction: column; gap: 1px; max-height: 320px; overflow: auto; margin-bottom: var(--sp-3); }
.cmd-bulk-prow { display: flex; align-items: center; justify-content: space-between; gap: var(--sp-3); padding: var(--sp-1) var(--sp-2); border-radius: var(--r-sm); }
.cmd-bulk-prow:nth-child(odd) { background: rgba(127, 127, 127, 0.05); }
.cmd-bulk-pname { font-size: var(--fs-75); color: var(--text); overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.cmd-bulk-status { font-size: var(--fs-50); color: var(--text-muted); flex: none; }
.cmd-bulk-status.is-running { color: var(--accent); }
.cmd-bulk-status.is-ok { color: var(--ok); }
.cmd-bulk-status.is-fail { color: var(--err); }
.cmd-bulk-summary.is-ok { color: var(--ok); }
.cmd-bulk-summary.is-warn { color: var(--warn); }
.cmd-bulk-summary.is-err { color: var(--err); }

.ds-cap {
  font-size: var(--fs-75);
  margin-bottom: var(--sp-1);
}

.ds-meter {
  margin: 0;
  max-width: 200px;
}

/* ---------- modal -------------------------------------------------------- */

.modal-backdrop {
  position: fixed;
  inset: 0;
  /* Solid, not blurred: backdrop-filter re-rasterizes the whole page every frame
     and made the dialog feel heavy/janky. A plain wash is instant and still reads. */
  background: rgba(3, 5, 8, 0.64);
  display: flex;
  align-items: center;
  justify-content: center;
  padding: var(--sp-6);
  z-index: 100;
  animation: modal-backdrop-in var(--dur-2) ease-out;
}

.modal {
  width: 100%;
  max-width: 460px;
  /* Bounded: the modal never exceeds the viewport; its body scrolls while the
     header and action footer stay pinned (see .modal-body / .modal .form-actions). */
  max-height: min(760px, calc(100vh - 48px));
  display: flex;
  flex-direction: column;
  background: var(--bg-raised);
  border: 1px solid var(--border-strong);
  border-radius: var(--r-modal);
  box-shadow: var(--shadow-xl);
  overflow: hidden;
  animation: modal-in var(--dur-3) var(--ease-entrance);
}

@keyframes modal-in {
  from { opacity: 0; transform: scale(0.97) translateY(6px); }
  to { opacity: 1; transform: scale(1) translateY(0); }
}

@keyframes modal-backdrop-in {
  from { opacity: 0; }
  to { opacity: 1; }
}

.modal-head {
  flex: 0 0 auto;
  display: flex;
  align-items: flex-start;
  justify-content: space-between;
  gap: var(--sp-3);
  padding: var(--sp-4) var(--sp-5);
  border-bottom: var(--sp-px) solid var(--border);
}

.modal-head-text {
  display: flex;
  flex-direction: column;
  gap: var(--sp-px);
  min-width: 0;
}

/* Window controls (minimize + close) sit together at the top-right of the head. */
.modal-head-actions {
  display: flex;
  align-items: center;
  gap: var(--sp-px);
  flex: none;
}

.modal-title {
  font-size: var(--fs-300);
  line-height: var(--lh-tight);
}

.modal-subtitle {
  margin: 0;
  font-size: var(--fs-75);
  font-weight: var(--fw-regular);
  color: var(--text-dim);
}

.modal-close {
  background: none;
  border: none;
  color: var(--text-dim);
  font-size: var(--fs-500);
  line-height: 1;
  cursor: pointer;
  padding: var(--sp-px) 6px;
  border-radius: 4px;
}

.modal-close:hover {
  color: var(--text);
  background: var(--bg-hover);
}

/* Minimize button — same quiet treatment as the close ×, sitting just before it. */
.modal-min {
  background: none;
  border: none;
  color: var(--text-dim);
  font-size: var(--fs-400);
  line-height: 1;
  cursor: pointer;
  padding: var(--sp-px) 6px;
  border-radius: 4px;
}
.modal-min:hover { color: var(--text); background: var(--bg-hover); }

/* A minimized dialog stays in the DOM (state intact) but out of view. */
.modal-backdrop.modal-minimized { display: none; }

/* The tray of parked dialogs. Bottom-right, above the tasks dock, so a paused
   wizard is always one click from being resumed. */
.modal-min-tray {
  position: fixed;
  right: var(--sp-4);
  bottom: 44px;
  z-index: 60;
  display: flex;
  flex-direction: column-reverse;
  gap: var(--sp-2);
  pointer-events: none;
}
.modal-min-tray:empty { display: none; }
.modal-min-chip {
  position: relative;
  pointer-events: auto;
  display: flex;
  align-items: center;
  gap: var(--sp-2);
  background: var(--bg-raised);
  border: 1px solid var(--border-strong);
  border-radius: var(--r-pill);
  padding: var(--sp-1) 6px var(--sp-1) var(--sp-3);
  box-shadow: var(--shadow-md, 0 6px 20px rgba(0, 0, 0, 0.3));
  max-width: 320px;
  animation: fade-in-up var(--dur-3) var(--ease-entrance) both;
}
.modal-min-chip-dot {
  width: 7px;
  height: 7px;
  border-radius: 50%;
  background: var(--accent);
  flex: none;
}
.modal-min-chip-label {
  background: none;
  border: none;
  color: var(--text);
  font-size: var(--fs-75);
  font-weight: 600;
  cursor: pointer;
  padding: var(--sp-px) var(--sp-px);
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
  min-width: 0;
}
.modal-min-chip-label:hover { color: var(--accent); }
.modal-min-chip-x {
  background: none;
  border: none;
  color: var(--text-dim);
  font-size: var(--fs-300);
  line-height: 1;
  cursor: pointer;
  padding: 0 var(--sp-1);
  border-radius: 4px;
  flex: none;
}
.modal-min-chip-x:hover { color: var(--err); background: var(--bg-hover); }
/* Live progress on a parked chip: a tabular "NN%" readout plus a thin fill tucked
   along the pill's bottom edge, so an upload the operator minimized stays visible. */
.modal-min-chip-pct {
  flex: none;
  font-size: var(--fs-75);
  font-weight: 600;
  color: var(--accent);
  font-variant-numeric: tabular-nums;
}
.modal-min-chip-bar {
  position: absolute;
  left: var(--sp-3);
  right: 6px;
  bottom: 3px;
  height: 2px;
  background: var(--bg-inset);
  border-radius: 1px;
  overflow: hidden;
}
.modal-min-chip-fill {
  display: block;
  height: 100%;
  width: 0;
  background: var(--accent);
  border-radius: 1px;
  transition: width var(--dur-2) linear;
}

.modal-body {
  flex: 1 1 auto;
  overflow-y: auto;
  overflow-x: hidden;
  padding: 18px var(--sp-5);
}

/* Action buttons pin to the bottom of the scrolling body so they are always
   reachable, even in a tall form — the modal never grows past the viewport.
   The negative bottom margin cancels the body's bottom padding so the bar sits
   flush at the modal's inner edge. */
.modal-body > .form-actions {
  position: sticky;
  bottom: 0;
  margin: var(--sp-5) calc(-1 * var(--sp-5)) -18px;
  padding: 14px var(--sp-5);
  background: var(--bg-raised);
  border-top: var(--sp-px) solid var(--border);
}

.modal-text {
  margin: 0 0 14px;
  color: var(--text-dim);
  font-size: var(--fs-100);
}

.modal-text strong {
  color: var(--text);
}

/* segmented control (network type picker etc.) */
.segmented {
  display: flex;
  gap: var(--sp-1);
  background: var(--bg-inset);
  border: 1px solid var(--border-strong);
  border-radius: var(--r-control);
  padding: var(--sp-px);
  margin-bottom: 6px;
}

.seg-btn {
  flex: 1 1 0;
  background: none;
  border: none;
  color: var(--text-dim);
  font-family: inherit;
  font-size: var(--fs-75);
  padding: 6px var(--sp-2);
  border-radius: 4px;
  cursor: pointer;
  transition: background var(--dur-2) var(--ease-standard), color var(--dur-2) var(--ease-standard);
}

.seg-btn:hover {
  color: var(--text);
}

.seg-btn.active {
  background: var(--accent);
  color: var(--accent-fg);
  font-weight: 600;
}

/* ---------- step wizard -------------------------------------------------- *
 * A multi-step wizard dialog: a left rail of numbered steps
 * (current highlighted, completed = check), a scrolling panel, and a sticky
 * footer with Back / Next / Finish. Built on openModal() via openWizard() in
 * components/modal.js; the modal body gets .is-wizard so the wizard owns the
 * whole body (no padding, panel scrolls, footer pinned). All values reuse the
 * design tokens so both themes and reduced-motion come along for free. */
.modal-body.is-wizard {
  padding: 0;
  overflow: hidden;
  display: flex;
  /* A steady floor so short steps don't shrink the dialog and make the rail/footer
     jump — but capped on short viewports so it never overflows the modal. */
  min-height: min(460px, calc(100vh - 120px));
}

.modal-wide:has(.wizard) {
  max-width: 960px;
}

.wizard {
  display: grid;
  grid-template-columns: 224px 1fr;
  flex: 1 1 auto;
  min-height: 0;
}

.wizard-rail {
  background: var(--bg-inset);
  border-right: var(--sp-px) solid var(--border);
  padding: var(--sp-5) var(--sp-3);
  overflow-y: auto;
}

.wizard-steps {
  list-style: none;
  margin: 0;
  padding: 0;
  display: flex;
  flex-direction: column;
  gap: var(--sp-px);
}

.wizard-step {
  position: relative;
  display: grid;
  grid-template-columns: 24px 1fr;
  gap: var(--sp-3);
  align-items: center;
  padding: var(--sp-2) var(--sp-2) var(--sp-2) var(--sp-2);
  border-radius: var(--r-control);
  font-size: var(--fs-100);
  color: var(--text-dim);
}

/* connector line down the numbered gutter between steps */
.wizard-step:not(:last-child)::before {
  content: "";
  position: absolute;
  left: calc(var(--sp-2) + var(--sp-3));
  top: calc(50% + var(--sp-3));
  width: 2px;
  height: calc(100% - 24px + var(--sp-px));
  background: var(--border);
}

.wizard-step.done:not(:last-child)::before {
  background: color-mix(in srgb, var(--accent) 55%, transparent);
}

.wizard-step-num {
  width: 24px;
  height: 24px;
  display: inline-flex;
  align-items: center;
  justify-content: center;
  border-radius: var(--r-pill);
  border: 1.5px solid var(--border-strong);
  background: var(--bg-raised);
  color: var(--text-muted);
  font-size: var(--fs-75);
  font-weight: var(--fw-semibold);
  font-variant-numeric: tabular-nums;
  transition: background var(--dur-2) var(--ease-standard),
    border-color var(--dur-2) var(--ease-standard), color var(--dur-2) var(--ease-standard);
}

.wizard-step-label {
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}

.wizard-step.current {
  color: var(--text);
  background: var(--accent-tint);
  /* A left accent keyline + heavier label so the active step reads as "you are
     here" at a glance — same selected-object language as the tree/table rows. */
  box-shadow: inset 2px 0 0 var(--accent);
  font-weight: var(--fw-semibold);
}

.wizard-step.current .wizard-step-num {
  border-color: var(--accent);
  background: var(--accent);
  color: var(--accent-fg);
}

.wizard-step.done .wizard-step-num {
  border-color: color-mix(in srgb, var(--accent) 55%, transparent);
  background: color-mix(in srgb, var(--accent) 16%, transparent);
  color: var(--accent-hover);
}

/* a completed step invalidated by a later change — an honest "needs attention" (!),
   never a false green check. */
.wizard-step.attention .wizard-step-num {
  border-color: var(--warn);
  background: var(--warn-tint);
  color: var(--warn);
}
.wizard-step.attention { color: var(--warn); }

.wizard-step.clickable {
  cursor: pointer;
}

.wizard-step.clickable:hover:not(.current) {
  background: var(--row-hover);
  color: var(--text);
}

/* The step's inner hit area is a real <button> (see modal.js renderRail). It spans
   both rail columns and re-declares the grid so the glyph/label layout is exactly
   what the <li> used to draw directly. */
.wizard-step-hit {
  grid-column: 1 / -1;
  display: grid;
  grid-template-columns: 24px 1fr;
  gap: var(--sp-3);
  align-items: center;
  width: 100%;
  appearance: none;
  background: none;
  border: 0;
  padding: 0;
  margin: 0;
  font: inherit;
  color: inherit;
  text-align: left;
}

.wizard-step.clickable .wizard-step-hit { cursor: pointer; }

.wizard-step-hit:focus-visible {
  outline: 2px solid var(--focus-ring);
  outline-offset: 2px;
  border-radius: var(--r-control);
}

/* right side: scrolling panel + pinned footer */
.wizard-main {
  display: flex;
  flex-direction: column;
  min-width: 0;
  min-height: 0;
}

.wizard-panel {
  flex: 1 1 auto;
  overflow-y: auto;
  padding: var(--sp-5) var(--sp-6);
  animation: wizard-step-in var(--dur-3) var(--ease-entrance);
}

@keyframes wizard-step-in {
  from { opacity: 0; transform: translateX(10px); }
  to { opacity: 1; transform: none; }
}

.wizard-panel.back {
  animation-name: wizard-step-in-back;
}

@keyframes wizard-step-in-back {
  from { opacity: 0; transform: translateX(-10px); }
  to { opacity: 1; transform: none; }
}

.wizard-step-title {
  margin: 0 0 var(--sp-1);
  font-size: var(--fs-300);
  font-weight: var(--fw-semibold);
  line-height: var(--lh-tight);
}

.wizard-step-desc {
  margin: 0 0 var(--sp-5);
  color: var(--text-dim);
  font-size: var(--fs-75);
}

/* a step's opening sentence — a touch larger than a form-hint, sets the task */
.wizard-step-lead {
  margin: 0 0 var(--sp-4);
  color: var(--text-dim);
  font-size: var(--fs-100);
  line-height: 1.5;
  max-width: 64ch;
}

.wizard-footer {
  flex: 0 0 auto;
  display: flex;
  align-items: center;
  gap: var(--sp-2);
  padding: var(--sp-3) var(--sp-6);
  border-top: var(--sp-px) solid var(--border);
  background: var(--bg-raised);
}

.wizard-footer-progress {
  margin-right: auto;
  color: var(--text-muted);
  font-size: var(--fs-75);
  font-variant-numeric: tabular-nums;
}

/* the blocking-count note beside a disabled final action ("N requirements to resolve") */
.wizard-footer-note {
  color: var(--warn);
  font-size: var(--fs-75);
  font-weight: var(--fw-medium);
}
.wizard-footer-note.hidden { display: none; }

/* a "not started" step: a muted hollow marker, distinct from a validated (done) one */
.wizard-step.todo { color: var(--text-muted); }

.wizard-footer .form-error {
  margin: 0 auto 0 0;
  color: var(--err);
  font-size: var(--fs-75);
}

/* a step's form fields */
.wizard-form {
  display: flex;
  flex-direction: column;
  gap: var(--sp-3);
}

/* A group heading INSIDE a wizard step. The Advanced step covers three
   unrelated subjects (boot, CPU shape, passthrough) and without a divider it
   reads as one undifferentiated wall of controls. Deliberately quieter than
   .cmd-band-title — this is a subdivision of a step, not a section of a page. */
.wizard-group {
  margin: var(--sp-2) 0 0;
  font-size: var(--fs-50);
  font-weight: 700;

  letter-spacing: 0.08em;
  color: var(--text-dim);
  border-top: var(--sp-px) solid var(--border);
  padding-top: var(--sp-3);
}
.wizard-form > .wizard-group:first-child { border-top: 0; padding-top: 0; }

/* Advanced step of New VM — its unrelated subjects (boot, CPU/memory, PCI) are
   grouped into light titled cards, so the step reads as a few clear decisions
   instead of one long undivided column of inputs. */
.cv-adv-sec {
  border: 1px solid var(--border);
  border-radius: var(--r-card);
  background: var(--bg-inset);
  padding: var(--sp-3) var(--sp-4);
}
.cv-adv-head { margin-bottom: var(--sp-3); }
.cv-adv-title { display: block; font-size: var(--fs-75); font-weight: var(--fw-semibold); color: var(--text); letter-spacing: 0.01em; }
.cv-adv-cap { display: block; margin-top: var(--sp-px); font-size: var(--fs-50); line-height: 1.45; color: var(--text-muted); }
.cv-adv-body { display: flex; flex-direction: column; gap: var(--sp-3); }
/* Compact fields sit two-up on a roomy modal, stacking when it is narrow. */
.cv-adv-grid { display: grid; grid-template-columns: 1fr 1fr; gap: var(--sp-3) var(--sp-4); }
@media (max-width: 560px) { .cv-adv-grid { grid-template-columns: 1fr; } }

/* Guest customization step — reuses the Advanced step's card + grid styles; its
   SSH-keys textarea and fields want full width and room to type. */
.cv-guest-fields { display: flex; flex-direction: column; gap: var(--sp-3); }
.cv-guest textarea { width: 100%; min-height: 58px; resize: vertical; font-family: ui-monospace, monospace; font-size: var(--fs-50); }
.cv-guest .form-field input, .cv-guest .form-field select { width: 100%; }
/* Guest Tools auto-install sits above the cloud-init customization, split by a
   hairline; dimmed when the chosen source is not a cloud image. */
.cv-guest-sep { height: 1px; background: var(--border); margin: var(--sp-2) 0; }
.cv-gt-disabled { opacity: 0.55; }

/* HA Admission Control: the two small reserve knobs (failures-to-tolerate,
   reserve %) sit side by side under the guardrail toggle. */
.cmd-adm-grid { display: flex; flex-wrap: wrap; gap: var(--sp-3) var(--sp-4); margin: var(--sp-2) 0 var(--sp-2); }

/* Backup & Restore — two titled cards instead of a flat stack of headers and
   paragraphs. Restore carries a danger accent because it replaces all state. */
.cmd-bkp { display: grid; grid-template-columns: repeat(auto-fit, minmax(340px, 1fr)); gap: var(--sp-4); align-items: start; }
.cmd-bkp-card { border: 1px solid var(--border); border-radius: var(--r-card); background: var(--bg-inset); padding: var(--sp-4); }
.cmd-bkp-danger { border-left: var(--sp-px) solid var(--err); }
.cmd-bkp-head { margin-bottom: var(--sp-3); }
.cmd-bkp-titlerow { display: flex; align-items: center; gap: var(--sp-3); flex-wrap: wrap; }
.cmd-bkp-title { margin: 0; font-size: var(--fs-200); font-weight: var(--fw-semibold); color: var(--text); }
.cmd-bkp-badge { flex: none; }
.cmd-bkp-cap { margin: var(--sp-px) 0 0; font-size: var(--fs-50); color: var(--text-muted); }
.cmd-bkp-text { margin: 0 0 var(--sp-3); font-size: var(--fs-75); line-height: 1.5; color: var(--text-dim); }
.cmd-bkp-chips { display: flex; flex-wrap: wrap; gap: var(--sp-2); margin-bottom: var(--sp-3); }
.cmd-bkp-chip { font-size: var(--fs-50); padding: var(--sp-px) var(--sp-2); border-radius: 999px; background: var(--bg-hover); color: var(--text-dim); border: 1px solid var(--border); }
.cmd-bkp-actions { display: flex; gap: var(--sp-3); flex-wrap: wrap; }

/* Custom file picker: a styled button drives a visually-hidden native input, so
   the control reads in the product's language instead of the browser's locale. */
.cmd-bkp-file-row { display: flex; align-items: center; gap: var(--sp-3); flex-wrap: wrap; margin-bottom: var(--sp-3); }
.cmd-file-native { position: absolute; width: 1px; height: 1px; padding: 0; margin: 0; overflow: hidden; clip: rect(0 0 0 0); border: 0; }
.cmd-file-pick { display: inline-flex; align-items: center; gap: var(--sp-2); padding: var(--sp-2) var(--sp-3); border: 1px solid var(--border-strong); border-radius: var(--r-control); background: var(--bg-raised); color: var(--text); font-size: var(--fs-75); cursor: pointer; }
.cmd-file-pick:hover { background: var(--bg-hover); border-color: var(--border-focus); }
/* Scoped to the backup picker — a bare .cmd-file-name also styles the datastore
   Files browser (4316), where names must stay --text and folder rows inherit
   --accent from button.cmd-file-dir; the unscoped muted grey used to leak there. */
.cmd-bkp-file-row .cmd-file-name { font-size: var(--fs-75); color: var(--text-muted); overflow-wrap: anywhere; }
.cmd-bkp-file-row .cmd-file-name.has-file { color: var(--text); font-weight: 500; }

/* The PCI passthrough picker. Scrolls rather than growing without bound: a
   host can have dozens of functions, and the step must stay usable. */
.cmd-pci-list {
  display: flex;
  flex-direction: column;
  gap: var(--sp-px);
  max-height: 230px;
  overflow-y: auto;
  border: 1px solid var(--border);
  border-radius: var(--r-card);
  padding: var(--sp-2);
  background: var(--bg-inset);
}
.cmd-pci-row {
  display: flex;
  align-items: center;
  gap: var(--sp-2);
  flex-wrap: wrap;
  padding: var(--sp-1) 6px;
  border-radius: 5px;
}
.cmd-pci-row:hover { background: var(--row-hover); }
/* A device that is not bound to vfio-pci is SHOWN, not hidden — "my GPU is
   missing" is a worse problem than "my GPU is here and says what to do" — but
   it must not look assignable. */
.cmd-pci-row.is-disabled { opacity: 0.62; cursor: default; }
.cmd-pci-row.is-disabled:hover { background: transparent; }
.cmd-pci-row .cell-sub { flex-basis: 100%; margin-left: 22px; }

/* Review step: a bordered key/value summary (the final "ready to complete" confirmation). */
.wizard-review {
  display: flex;
  flex-direction: column;
  border: 1px solid var(--border);
  border-radius: var(--r-card);
  overflow: hidden;
}

.wizard-review .rev-row {
  display: grid;
  grid-template-columns: 168px 1fr;
  gap: var(--sp-3);
  padding: var(--sp-2) var(--sp-4);
  font-size: var(--fs-100);
}

.wizard-review .rev-row:not(:last-child) {
  border-bottom: var(--sp-px) solid var(--border);
}

.wizard-review .rev-row:nth-child(even) {
  background: var(--row-hover);
}

.wizard-review .rev-k {
  color: var(--text-muted);
}

.wizard-review .rev-v {
  color: var(--text);
  font-variant-numeric: tabular-nums;
}

/* ---------- recent tasks dock ------------------------------------------- */

.tasks-dock {
  background: var(--bg-rail);
  border-top: var(--sp-px) solid var(--border);
  display: flex;
  flex-direction: column;
}

/* #27 — a one-shot flash when a toast's "View" opens the dock, to land the eye on
   it. The accent top border + inner glow pulse twice, then the class is removed. */
.tasks-dock.cmd-dock-flash {
  animation: cmd-dock-flash 600ms var(--ease-standard) 2;
}
@keyframes cmd-dock-flash {
  0%, 100% { border-top-color: var(--border); box-shadow: none; }
  50% { border-top-color: var(--accent); box-shadow: inset 0 2px 0 0 var(--accent), inset 0 12px 20px -12px var(--accent-tint); }
}
@media (prefers-reduced-motion: reduce) {
  /* No pulse; a steady accent keyline still says "here" for the flash's duration. */
  .tasks-dock.cmd-dock-flash { animation: none; border-top-color: var(--accent); }
}

/* drag handle to resize the dock; only meaningful while expanded */
.tasks-resize {
  display: none;
  height: 6px;
  cursor: ns-resize;
  flex: 0 0 auto;
}

.tasks-dock.expanded .tasks-resize {
  display: block;
}

.tasks-resize:hover,
.tasks-resize.dragging {
  background: var(--accent);
}

.tasks-header {
  display: flex;
  align-items: center;
  gap: 10px;
  height: 39px;
  padding: 0 var(--sp-4);
  cursor: pointer;
  flex: 0 0 auto;
  user-select: none;
}

.tasks-header:hover {
  background: var(--bg-hover);
}

.tasks-caret {
  color: var(--text-muted);
  transition: transform var(--dur-3) var(--ease-standard);
  font-size: var(--fs-50);
}

.tasks-dock.expanded .tasks-caret {
  transform: rotate(90deg);
}

.tasks-title {
  font-size: var(--fs-75);
  font-weight: 600;

  letter-spacing: 0.04em;
  color: var(--text-dim);
}

.tasks-count {
  font-size: var(--fs-50);
  color: var(--text-muted);
  font-variant-numeric: tabular-nums;
}

.tasks-activity {
  margin-left: auto;
  display: flex;
  align-items: center;
  gap: var(--sp-2);
  font-size: var(--fs-75);
  color: var(--text-dim);
  overflow: hidden;
  white-space: nowrap;
  text-overflow: ellipsis;
}

.tasks-body {
  overflow-y: auto;
  flex: 0 0 auto;
  height: 200px; /* JS drag on .tasks-resize adjusts this */
}

.tasks-body table {
  font-size: var(--fs-75);
}

.tasks-body th {
  height: 28px;
}

.tasks-body td {
  height: 32px;
}

.task-progress-track {
  height: 4px;
  width: 90px;
  border-radius: var(--r-pill);
  background: var(--border);
  overflow: hidden;
}

.task-progress-fill {
  height: 100%;
  background: var(--accent);
  border-radius: var(--r-pill);
  transition: width var(--dur-3) var(--ease-standard);
}

.task-result-ok { color: var(--ok); }
.task-result-err { color: var(--err); }

.tasks-empty {
  padding: var(--sp-4);
  color: var(--text-muted);
  font-size: var(--fs-75);
  text-align: center;
}

/* ---------- inline live console preview (VM detail) --------------------- */

.cpreview-head {
  display: flex;
  align-items: center;
  gap: var(--sp-3);
  margin-bottom: 10px;
}

.cpreview-label {
  font-size: var(--fs-75);
  color: var(--text-dim);

  letter-spacing: 0.04em;
  font-weight: 600;
}

.cpreview-status {
  display: inline-flex;
  align-items: center;
  gap: var(--sp-2);
  font-size: var(--fs-75);
  color: var(--text-dim);
}

.cpreview-actions {
  margin-left: auto;
  display: flex;
  gap: 6px;
}

.cpreview {
  position: relative;
  height: 360px;
  background: var(--bg-inset);
  border: 1px solid var(--border);
  border-radius: var(--r-card);
  overflow: hidden;
  cursor: pointer;
}

.cpreview-screen {
  width: 100%;
  height: 100%;
}

.cpreview-hint {
  position: absolute;
  bottom: var(--sp-2);
  right: 10px;
  font-size: var(--fs-50);
  color: #e6edf3;
  background: rgba(0, 0, 0, 0.55);
  padding: var(--sp-px) var(--sp-2);
  border-radius: var(--r-pill);
  pointer-events: none;
}

/* ---------- console page (a console that recedes) ------------------------ */

/* The console is a DEVICE, not a page, and its palette is declared here
   explicitly and DARK-COMMITTED — never derived from --bg/--bg-rail — for the
   same reason the login screen declares --auth-*: the area around a framebuffer
   is the bezel of a screen, so it stays dark under the light theme, and the very
   first painted frame is already the finished environment with no white flash
   before RFB attaches. */
.console-body {
  --con-bezel:         #06080e;
  --con-bezel-lift:    #0e1220;
  --con-panel:         #131926;
  --con-panel-top:     #1a2231;
  --con-border:        rgba(150, 170, 205, 0.13);
  --con-border-strong: rgba(150, 170, 205, 0.22);
  --con-text:          #e7edf4;
  --con-dim:           #98a2b3;
  --con-faint:         #5b6472;
  --con-accent:        #3b82f6;
  --con-ok:            #3fb950;
  --con-warn:          #d29922;
  --con-err:           #f85149;
  --con-rest:          rgba(140, 170, 220, 0.15); /* healthy hairline — all but invisible */
  --con-bar-h:         38px;
  --con-hair-h:        2px;
  --con-hot:           10px;   /* top hot-zone that summons the bar */

  margin: 0;
  height: 100vh;
  overflow: hidden;
  background: var(--con-bezel);
  color: var(--con-text);
  font-family: var(--font);
  cursor: default;
}

/* The framebuffer owns the entire viewport. Nothing is ever laid out above it,
   so no chrome can resize the guest: noVNC observes this element with a
   ResizeObserver, and a header that appears or hides would rescale the picture
   under the operator. A faint radial lift keeps the bezel from reading as a flat
   void; it is still unmistakably dark. */
.console-screen {
  position: fixed;
  inset: 0;
  z-index: 0;
  overflow: hidden;
  background:
    radial-gradient(120% 100% at 50% 38%, var(--con-bezel-lift) 0%, #080b12 58%, #05070c 100%);
}

/* The guest panel gets a hairline edge and a deep contact shadow so it reads as
   a physical screen sitting in a bezel rather than an image bleeding into black.
   The keyline is ALSO the keyboard-capture indicator: blue means your keystrokes
   are landing in the guest. That is the whole "am I captured?" story — no badge,
   no banner, one 1px ring. */
/* The guest reads as a physical screen only if its edge is visible — and against
   a near-black bezel a 10%-alpha keyline plus a black-on-black contact shadow are
   invisible, so a mostly-black boot screen looked like a black rectangle floating
   in black (the single biggest "web page" tell). This is a matte frame: a legible
   1px hairline edge, a 5px dark reveal-gap "moat" that separates the panel from
   the bezel the way a mounted screen sits in its surround, a real contact shadow,
   and a faint bluish ambient layer so the panel sits IN a lit bezel, not a void.
   box-shadow is paint-only — it never changes the element box, so noVNC's
   ResizeObserver never fires and the guest is never rescaled. */
.console-screen canvas {
  box-shadow:
    0 0 0 1px rgba(160, 186, 226, 0.22),
    0 0 0 5px rgba(6, 9, 16, 0.60),
    0 22px 55px -26px rgba(0, 0, 0, 0.85),
    0 70px 150px -70px rgba(96, 124, 184, 0.12);
  transition: box-shadow var(--dur-3) var(--ease-standard);
}
/* Crisp text is the difference between a console and a fuzzy JPEG. noVNC upscales
   by stretching the canvas ELEMENT (CSS width/height) over a native-resolution
   backing store, and never sets image-rendering — so the browser bilinear-blurs a
   720x400 boot screen up to a 1700px window (and again on a HiDPI display).

   A console is text far more often than not (BIOS / bootloader / VT / login), and
   "Fit" almost always UP-scales a low-res guest — so CRISP is the DEFAULT here, not a
   state JS must first prove. This is the fix for a blurry Fit console: previously the
   canvas defaulted to smooth and only became crisp once the JS scale-direction probe
   (syncRender) had run and landed on data-render="crisp"; any timing/measurement miss
   (canvas measured before noVNC applies the CSS scale, a probe that never re-fires)
   left a low-res boot screen bilinear-blurred. Now pixel-snapped rendering is on by
   default and is only turned OFF for a CONFIRMED down-scale (a genuine high-res
   desktop shrunk to a smaller window, where smoothing reads better) — data-render is
   set to "smooth" by syncRender solely in that case. At true 1:1 pixelated is a no-op,
   so nothing regresses. Two keywords: Chromium honours `pixelated`, Firefox
   `crisp-edges` (each ignores the other), so the later valid one wins per engine. */
.console-screen canvas {
  image-rendering: crisp-edges;
  image-rendering: pixelated;
}
.console-body[data-render="smooth"] .console-screen canvas {
  image-rendering: auto;
}
/* Captured: the blue ring sits ON TOP of the same dark reveal-gap moat so the
   frame never disappears while you type (the old held rule dropped the base frame,
   leaving only a ring over a black void). */
.console-body[data-kbd="held"] .console-screen canvas {
  box-shadow:
    0 0 0 1px rgba(59, 130, 246, 0.45),
    0 0 0 5px rgba(6, 9, 16, 0.60),
    0 0 0 6px rgba(59, 130, 246, 0.08),
    0 22px 55px -26px rgba(0, 0, 0, 0.85);
}
/* 1:1 mode pans with scrollbars; make them overlay-thin so they are not chrome. */
.console-screen div::-webkit-scrollbar { width: 8px; height: 8px; }
.console-screen div::-webkit-scrollbar-track { background: transparent; }
.console-screen div::-webkit-scrollbar-thumb { background: rgba(150, 175, 220, 0.16); border-radius: 4px; }
.console-screen div { scrollbar-width: thin; scrollbar-color: rgba(150, 175, 220, 0.16) transparent; }

/* ---- the hairline: the console's own status LED, and its paste meter ------ */

.con-edge {
  position: fixed;
  top: 0;
  left: 0;
  right: 0;
  height: var(--con-hot);
  z-index: 40;
}
.con-hair {
  position: absolute;
  top: 0;
  left: 0;
  right: 0;
  height: var(--con-hair-h);
  background: var(--con-rest);
  transition: background var(--dur-3) var(--ease-standard),
              opacity var(--dur-3) var(--ease-standard);
}
/* width is written from JS — the same meter exception the file header sanctions */
.con-hair-fill {
  display: block;
  height: 100%;
  width: 0;
  background: var(--con-accent);
  transition: width 90ms linear, background var(--dur-2) var(--ease-standard);
}
.con-hair-fill.is-done { background: var(--con-ok); }

.console-body[data-link="connecting"] .con-hair {
  background: linear-gradient(90deg,
    transparent 0%, rgba(59, 130, 246, 0.65) 42%, rgba(59, 130, 246, 0.95) 50%,
    rgba(59, 130, 246, 0.65) 58%, transparent 100%);
  background-size: 34% 100%;
  background-repeat: no-repeat;
  animation: con-scan 1.15s var(--ease-standard) infinite;
}
@keyframes con-scan { from { background-position: -40% 0; } to { background-position: 140% 0; } }
.console-body[data-link="retry"] .con-hair { background: var(--con-warn); animation: con-breathe 1.4s ease-in-out infinite; }
@keyframes con-breathe { 0%, 100% { opacity: 0.3; } 50% { opacity: 1; } }
.console-body[data-link="down"]  .con-hair { background: var(--con-err); height: 3px; }
.console-body[data-link="ended"] .con-hair { background: rgba(140, 150, 168, 0.42); }
/* An unencrypted console is a permanent, non-hideable warning: over ws:// the
   operator is typing root passwords across a link nobody is protecting. */
.console-body[data-tls="off"][data-link="live"] .con-hair { background: rgba(210, 153, 34, 0.55); }
.console-body[data-paste="on"] .con-hair { background: rgba(59, 130, 246, 0.16); animation: none; }
.con-hair.is-bell { background: var(--con-accent); transition: none; }

/* ---- the connection pill ------------------------------------------------- */

.con-bar {
  position: fixed;
  z-index: 50;
  top: 12px;
  left: 50%;
  transform: translate(-50%, -14px);
  display: flex;
  align-items: center;
  gap: 2px;
  height: var(--con-bar-h);
  padding: 0 7px 0 11px;
  border: 1px solid var(--con-border);
  border-radius: 11px;
  background: linear-gradient(180deg, rgba(26, 34, 49, 0.9), rgba(19, 25, 38, 0.9));
  -webkit-backdrop-filter: blur(14px) saturate(1.25);
  backdrop-filter: blur(14px) saturate(1.25);
  box-shadow:
    0 18px 46px -22px rgba(0, 0, 0, 0.85),
    0 2px 8px -4px rgba(0, 0, 0, 0.6),
    inset 0 1px 0 rgba(255, 255, 255, 0.06);
  opacity: 0;
  pointer-events: none;
  transition: opacity var(--dur-3) var(--ease-standard),
              transform var(--dur-3) var(--ease-entrance);
}
.con-bar.is-shown { opacity: 1; transform: translate(-50%, 0); pointer-events: auto; }
.con-mark { color: var(--con-accent); flex: none; margin-right: 8px; }
.con-name {
  font-size: var(--fs-75);
  font-weight: var(--fw-semibold);
  color: var(--con-text);
  letter-spacing: -0.005em;
  max-width: 30ch;
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}
.con-dot {
  width: 6px;
  height: 6px;
  border-radius: 50%;
  flex: none;
  margin: 0 2px 0 8px;
  background: var(--con-faint);
  transition: background var(--dur-2) var(--ease-standard),
              box-shadow var(--dur-2) var(--ease-standard);
}
.console-body[data-link="live"]       .con-dot { background: var(--con-ok);     box-shadow: 0 0 0 3px rgba(63, 185, 80, 0.14); }
.console-body[data-link="connecting"] .con-dot { background: var(--con-accent); box-shadow: 0 0 0 3px rgba(59, 130, 246, 0.16); }
.console-body[data-link="retry"]      .con-dot { background: var(--con-warn);   box-shadow: 0 0 0 3px rgba(210, 153, 34, 0.16); }
.console-body[data-link="down"]       .con-dot { background: var(--con-err);    box-shadow: 0 0 0 3px rgba(248, 81, 73, 0.18); }

/* Guest Caps Lock. noVNC reports it as a tri-state and null means the guest never
   told us, so the glyph stays absent rather than claiming a confident "off" —
   the cheapest possible explanation for a root password that keeps failing. */
.con-caps {
  margin-left: 6px;
  padding: 1px 5px;
  border-radius: 4px;
  background: rgba(210, 153, 34, 0.18);
  color: #f0cd7e;
  font: var(--fw-bold) 9px/13px var(--mono);
  letter-spacing: 0.06em;
}

.con-rule { width: 1px; height: 17px; margin: 0 7px; background: var(--con-border); flex: none; }

/* Icon-first controls. The text label is in the DOM (a11y, and it reveals on
   hover) but occupies no width at rest, so the pill reads as a device strip
   rather than a row of web buttons. */
.con-act {
  position: relative;
  display: inline-flex;
  align-items: center;
  gap: 0;
  height: 28px;
  min-width: 28px;
  padding: 0 6px;
  border: 0;
  border-radius: 8px;
  background: transparent;
  color: var(--con-dim);
  font: var(--fw-medium) var(--fs-75)/1 var(--font);
  cursor: pointer;
  transition: background var(--dur-2) var(--ease-standard),
              color var(--dur-2) var(--ease-standard);
}
.con-act:hover { background: rgba(255, 255, 255, 0.075); color: var(--con-text); }
.con-act:active { background: rgba(255, 255, 255, 0.11); }
.con-act:focus-visible { outline: none; box-shadow: 0 0 0 2px rgba(59, 130, 246, 0.55); color: var(--con-text); }
.con-act[aria-pressed="true"] { color: var(--con-accent); background: rgba(59, 130, 246, 0.13); }
.con-act-alarm { color: var(--con-err); }
.con-act-alarm:hover { background: rgba(248, 81, 73, 0.14); color: #ffb3ae; }
.con-ic {
  width: 15px;
  height: 15px;
  flex: none;
  fill: none;
  stroke: currentColor;
  stroke-width: 1.35;
  stroke-linecap: round;
  stroke-linejoin: round;
}
.con-act-t {
  max-width: 0;
  opacity: 0;
  overflow: hidden;
  white-space: nowrap;
  transition: max-width var(--dur-3) var(--ease-standard),
              opacity var(--dur-2) var(--ease-standard),
              margin var(--dur-3) var(--ease-standard);
}
.con-act:hover .con-act-t,
.con-act:focus-visible .con-act-t { max-width: 96px; opacity: 1; margin-left: 6px; }

/* Speed is INFORMATION, not an icon: a mangled paste is diagnosed by it, so its
   value must be readable without hovering anything. Mono, tabular, quiet. */
.con-chip {
  position: relative;
  height: 26px;
  padding: 0 8px;
  margin: 0 2px;
  border: 1px solid var(--con-border);
  border-radius: 6px;
  /* A light lift so the chip belongs to the strip instead of reading as a sunken
     hole punched into the pill (a dark inset among the ghost icon buttons). */
  background: rgba(255, 255, 255, 0.05);
  color: var(--con-dim);
  font: var(--fw-medium) var(--fs-50)/1 var(--mono);
  cursor: pointer;
  transition: color var(--dur-2) var(--ease-standard),
              border-color var(--dur-2) var(--ease-standard);
}
.con-chip:hover { color: var(--con-text); border-color: var(--con-border-strong); }
.con-chip:focus-visible { outline: none; box-shadow: 0 0 0 2px rgba(59, 130, 246, 0.55); }

/* Leader mode: every bound letter appears as a keycap on its own control, so the
   chord system teaches itself and never needs a help screen. Scoped to
   [data-key] so an unbound control (Pin) never paints an empty badge. */
.con-act[data-key]::after,
.con-chip[data-key]::after {
  content: attr(data-key);
  position: absolute;
  top: -3px;
  right: -3px;
  min-width: 12px;
  height: 12px;
  padding: 0 2px;
  border-radius: 3px;
  background: var(--con-accent);
  color: #fff;
  font: var(--fw-bold) 8px/12px var(--mono);

  opacity: 0;
  transform: scale(0.7);
  transition: opacity var(--dur-2) var(--ease-standard),
              transform var(--dur-2) var(--ease-entrance);
  pointer-events: none;
}
.console-body[data-leader="on"] .con-act[data-key]::after,
.console-body[data-leader="on"] .con-chip[data-key]::after { opacity: 1; transform: scale(1); }

/* ---- transient status ---------------------------------------------------- */

.con-whisper {
  position: fixed;
  z-index: 44;
  left: 50%;
  bottom: 22px;
  transform: translateX(-50%) translateY(5px);
  max-width: min(680px, 88vw);
  padding: 7px 13px;
  border: 1px solid var(--con-border);
  border-radius: 8px;
  background: rgba(9, 13, 20, 0.82);
  -webkit-backdrop-filter: blur(12px);
  backdrop-filter: blur(12px);
  color: var(--con-dim);
  font: var(--fw-medium) var(--fs-75)/1.35 var(--font);
  opacity: 0;
  pointer-events: none;
  transition: opacity var(--dur-3) var(--ease-standard),
              transform var(--dur-3) var(--ease-standard);
}
.con-whisper.is-on { opacity: 1; transform: translateX(-50%) translateY(0); }
.con-whisper[data-tone="ok"]   { color: #9be3a8; }
.con-whisper[data-tone="warn"] { color: #f0cd7e; border-color: rgba(210, 153, 34, 0.34); }
.con-whisper[data-tone="err"]  { color: #ffb3ae; border-color: rgba(248, 81, 73, 0.38); }
.console-body[data-kbd="released"] .con-whisper { bottom: 62px; }

.con-focus {
  position: fixed;
  z-index: 45;
  left: 50%;
  bottom: 22px;
  transform: translateX(-50%);
  height: 30px;
  padding: 0 15px;
  border: 1px solid var(--con-border-strong);
  border-radius: var(--r-pill);
  background: rgba(11, 16, 23, 0.84);
  -webkit-backdrop-filter: blur(10px);
  backdrop-filter: blur(10px);
  color: var(--con-dim);
  font: var(--fw-medium) var(--fs-75)/1 var(--font);
  cursor: pointer;
  transition: color var(--dur-2) var(--ease-standard),
              border-color var(--dur-2) var(--ease-standard);
}
.con-focus:hover { color: var(--con-text); border-color: var(--con-accent); }

.con-legend {
  position: fixed;
  z-index: 45;
  left: 50%;
  top: calc(12px + var(--con-bar-h) + 8px);
  transform: translateX(-50%);
  padding: 5px 11px;
  border: 1px solid var(--con-border);
  border-radius: 7px;
  background: rgba(9, 13, 20, 0.8);
  -webkit-backdrop-filter: blur(12px);
  backdrop-filter: blur(12px);
  /* One step brighter than --con-faint + medium weight: still quiet, but actually
     readable — the legend teaches the chord and is useless if it can't be read. */
  color: rgba(150, 170, 205, 0.66);
  font: var(--fw-medium) var(--fs-50)/1 var(--mono);
  letter-spacing: 0.02em;
  pointer-events: none;
}
.con-legend b { color: var(--con-accent); font-weight: var(--fw-bold); }

/* Screen-reader-only live region (app.css had no such utility). */
.con-sr {
  position: absolute;
  width: 1px;
  height: 1px;
  margin: -1px;
  padding: 0;
  overflow: hidden;
  clip-path: inset(50%);
  white-space: nowrap;
  border: 0;
}

/* ---- key palette --------------------------------------------------------- */

.con-keys-panel { width: min(520px, 92vw); }
.con-keys { display: flex; flex-direction: column; gap: 2px; }
.con-keys-row {
  display: flex;
  align-items: center;
  gap: var(--sp-3);
  width: 100%;
  padding: 8px 10px;
  border: 0;
  border-radius: 8px;
  background: transparent;
  color: var(--con-dim);
  font: var(--fw-medium) var(--fs-75)/1 var(--font);
  text-align: left;
  cursor: pointer;
  transition: background var(--dur-2) var(--ease-standard),
              color var(--dur-2) var(--ease-standard);
}
.con-keys-row:hover { background: rgba(255, 255, 255, 0.06); color: var(--con-text); }
.con-keys-row:focus-visible { outline: none; box-shadow: 0 0 0 2px rgba(59, 130, 246, 0.55); }
.con-keys-combo { display: flex; align-items: center; gap: 3px; flex: none; min-width: 168px; }
.con-keys-what { color: var(--con-faint); }
.con-keys-row:hover .con-keys-what { color: var(--con-dim); }

/* A chord reads as something you PRESS. The 2px bottom border is the whole
   trick: it gives a flat span the depth of a physical keycap. */
.ckey {
  display: inline-block;
  min-width: 20px;
  padding: 2px 6px;
  border: 1px solid var(--con-border-strong);
  border-bottom-width: 2px;
  border-radius: 5px;
  background: rgba(0, 0, 0, 0.3);
  color: var(--con-text);
  font: var(--fw-medium) 11px/1.25 var(--mono);
  text-align: center;
}
.ckey-plus { color: var(--con-faint); font-size: var(--fs-10); }

/* ---- clipboard bridge ---------------------------------------------------- */
/* The always-works path when the browser blocks the OS clipboard (plain HTTP /
   insecure context). Restyled onto the console palette; behaviour untouched. */

.clip-overlay {
  position: fixed;
  inset: 0;
  z-index: 300;
  display: flex;
  align-items: center;
  justify-content: center;
  background: rgba(4, 6, 12, 0.66);
  -webkit-backdrop-filter: blur(3px);
  backdrop-filter: blur(3px);
}
.clip-panel {
  width: min(600px, 92vw);
  padding: 22px 22px 18px;
  border: 1px solid var(--con-border);
  border-radius: 14px;
  background: linear-gradient(180deg, var(--con-panel-top), var(--con-panel));
  box-shadow:
    0 24px 70px -26px rgba(0, 0, 0, 0.7),
    0 4px 14px -8px rgba(0, 0, 0, 0.5),
    inset 0 1px 0 rgba(255, 255, 255, 0.05);
}
.clip-title {
  margin-bottom: 12px;
  color: var(--con-text);
  font-size: var(--fs-200);
  font-weight: var(--fw-semibold);
}
.clip-text {
  width: 100%;
  min-height: 168px;
  resize: vertical;
  padding: 11px 12px;
  background: #0d121c;
  color: var(--con-text);
  border: 1px solid #2a3342;
  border-radius: 9px;
  font: 13px/1.55 var(--mono);
  transition: border-color var(--dur-2) var(--ease-standard),
              box-shadow var(--dur-2) var(--ease-standard);
}
.clip-text:focus { outline: none; border-color: var(--con-accent); box-shadow: 0 0 0 3px rgba(59, 130, 246, 0.18); }
.clip-actions { display: flex; gap: var(--sp-2); justify-content: flex-end; margin-top: var(--sp-3); }

/* Restraint is a promise, not a preference. */
@media (prefers-reduced-motion: reduce) {
  .con-bar,
  .con-whisper,
  .con-act-t,
  .con-hair,
  .con-hair-fill,
  .console-screen canvas,
  .con-act[data-key]::after,
  .con-chip[data-key]::after { transition: none; }
  .console-body[data-link="connecting"] .con-hair,
  .console-body[data-link="retry"] .con-hair { animation: none; opacity: 1; }
}
@media (max-width: 700px) {
  .con-name { max-width: 14ch; }
  .con-act-t { display: none; }
  .con-keys-combo { min-width: 0; }
}

/* ---------- toasts ------------------------------------------------------- */

.toast-container {
  position: fixed;
  right: var(--sp-5);
  bottom: 60px;
  display: flex;
  flex-direction: column;
  gap: 10px;
  z-index: 200;
  max-width: 380px;
}

.toast {
  display: flex;
  align-items: flex-start;
  gap: var(--sp-3);
  background: var(--bg-raised);
  border: 1px solid var(--border-strong);
  border-left: var(--sp-px) solid var(--grey);
  border-radius: var(--r-card);
  padding: var(--sp-3) var(--sp-4);
  font-size: var(--fs-100);
  box-shadow: var(--shadow-md);
  animation: toast-in var(--dur-3) var(--ease-entrance);
  overflow-wrap: break-word;
  cursor: pointer;
}
.toast-ic {
  flex: none;
  width: 20px;
  height: 20px;
  margin-top: 1px;
  border-radius: var(--r-pill);
  display: inline-flex;
  align-items: center;
  justify-content: center;
  font-size: var(--fs-75);
  font-weight: var(--fw-bold);
  line-height: 1;
}
.toast-msg { flex: 1; min-width: 0; }
/* #27 — a toast's action button (e.g. "View" → open Recent Tasks). It reads as a
   quiet inline link so the message stays primary, but it is a real focusable button. */
.toast-action {
  flex: none;
  align-self: center;
  background: transparent;
  border: 1px solid var(--border-strong);
  border-radius: var(--r-sm);
  color: var(--accent);
  font-size: var(--fs-50);
  font-weight: var(--fw-bold);
  padding: 2px var(--sp-2);
  cursor: pointer;
  transition: background var(--dur-2) var(--ease-standard), border-color var(--dur-2) var(--ease-standard);
}
.toast-action:hover { background: var(--accent-tint); border-color: var(--accent); }
.toast-action:focus-visible { outline: 2px solid var(--accent); outline-offset: 1px; }
.toast-success { border-left-color: var(--ok); }
.toast-success .toast-ic { background: var(--ok-tint); color: var(--ok); }
.toast-error { border-left-color: var(--err); }
.toast-error .toast-ic { background: var(--err-tint); color: var(--err); }
.toast-warn { border-left-color: var(--warn); }
.toast-warn .toast-ic { background: var(--warn-tint); color: var(--warn); }

.toast-out { animation: toast-out 160ms var(--ease-exit) forwards; }

@keyframes toast-in {
  from { opacity: 0; transform: translateY(8px); }
  to { opacity: 1; transform: translateY(0); }
}

@keyframes toast-out {
  to { opacity: 0; transform: translateY(8px); }
}

/* ---------- polish pass: scrollbars, selection, reduced motion ---------- */

::-webkit-scrollbar {
  width: 11px;
  height: 11px;
}

::-webkit-scrollbar-track {
  background: transparent;
}

::-webkit-scrollbar-thumb {
  background: var(--border-strong);
  border-radius: 6px;
  border: 3px solid transparent;
  background-clip: content-box;
}

::-webkit-scrollbar-thumb:hover {
  background: var(--text-muted);
  background-clip: content-box;
}

* {
  scrollbar-width: thin;
  scrollbar-color: var(--border-strong) transparent;
}

::selection {
  background: var(--accent-tint);
}

@media (prefers-reduced-motion: reduce) {
  *,
  *::before,
  *::after {
    animation-duration: 0.001ms !important;
    animation-iteration-count: 1 !important;
    transition-duration: 0.001ms !important;
    scroll-behavior: auto !important;
  }
  /* Loading indicators are functional feedback ("work in progress"), so keep
     them turning even under reduced-motion — a spinner frozen mid-load reads as
     a hang. */
  .loading::before,
  .spinner,
  .cmd-spin,
  .btn.is-loading::after,
  .meter-indeterminate::after {
    animation-duration: 0.8s !important;
    animation-iteration-count: infinite !important;
  }
}

/* ---------- datastore file browser -------------------------------------- */

.modal-wide {
  max-width: 760px;
  /* Complex dialogs (create wizards) may use the full viewport height rather
     than the 760px cap simple dialogs get, so the form breathes. */
  max-height: calc(100vh - 48px);
}

.ds-crumbs {
  display: flex;
  align-items: center;
  gap: var(--sp-1);
  flex-wrap: wrap;
  margin-bottom: var(--sp-3);
  font-size: var(--fs-75);
}

.ds-listing {
  max-height: 52vh;
  overflow-y: auto;
}

.ds-dir {
  background: none;
  border: none;
  color: var(--accent);
  font-family: inherit;
  font-size: var(--fs-100);
  font-weight: 600;
  cursor: pointer;
  padding: 0;
  text-align: left;
}

.ds-dir:hover {
  text-decoration: underline;
}

/* Host tab: Hardware / Services / Security helpers ----------------------- */

/* Configure fact/control panels. The Certificate / Time / Licensing sub-tabs
   rendered label→value facts as a full-width "Field | Value" table, so on a wide
   host pane the value stranded far to the right of its label. These lay the facts
   in a readable card beside their controls — filling the pane as a responsive
   pair instead of one table stretched across the void. */
.cfg-grid { display: grid; grid-template-columns: repeat(auto-fit, minmax(min(100%, 300px), 1fr)); gap: var(--sp-4); align-items: start; }
.cfg-card-title { margin: 0 0 var(--sp-3); font-size: var(--fs-100); font-weight: 600; color: var(--text); }
.cfg-card .form-field { margin-bottom: var(--sp-3); }
.cfg-card .cmd-inline-actions { margin: var(--sp-3) 0 0; }
/* PEM textareas need monospace and height; raise specificity so .cert-ta wins
   over the generic .form-field textarea it is rendered inside. */
.form-field textarea.cert-ta { font-family: var(--mono); min-height: 120px; white-space: pre; line-height: 1.45; }

/* A muted note above a table (PCI passthrough / services explainers). */
.pad-note {
  margin: 0 0 14px;
  max-width: 70ch;
}

/* Secondary line inside a table cell (device class, service description). */
.cell-sub {
  color: var(--text-dim);
  font-size: var(--fs-75);
  margin-top: var(--sp-px);
}

/* A rejected host-update upload, pinned to the row (not a vanishing toast). */
.cmd-upload-err {
  color: #f0868f;
  white-space: normal;
  max-width: 340px;
  margin-top: var(--sp-2);
  line-height: 1.35;
}

/* GPU / passthrough-relevant rows get a faint accent so they stand out. */
.row-accent td {
  background: var(--accent-tint, rgba(56, 139, 253, 0.06));
}

/* Header row with a title on the left and an action bar on the right. */
.panel-head {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: var(--sp-3);
  margin: 26px 0 var(--sp-3);
}

.panel-head .section-title {
  margin: 0;
}

/* Small account dot before an administrator's username. */
.user-glyph {
  color: var(--accent);
  font-size: var(--fs-50);
  vertical-align: middle;
}

/* Create VM: PCI passthrough device checklist. */
.pci-list {
  display: flex;
  flex-direction: column;
  gap: 6px;
}

/* Edit VM dialog: sectioned settings ----------------------------------- */
.edit-section {
  padding: var(--sp-3) 0;
  border-top: var(--sp-px) solid var(--border);
}
.edit-section:first-of-type {
  border-top: none;
  padding-top: var(--sp-1);
}
.edit-section-title {
  font-size: var(--fs-75);
  font-weight: 600;
  letter-spacing: 0.02em;

  color: var(--text-dim);
  margin: 0 0 10px;
}
/* Edit-settings footer: the live change summary sits on the left (margin-auto
   pushes the buttons to the right) and reads out exactly what Apply will send. */
.edit-foot {
  display: flex;
  align-items: center;
  gap: 12px;
  margin-top: var(--sp-4);
  padding-top: var(--sp-3);
  border-top: var(--sp-px) solid var(--border);
}
.edit-changes {
  margin-right: auto;
  font-size: var(--fs-75);
  color: var(--text-dim);
  line-height: 1.4;
}
.edit-changes-active {
  color: var(--text);
}
.edit-changes strong {
  font-weight: 600;
  color: var(--text);
}
.dev-list {
  display: flex;
  flex-direction: column;
  gap: var(--sp-2);
}
.dev-row {
  padding: var(--sp-1) var(--sp-2);
  border-radius: var(--r-sm, 6px);
  transition: background var(--dur-2) var(--ease-standard);
}
.dev-row:hover {
  background: var(--bg-hover);
}

/* vmnicN label for physical NICs. */
.nic-vmnic {
  font-weight: 600;
  color: var(--text);
}

/* ======================================================================
   Virtual Machines — interactive list redesign
   ====================================================================== */

/* Name cell becomes a flex row so the status dot leads the name+subline. */
.cell-name {
  display: flex;
  align-items: center;
  gap: 10px;
}
.vm-name-wrap {
  display: flex;
  flex-direction: column;
  min-width: 0;
}
.vm-name-text {
  font-weight: 600;
}
.vm-name-sub {
  font-size: var(--fs-50);
  color: var(--text-muted, var(--text-dim));
  font-weight: 400;
  margin-top: var(--sp-px);
}


/* Row hover: accent left-edge + reveal-on-hover chevron marks rows navigable. */
tbody tr.row-click {
  transition: background var(--dur-2) var(--ease-standard), box-shadow var(--dur-2) var(--ease-standard);
}
tbody tr.row-click:hover {
  box-shadow: inset 2px 0 0 var(--accent);
}
.row-chevron {
  color: var(--text-muted, var(--text-dim));
  opacity: 0;
  transform: translateX(-3px);
  transition: opacity var(--dur-2) var(--ease-standard), transform var(--dur-2) var(--ease-standard);
  text-align: right;
  vertical-align: middle;
}
tr.row-click:hover .row-chevron {
  opacity: 1;
  transform: none;
}

/* Optimistic "starting…/stopping…" badge shimmer while an action is in flight. */
.badge.is-pending {
  opacity: 0.9;
  animation: pending-shimmer 1.4s ease-in-out infinite;
}
@keyframes pending-shimmer {
  0%, 100% { opacity: 0.55; }
  50% { opacity: 1; }
}

/* A brief tint when a VM's power state actually changes between polls. */
@keyframes row-flash {
  from { background: var(--accent-tint, rgba(56, 139, 253, 0.14)); }
  to { background: transparent; }
}
tr.row-flash td,
.vm-card.row-flash {
  animation: row-flash 1s ease-out;
}

/* Skeleton first-paint rows. */
.skel-bar {
  display: block;
  height: 12px;
  border-radius: 4px;
  background: linear-gradient(
    90deg,
    var(--bg-inset, var(--bg-hover)) 25%,
    var(--bg-hover) 37%,
    var(--bg-inset, var(--bg-hover)) 63%
  );
  background-size: 400px 100%;
  animation: skel 1.3s ease-in-out infinite;
}
.skel-sm { width: 40%; }
.skel-md { width: 60%; }
.skel-lg { width: 80%; }
@keyframes skel {
  0% { background-position: -200px 0; }
  100% { background-position: 200px 0; }
}

/* Filter toolbar bits (segmented/toolbar already exist). */
.filter-input {
  min-width: 220px;
  max-width: 320px;
}

/* ---------- fleet-scale tables: search box, sort headers, cap note ------- */
/* The three controls views/parts.js adds to a table that is read at fleet
   scale. They are deliberately quiet: a 200-row inventory is already dense, so
   a sort header must read as a column label until you reach for it. */
.cmd-search { display: inline-flex; align-items: center; gap: var(--sp-2); }
.cmd-search-clear { flex: none; }

/* A sort control is a REAL button (keyboard-reachable, with an accessible
   name), styled to disappear into the header row until it is hovered, focused
   or active. It inherits the th's uppercase/tracking so the column label looks
   identical whether or not the column can be sorted. */
.cmd-sort {
  display: inline-flex;
  align-items: center;
  gap: var(--sp-1);
  background: none;
  border: 0;
  padding: 0;
  margin: 0;
  color: inherit;
  font: inherit;
  text-transform: inherit;
  letter-spacing: inherit;
  cursor: pointer;
}
.cmd-sort:hover { color: var(--text); }
.cmd-sort.is-sorted { color: var(--text); }
.cmd-sort-caret { font-size: var(--fs-50); opacity: 0; transition: opacity var(--dur-2) var(--ease-standard); }
.cmd-sort:hover .cmd-sort-caret,
.cmd-sort:focus-visible .cmd-sort-caret { opacity: 0.6; }
.cmd-sort.is-sorted .cmd-sort-caret { opacity: 1; color: var(--accent); }

/* The capped-render footer. It states how many rows are held back — it is the
   opposite of a silent omission, so it is legible, not a whisper. It lives in
   its own <tbody> so it can never be read as the last row of a folder, and that
   cell contributes no padding of its own. */
.cmd-vms-capfoot td { padding: 0; }
.cmd-table-cap {
  display: flex;
  align-items: center;
  gap: var(--sp-2);
  flex-wrap: wrap;
  padding: var(--sp-3) var(--sp-4);
  font-size: var(--fs-75);
  color: var(--text-dim);
  border-top: var(--sp-px) solid var(--border);
}

/* A poll landing while an operator reads the table must not blank it. When a
   refresh fails after the first successful read the table STAYS, and this strip
   says the readings are no longer being updated. */
.cmd-stale-note {
  display: flex;
  align-items: center;
  gap: var(--sp-2);
  flex-wrap: wrap;
  padding: var(--sp-2) var(--sp-3);
  margin-bottom: var(--sp-2);
  font-size: var(--fs-75);
  color: var(--warn);
  background: var(--bg-inset);
  border: var(--sp-px) solid var(--border);
  border-left: 3px solid var(--warn);
}
.toolbar-count {
  color: var(--text-dim);
  font-size: var(--fs-75);
  margin-left: var(--sp-3);
  white-space: nowrap;
}

/* Card / grid density view. */
.vm-grid {
  display: grid;
  grid-template-columns: repeat(auto-fill, minmax(264px, 1fr));
  gap: 14px;
}
.vm-card {
  background: var(--bg-raised);
  border: 1px solid var(--border);
  border-radius: var(--r-card);
  padding: 14px var(--sp-4);
  cursor: pointer;
  transition:
    transform var(--dur-2) cubic-bezier(0.2, 0.7, 0.3, 1),
    border-color var(--dur-2) var(--ease-standard),
    box-shadow var(--dur-2) var(--ease-standard);
}
.vm-card:hover {
  transform: translateY(-2px);
  border-color: var(--border-strong);
  box-shadow: var(--shadow-lg);
}
.vm-card-head {
  display: flex;
  align-items: center;
  gap: var(--sp-2);
  margin-bottom: var(--sp-2);
}
.vm-card-name {
  font-weight: 600;
  flex: 1;
  min-width: 0;
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}
.vm-card-meta {
  font-size: var(--fs-75);
  color: var(--text-dim);
}
.vm-card-ips {
  font-size: var(--fs-50);
  color: var(--text-muted, var(--text-dim));
  font-family: var(--mono);
  margin-top: var(--sp-px);
  min-height: 14px;
}
.vm-card-actions {
  margin-top: var(--sp-3);
  display: flex;
  flex-wrap: wrap;
  gap: 6px;
}
/* ISO images list: disc glyph + name. */
.iso-disc {
  width: 18px;
  height: 18px;
  color: var(--accent);
  flex: 0 0 auto;
}
.iso-name {
  font-weight: 500;
}

/* Warning shown before deleting a port group that still has VMs attached. */
.vm-attach-warning {
  margin: var(--sp-3) 0;
  padding: 10px var(--sp-3);
  border: 1px solid var(--warn);
  border-radius: 8px;
  background: color-mix(in srgb, var(--warn) 12%, transparent);
}
.vm-attach-warning-title {
  font-weight: 600;
  color: var(--warn);
  font-size: var(--fs-100);
}
.vm-attach-warning-body {
  margin-top: var(--sp-1);
  font-size: var(--fs-75);
  color: var(--text-dim);
}

/* Service-adapter connectivity-test result line. */
.adapter-test-result {
  margin-top: var(--sp-3);
  display: flex;
  align-items: center;
  gap: var(--sp-2);
  min-height: 20px;
}
.adapter-test-detail {
  font-size: var(--fs-75);
  color: var(--text-dim);
}

/* ---------- Health dashboard ---------- */
.health { display: flex; flex-direction: column; gap: var(--sp-4); }
.health .kpi-grid {
  display: grid; grid-template-columns: repeat(auto-fit, minmax(150px, 1fr)); gap: var(--sp-3);
}
.health .kpi-tile {
  background: var(--bg-raised); border: 1px solid var(--border);
  border-radius: var(--r-card); padding: 14px var(--sp-4);
}
.health .kpi-label { color: var(--text-muted); font-size: var(--fs-50); font-weight: 600; letter-spacing: 0.05em; }
.health .kpi-value { font-size: var(--fs-600); font-weight: 700; margin-top: var(--sp-1); }
.health-panel {
  background: var(--bg-raised); border: 1px solid var(--border);
  border-radius: var(--r-card); padding: 14px var(--sp-4);
}
.health-panel > h3 { margin: 0 0 10px; font-size: var(--fs-200); }
.health-bar { margin: var(--sp-2) 0; }
.health-bar-head { display: flex; justify-content: space-between; font-size: var(--fs-100); margin-bottom: var(--sp-1); }
.health-bar-note { color: var(--text-muted); }
.health-bar-track { height: 8px; background: var(--bg-inset); border-radius: 4px; overflow: hidden; }
.health-bar-fill { height: 100%; border-radius: 4px; background: var(--accent); }
.health-bar-fill.health-warn { background: var(--warn); }
.health-bar-fill.health-err { background: var(--err); }
/*
 * HA readiness checklist. A grid, not .health-row's flex space-between: the
 * operator scans this top-to-bottom deciding what to go and fix, so the badge
 * sits in a fixed gutter and title/detail/remedy share one left edge. Three
 * children under space-between land at three different x positions per row and
 * the column stops existing.
 */
.cmd-ha-check {
  display: grid;
  grid-template-columns: 92px minmax(0, 1fr);
  align-items: baseline;
  gap: var(--sp-1) var(--sp-3);
  padding: 14px 0;
  border-top: var(--sp-px) solid var(--border);
}
.cmd-ha-check:first-of-type { border-top: none; }
/* Passing checks in the wizard collapse to a tight single line. */
.cmd-ha-check-ok { padding: 9px 0; align-items: center; }
.cmd-ha-check-ok .cmd-ha-check-title { font-weight: 500; color: var(--text-dim); }

/* Not-enabled entry card: the launcher for the HA setup wizard. */
.cmd-ha-setup {
  border: var(--sp-px) solid var(--border);
  border-radius: var(--r-md);
  background: var(--bg-inset);
  padding: var(--sp-4);
}
.cmd-ha-setup > .cmd-net-title { margin-top: 0; }
.cmd-ha-setup-blockers {
  list-style: none;
  margin: var(--sp-2) 0 0;
  padding: 0;
  display: flex;
  flex-direction: column;
  gap: var(--sp-1);
}
.cmd-ha-setup-blockers li {
  display: flex;
  align-items: center;
  gap: var(--sp-2);
  font-size: var(--fs-100);
  color: var(--text-dim);
}

/* Review step: a compact label/value summary before arming. */
.cmd-ha-review {
  border: var(--sp-px) solid var(--border);
  border-radius: var(--r-md);
  overflow: hidden;
  margin-bottom: var(--sp-3);
}
.cmd-ha-review-row {
  display: flex;
  justify-content: space-between;
  gap: var(--sp-3);
  padding: 12px var(--sp-3);
  border-top: var(--sp-px) solid var(--border);
}
.cmd-ha-review-row:first-child { border-top: none; }
.cmd-ha-review-label { color: var(--text-dim); }
.cmd-ha-review-val { font-weight: 600; color: var(--text); text-align: right; }

/* A row of small action buttons in a section header (e.g. Datastores: SAN + Add). */
.cmd-inline-actions { display: flex; gap: var(--sp-2); align-items: center; }

/* SimpleVirt SAN wizard: per-host disk-claim cards + the live capacity readout. */
.cmd-san-host {
  border: var(--sp-px) solid var(--border);
  border-radius: var(--r-md);
  padding: var(--sp-3);
  margin-bottom: var(--sp-2);
}
/* #40 — host card header: name on the left, the "Claim all empty / Clear" bulk
   control on the right. */
.cmd-san-host-head {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: var(--sp-3);
  margin-bottom: var(--sp-2);
}
.cmd-san-host-head .cmd-san-host-name { margin-bottom: 0; }
.cmd-san-bulk { flex: none; }
.cmd-san-host-name { font-weight: 600; color: var(--text); margin-bottom: var(--sp-2); }
.cmd-san-disk {
  display: grid;
  grid-template-columns: auto 1fr auto auto;
  align-items: center;
  gap: var(--sp-2) var(--sp-3);
  padding: 4px 0;
}
.cmd-san-disk.is-disabled { opacity: 0.55; }
.cmd-san-disk-path { font-family: var(--mono); font-size: var(--fs-100); }
.cmd-san-disk-size { font-weight: 600; text-align: right; }
.cmd-san-disk-meta { color: var(--text-dim); font-size: var(--fs-75); text-align: right; }
.cmd-san-capacity {
  display: flex;
  flex-wrap: wrap;
  gap: var(--sp-4);
  margin: var(--sp-3) 0 var(--sp-1);
  padding: var(--sp-3);
  border: var(--sp-px) solid var(--border);
  border-radius: var(--r-md);
  background: var(--bg-inset);
}
.cmd-san-cap { display: flex; flex-direction: column; }
.cmd-san-cap-val { font-size: var(--fs-300); font-weight: 700; color: var(--text); }
.cmd-san-cap-label { font-size: var(--fs-75); color: var(--text-dim); }
.cmd-ha-check-badge { justify-self: start; }
.cmd-ha-check-title { font-weight: 600; color: var(--text); font-size: var(--fs-100); }
.cmd-ha-check-detail,
.cmd-ha-check-remedy {
  grid-column: 2;
  margin: 0;
  font-size: var(--fs-75);
  line-height: 1.5;
}
.cmd-ha-check-detail { color: var(--text-dim); }
/* A remedy is the operator's next action — tinted so it reads as actionable
   rather than as another line of prose. The tint tracks the STATUS: red means a
   failed requirement, amber means we could not confirm one, and a passing check
   carries a caveat, not a task. Painting all three red would tell the operator
   something is broken when we only mean we could not look. */
.cmd-ha-check-remedy {
  grid-column: 2;
  border-radius: var(--r-control);
  padding: var(--sp-2) 10px;
}
.cmd-ha-remedy-fail { color: var(--err); background: var(--err-tint); }
.cmd-ha-remedy-warn,
.cmd-ha-remedy-unknown { color: var(--warn); background: var(--warn-tint); }
.cmd-ha-remedy-pass { color: var(--text-dim); background: var(--bg-inset); }

/* HA status banner tints beyond the shared warn/err/ok set: a neutral "not
   active" (muted) and an active-informational (accent). */
.cmd-banner-info { background: var(--accent-tint); color: var(--info); }
.cmd-banner-muted { background: var(--bg-inset); color: var(--text-dim); }

/* Failover mode selector: two option cards, radio semantics. The active card is
   ringed and its dot filled; the whole card is the click target. */
.cmd-ha-modes {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(240px, 1fr));
  gap: 10px;
  margin: var(--sp-2) 0 var(--sp-1);
}
.cmd-ha-mode {
  border: 1px solid var(--border-strong);
  border-radius: var(--r-card);
  padding: var(--sp-3) 14px;
  background: var(--bg-inset);
  cursor: pointer;
  transition: border-color var(--dur-2) var(--ease-standard), background var(--dur-2) var(--ease-standard);
}
.cmd-ha-mode:hover { border-color: var(--accent); }
.cmd-ha-mode.active {
  border-color: var(--accent);
  background: var(--accent-tint);
  cursor: default;
}
.cmd-ha-mode.is-busy { opacity: 0.6; cursor: default; pointer-events: none; }
.cmd-ha-mode:focus-visible { outline: 2px solid var(--accent); outline-offset: 2px; }
.cmd-ha-mode-head { display: flex; align-items: center; gap: var(--sp-2); }
.cmd-ha-mode-radio {
  width: 13px;
  height: 13px;
  border-radius: 50%;
  border: 2px solid var(--border-strong);
  flex: none;
  transition: border-color var(--dur-2) var(--ease-standard), box-shadow var(--dur-2) var(--ease-standard);
}
.cmd-ha-mode.active .cmd-ha-mode-radio {
  border-color: var(--accent);
  box-shadow: inset 0 0 0 3px var(--accent);
}
.cmd-ha-mode-title { font-weight: 600; color: var(--text); font-size: var(--fs-100); }
.cmd-ha-mode-badge { margin-left: auto; }
.cmd-ha-mode-desc { margin: 6px 0 0; font-size: var(--fs-75); line-height: 1.5; color: var(--text-dim); }
/* A migration type the VM's current state can't take (e.g. storage-only on a
   stopped VM): shown, greyed, with the reason underneath so the choice is legible
   rather than missing. Reuses .cmd-ha-mode above for the card shell. */
.cmd-ha-mode.disabled {
  opacity: 0.55;
  cursor: not-allowed;
  pointer-events: none;
  background: var(--bg-inset);
  border-color: var(--border);
}

/* Migrate wizard: the destination compatibility panel. Each precheck finding is a
   line — refusals block the move (red), advisories are worth a look (amber) — and
   a clean pass gets a single green line. */
.cmd-mig-reasons { display: grid; gap: 6px; margin: 10px 0 var(--sp-1); }
.cmd-mig-reason,
.cmd-mig-ok {
  font-size: var(--fs-75);
  line-height: 1.45;
  border-radius: var(--r-control);
  padding: var(--sp-2) 10px;
  border: 1px solid transparent;
}
.cmd-mig-reason.refuse { color: var(--err); background: var(--err-tint); border-color: var(--err); }
.cmd-mig-reason.warn { color: var(--warn); background: var(--warn-tint); border-color: var(--warn); }
.cmd-mig-ok { color: var(--ok); background: var(--ok-tint); border-color: var(--ok); margin: 10px 0 var(--sp-1); }

/* Pending failover approval card (ask-me-first mode). The verify-truly-dead
   warning is load-bearing, not decoration: approving vouches the host is dead. */
.cmd-ha-pending {
  border: 1px solid var(--warn);
  border-radius: var(--r-card);
  background: var(--warn-tint);
  padding: 14px var(--sp-4);
  margin-bottom: var(--sp-3);
}
.cmd-ha-pending-q { margin: 0 0 var(--sp-2); font-weight: 600; font-size: var(--fs-200); color: var(--text); }
.cmd-ha-pending-warn { margin: 0 0 var(--sp-2); font-size: var(--fs-75); line-height: 1.5; color: var(--text-dim); }
.cmd-ha-pending-warn strong { color: var(--warn); }
.cmd-ha-pending-meta { margin: 0 0 10px; font-size: var(--fs-75); color: var(--text-muted); }
.cmd-ha-vmlist {
  display: flex;
  flex-direction: column;
  gap: 6px;
  margin-bottom: var(--sp-3);
  padding: 10px var(--sp-3);
  border-radius: var(--r-control);
  background: var(--bg-raised);
}
.cmd-ha-vm { display: flex; align-items: baseline; flex-wrap: wrap; gap: var(--sp-2); }
.cmd-ha-vm-name { font-weight: 600; font-size: var(--fs-100); color: var(--text); }
.cmd-ha-vm-plan { font-size: var(--fs-75); color: var(--text-dim); }
.cmd-ha-vm-badge { align-self: center; }
.cmd-ha-vm-outcome { width: 100%; margin: 0; font-size: var(--fs-75); color: var(--text-muted); line-height: 1.4; }

.health-row { display: flex; justify-content: space-between; gap: var(--sp-3); font-size: var(--fs-100); padding: var(--sp-px) 0; }
.health-row-label { color: var(--text-muted); }
.health-svc { display: flex; align-items: center; gap: var(--sp-2); font-size: var(--fs-100); padding: var(--sp-px) 0; }
.health-svc-name { flex: 1; }
.health-svc-state { color: var(--text-muted); }
.health-banner {
  display: flex; align-items: center; gap: 10px;
  padding: var(--sp-3) var(--sp-4); border-radius: var(--r-card); border: 1px solid var(--border);
  background: var(--bg-raised); font-weight: 500;
}
.health-banner.health-ok { border-color: var(--ok); background: var(--ok-tint); }
.health-banner.health-warn { border-color: var(--warn); background: var(--warn-tint); }
.health-banner.health-err { border-color: var(--err); background: var(--err-tint); }
.health-banner-note { color: var(--text-dim); font-weight: 400; margin-left: var(--sp-1); }
.health-alerts { list-style: none; margin: var(--sp-2) 0 0; padding: 0; display: flex; flex-direction: column; gap: var(--sp-1); }
.health-alert { padding: var(--sp-2) var(--sp-3); border-radius: 8px; font-size: var(--fs-100); border-left: var(--sp-px) solid var(--warn); background: var(--bg-raised); }
.health-alert.health-err { border-left-color: var(--err); }
/* A health alert that navigates to its object: keep the row look but reset the
   button chrome, run full width, read as clickable, and take keyboard focus. */
.health-alert-link { border: 0; border-left: var(--sp-px) solid var(--warn); background: transparent; display: block; width: 100%; text-align: left; color: var(--text); cursor: pointer; font: inherit; font-size: var(--fs-100); transition: background var(--dur-2) var(--ease-standard); }
.health-alert-link:hover { background: var(--bg-hover); }
.health-alert-link:focus-visible { outline: var(--sp-px) solid var(--focus-ring); outline-offset: 1px; }
/* HCI-on-cluster-Overview card head: the health badge next to the fsid caption. */
.cmd-hci-ov-head { display: flex; align-items: center; gap: var(--sp-2); margin-bottom: var(--sp-3); }
/* HCI Health finding: the guided-remediation action button under the finding text */
.cmd-hci-hz-action { margin-top: var(--sp-2); }
/* HCI network configuration wizard */
.cmd-wiz-modes { display: flex; flex-direction: column; gap: var(--sp-2); }
.cmd-wiz-mode { border: var(--sp-px) solid var(--border); border-radius: var(--r-md); padding: var(--sp-3); }
.cmd-wiz-mode.is-selected { border-color: var(--accent); background: var(--accent-tint); }
.cmd-wiz-mode.is-disabled { opacity: 0.55; }
.cmd-wiz-mode-title { font-weight: var(--fw-semibold); color: var(--text); }
.cmd-wiz-mode-desc { font-size: var(--fs-75); color: var(--text-dim); margin-top: var(--sp-1); }
.cmd-wiz-table { width: 100%; border-collapse: collapse; margin-top: var(--sp-2); }
.cmd-wiz-table th { text-align: left; font-size: var(--fs-50); letter-spacing: var(--ls-caps); color: var(--text-muted); padding: var(--sp-1) var(--sp-2); }
.cmd-wiz-table td { padding: var(--sp-1) var(--sp-2); vertical-align: middle; }
.cmd-wiz-form { display: flex; flex-direction: column; gap: var(--sp-3); }
.cmd-wiz-precheck { display: flex; flex-direction: column; gap: var(--sp-1); }
.cmd-precheck-row { display: flex; justify-content: space-between; gap: var(--sp-3); padding: var(--sp-1) 0; border-bottom: var(--sp-px) solid var(--border); font-size: var(--fs-75); }
.cmd-precheck-name { color: var(--text); }
.cmd-precheck-detail { color: var(--text-dim); text-align: right; }
.cmd-precheck-err .cmd-precheck-name { color: var(--err); }
.cmd-precheck-warn .cmd-precheck-name { color: var(--warn); }
.cmd-apply-list { display: flex; flex-direction: column; gap: var(--sp-1); margin: var(--sp-2) 0; }
.cmd-apply-row { display: flex; justify-content: space-between; align-items: center; padding: var(--sp-1) var(--sp-2); background: var(--bg-inset); border-radius: var(--r-sm); }
.cmd-apply-host { color: var(--text); }
/* VM virtual-hardware appliance rows (CD/DVD, and the CPU/memory value+Edit) */
.cmd-hw-row { display: flex; align-items: center; justify-content: space-between; gap: var(--sp-3); padding: var(--sp-2) 0; }
.cmd-hw-row-main { display: flex; align-items: center; gap: var(--sp-2); }
.cmd-hw-row-actions { display: flex; gap: var(--sp-2); }
.cmd-hw-icon { opacity: 0.85; }
/* per-object Recent activity feed (activityPanel) */
.cmd-activity { display: flex; flex-direction: column; gap: var(--sp-1); }
.cmd-activity-row { display: flex; align-items: center; gap: var(--sp-2); padding: var(--sp-1) 0; border-bottom: var(--sp-px) solid var(--border); }
.cmd-activity-row:last-child { border-bottom: none; }
.cmd-activity-name { flex: 1; color: var(--text); font-size: var(--fs-75); }
.cmd-activity-meta { color: var(--text-muted); font-size: var(--fs-50); }
/* Global Health Center */
.cmd-hc-head { padding: var(--sp-4) var(--sp-5) var(--sp-2); }
.cmd-hc-title { font-size: var(--fs-500); font-weight: var(--fw-bold); letter-spacing: var(--ls-tight); margin: 0 0 var(--sp-1); }
.cmd-hc-summary { display: flex; align-items: center; gap: var(--sp-3); margin: 0 var(--sp-5) var(--sp-4); padding: var(--sp-3); border-radius: var(--r-md); background: var(--bg-raised); border: var(--sp-px) solid var(--border); }
.cmd-hc-summary.is-crit { border-left: var(--sp-1) solid var(--err); }
.cmd-hc-summary.is-warn { border-left: var(--sp-1) solid var(--warn); }
.cmd-hc-summary.is-ok { border-left: var(--sp-1) solid var(--ok); }
.cmd-hc-summary-text { color: var(--text-dim); font-size: var(--fs-100); }
.cmd-hc-grid { display: grid; grid-template-columns: repeat(auto-fill, minmax(340px, 1fr)); gap: var(--sp-4); padding: 0 var(--sp-5) var(--sp-6); }
.cmd-hc-card { display: flex; flex-direction: column; }

/* LDAP admin form helpers */
.form-subhead { margin: 14px 0 var(--sp-1); font-weight: 500; font-size: var(--fs-100); color: var(--text-dim); }
.form-ok { color: var(--ok); }

/* ======================================================================== *
 *  SimpleVirt-Command — controller-specific helpers                             *
 * ======================================================================== */

/* label/value row used across host, cluster and VM cards */
.stat-row {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: var(--sp-3);
  padding: var(--sp-1) 0;
  font-size: var(--fs-100);
}
.stat-row .stat-value { text-align: right; overflow-wrap: anywhere; }

.cmd-host-name { display: inline-flex; align-items: center; gap: var(--sp-2); }
.cmd-host-name .card-title { margin-bottom: 0; }
.cmd-card-actions { display: flex; justify-content: flex-end; margin-top: var(--sp-1); }

/* A SECTION HEADING is not a button row. .cmd-card-actions above is
   `justify-content: flex-end` because it exists to park buttons at the foot of a
   card; every section header in the product used to borrow it, which shoved the
   TITLE to the right edge as well and left it colliding with its own Add button.
   Titles read from the left. Actions group at the right. */
.cmd-section-head {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: var(--sp-3);
  margin-top: var(--sp-1);
}
/* The title's own bottom margin came from a stacked-heading context and pushed
   it out of alignment with the buttons beside it. Inside a section head the row
   owns the spacing. */
.cmd-section-head > .cmd-net-title { margin-bottom: 0; }
.cmd-section-head-actions { display: flex; align-items: center; gap: var(--sp-2); flex-shrink: 0; }

/* aggregated VM table trimmings */
.cmd-tmpl-badge { margin-left: var(--sp-2); }

/* "Distributed" marker: a switch realized from a Logical Distributed Switch. The
   accent hue + the dswitch glyph make a cluster-managed switch read apart from a
   host-local one at a glance. */
.cmd-dsw-badge {
  display: inline-flex; align-items: center; gap: var(--sp-1);
  margin-left: var(--sp-2); padding: var(--sp-px) var(--sp-2) var(--sp-px) var(--sp-1);
  font-size: var(--fs-50); font-weight: 600; line-height: 1.5;
  color: var(--accent); background: color-mix(in srgb, var(--accent) 12%, transparent);
  border: 1px solid color-mix(in srgb, var(--accent) 35%, transparent);
  border-radius: 999px; vertical-align: middle;
}
.cmd-dsw-badge-ic { width: 13px; height: 13px; }

/* File browser rows: a folder is a borderless link with a folder glyph; a file
   is plain. Replaces the old "[dir] name" literal with a real icon + name. */
.cmd-file-row { display: inline-flex; align-items: center; gap: var(--sp-2); }
.cmd-file-ic { font-size: var(--fs-200); line-height: 1; }
.cmd-file-name { overflow-wrap: anywhere; }
button.cmd-file-dir {
  background: none; border: none; padding: 0; margin: 0; cursor: pointer;
  color: var(--accent); font: inherit; text-align: left;
}
button.cmd-file-dir:hover .cmd-file-name { text-decoration: underline; }

/* Shared content utilities referenced by wizards/dialogs but previously undefined:
   a bounded, scrollable checkbox list (e.g. "Mount on additional hosts"), an inset
   explanatory callout, and a right-aligned action cell for table rows. */
.cmd-check-list { display: flex; flex-direction: column; gap: var(--sp-1); max-height: 240px; overflow-y: auto; }
.cmd-callout { padding: var(--sp-3); background: var(--bg-inset); border: 1px solid var(--border); border-radius: var(--r-md); color: var(--text-dim); font-size: var(--fs-75); line-height: 1.5; }
/* callout tones + parts (warningCard) */
.cmd-callout-warn { border-left: var(--sp-px) solid var(--warn); }
.cmd-callout-err { border-left: var(--sp-px) solid var(--err); }
.cmd-callout-title { font-weight: var(--fw-semibold); color: var(--text); margin-bottom: var(--sp-1); }
.cmd-callout-msg { color: var(--text-dim); }
.cmd-callout-action { margin-top: var(--sp-2); }
/* netLabel — "name · VLAN nn" */
.cmd-netlabel { display: inline-flex; align-items: baseline; gap: var(--sp-1); }
.cmd-netlabel-vlan { font-size: var(--fs-50); color: var(--text-muted); font-variant-numeric: tabular-nums; }
/* expander — shared "Technical details" disclosure */
.cmd-expander { margin: var(--sp-2) 0; }
.cmd-expander-summary { cursor: pointer; color: var(--text-dim); font-size: var(--fs-75); padding: var(--sp-1) 0; user-select: none; }
.cmd-expander-summary:hover { color: var(--text); }
.cmd-expander-body { padding: var(--sp-2) 0 0; }
/* info badge tone (in-progress / informational, blue — never amber for running) */
.badge-info { color: var(--info); background: var(--accent-tint); border-color: color-mix(in srgb, var(--info) 30%, transparent); }
/* standalone input (confirmDialog type-to-confirm, inline filters) — the base
   input style is otherwise scoped to .form-field input, so bare inputs need this */
.cmd-modal-input { width: 100%; box-sizing: border-box; background: var(--bg-inset); border: var(--sp-px) solid var(--border); border-radius: var(--r-sm); color: var(--text); font: inherit; font-size: var(--fs-100); padding: var(--sp-2); margin-top: var(--sp-1); }
.cmd-modal-input:focus { outline: none; border-color: var(--border-focus); box-shadow: 0 0 0 var(--sp-px) var(--focus-ring); }
.cmd-row-actions { text-align: right; white-space: nowrap; }

/* Test-connectivity (ping) result: a clear pass/fail panel instead of a badge
   tucked under the buttons — the operator ran a test and wants the answer. */
.cmd-ping-result { margin-top: 14px; padding: var(--sp-3) 14px; border-radius: 8px; border: 1px solid var(--border); background: var(--bg-inset); }
.cmd-ping-result.is-ok { border-color: color-mix(in srgb, var(--ok) 45%, transparent); background: color-mix(in srgb, var(--ok) 10%, transparent); }
.cmd-ping-result.is-fail { border-color: color-mix(in srgb, var(--err) 45%, transparent); background: color-mix(in srgb, var(--err) 10%, transparent); }
.cmd-ping-result-head { display: flex; align-items: center; gap: 10px; font-weight: 600; }
.cmd-ping-result-detail { margin-top: 6px; color: var(--text-dim); font-size: var(--fs-75); }
.cmd-ping-result-detail .mono { font-family: var(--mono, ui-monospace, monospace); }

/* Cluster convergence "all good" line — quiet reassurance, not an alarm. */
.cmd-converged-ok { color: var(--ok); }
/* A host/cluster NAME chip. These are names, not machine values (an octet or a
   device id), so they read as plain UI text — monospace here was noise, not signal. */
.cmd-host-tag {
  font-size: var(--fs-75);
  color: var(--text-dim);
}
.cmd-inline-check { margin-bottom: 0; }

/* Lost-connection VMs. A VM whose host has stopped answering is kept in the
   list, dimmed and labelled last-known — never removed (a VM blinking out of
   existence is the single most alarming thing an operator can see) and never
   asserted as live. The row is quieted, not hidden: still readable, clearly not
   current. */
.cmd-vms-lostnote:empty { display: none; }
.cmd-vms-lostnote { margin: 0 0 var(--sp-3); }
tr.cmd-vm-lost { opacity: 0.7; }
tr.cmd-vm-lost:hover { opacity: 1; }
tr.cmd-vm-lost .cmd-vm-link { color: var(--text-dim); }
.cmd-host-tag-lost { color: var(--warn); }
/* The last-known state cell: the badge on top, a quiet "was Running, 2:14 ago"
   underneath, so the age of the reading is visible without a hover. */
.cmd-vm-laststate { display: inline-flex; flex-direction: column; gap: var(--sp-px); align-items: flex-start; }
.cmd-vm-laststate .cell-sub { font-variant-numeric: tabular-nums; }
/* A VM HA is failing over: the same stacked cell (the move, then "HA failover →
   host" underneath), but the row is NOT dimmed — it is a transient on its way to a
   new host, not a memory of a lost one (the tree makes the same distinction). */
.cmd-vm-failover-state { display: inline-flex; flex-direction: column; gap: var(--sp-px); align-items: flex-start; }
tr.cmd-vm-failover .cmd-host-tag-lost { color: var(--text-dim); }
/* A last-known VM node in the inventory tree reads as remembered, not live. */
.cmd-tree-row.is-stale .cmd-tree-label { font-style: italic; opacity: 0.82; }

/* VMs view: header actions + clickable VM name */
.cmd-header-right { display: inline-flex; align-items: center; gap: var(--sp-3); }
.cmd-vm-link {
  background: none;
  border: none;
  padding: 0;
  font: inherit;
  font-weight: 500;
  color: var(--accent);
  cursor: pointer;
  text-align: left;
}
.cmd-vm-link:hover { text-decoration: underline; }

/* ============================================================
   Datacenter-console layout: inventory tree + tabbed object
   detail + Recent Tasks dock (the cmd-* namespace).
   ============================================================ */
.cmd-shell {
  display: grid;
  grid-template-columns: var(--rail-w) 1fr;
  /* The first row is the connectivity/notice banner. It is `auto`, so when the
     banner is display:none the track collapses to 0 and the topbar sits at the
     top; when the banner shows it RESERVES its own space and pushes the topbar
     down instead of overlaying (and hiding) the topbar controls during an
     outage — the exact moment those controls matter most. */
  /* Three columns now: a dedicated PRODUCT-NAV rail, the inventory EXPLORER, and
     the workspace — product navigation and infrastructure inventory are separate
     concepts and get separate columns (the new shell silhouette). */
  grid-template-columns: 88px var(--rail-w) 1fr;
  grid-template-rows: auto auto 1fr auto;
  grid-template-areas:
    "banner banner banner"
    "topbar topbar topbar"
    "productnav inventory object"
    "tasks tasks tasks";
  height: 100vh;
  overflow: hidden;
}
/* On the controller's own settings pages the inventory tree is irrelevant, so
   the explorer is dropped — but the product-nav rail stays (it is how you leave
   the page). */
.cmd-shell.cmd-no-inventory {
  grid-template-columns: 88px 1fr;
  grid-template-areas:
    "banner banner"
    "topbar topbar"
    "productnav object"
    "tasks tasks";
}
.cmd-shell.cmd-no-inventory .cmd-inventory { display: none; }
/* User-collapsible inventory: minimise the explorer to give the workspace the full
   width, then re-open it — independent of .cmd-no-inventory (which drops it
   automatically on the controller's own settings pages). Collapsing shrinks
   --rail-w to a slim rail (not 0) so an expand handle stays on screen right where
   the panel lived; the 3-column grid-areas stay intact, so it composes cleanly with
   every page (unlike overriding grid-template-columns, which would fight
   .cmd-no-inventory). Both the in-panel « handle and the topbar button drive it. */
.cmd-shell.cmd-inv-collapsed {
  --rail-w: 34px;
}
.cmd-shell.cmd-inv-collapsed .cmd-inventory { padding: 0; overflow: hidden; }
.cmd-shell.cmd-inv-collapsed .cmd-inv-head,
.cmd-shell.cmd-inv-collapsed .cmd-tree-filter,
.cmd-shell.cmd-inv-collapsed #inv-tree { display: none; }
.cmd-shell.cmd-inv-collapsed .cmd-inv-expand { display: flex; }
/* The toggle reads as pressed while the panel is hidden. */
#inv-collapse-btn[aria-pressed="true"] {
  color: var(--accent);
  background: var(--bg-hover);
}
/* Inventory header: the lens label plus the « collapse handle, on one row. */
.cmd-inv-head {
  display: flex; align-items: center; justify-content: space-between; gap: var(--sp-1);
}
.cmd-inv-collapse {
  flex: none; appearance: none; background: transparent; border: 0; cursor: pointer;
  color: var(--text-muted); border-radius: var(--r-sm);
  width: 22px; height: 22px; margin-right: 2px;
  display: inline-flex; align-items: center; justify-content: center;
}
.cmd-inv-collapse:hover { color: var(--text); background: var(--bg-hover); }
.cmd-inv-collapse:focus-visible { outline: none; box-shadow: 0 0 0 2px var(--focus-ring); }
/* Expand handle: hidden until the panel is collapsed, then the entire slim rail is
   the click target to re-open — with a vertical "Inventory" label so the rail reads
   as the parked panel, not a stray button. */
.cmd-inv-expand {
  display: none; appearance: none; background: transparent; border: 0; cursor: pointer;
  color: var(--text-muted);
  width: 100%; height: 100%;
  flex-direction: column; align-items: center; gap: var(--sp-2);
  padding: var(--sp-3) 0;
}
.cmd-inv-expand:hover { color: var(--accent); background: var(--bg-hover); }
.cmd-inv-expand:focus-visible { outline: none; box-shadow: inset 0 0 0 2px var(--focus-ring); }
.cmd-inv-expand-label {
  writing-mode: vertical-rl;
  font-size: var(--fs-50); font-weight: 600; letter-spacing: 0.08em;
}
/* Connectivity banner: shown only when the controller is unreachable. Fixed to
   the top so it is visible regardless of scroll, amber (a warning, not an error —
   the data is stale, not wrong). */
.cmd-conn-banner {
  grid-area: banner;
  padding: var(--sp-2) var(--sp-4);
  text-align: center;
  font-size: var(--fs-75);
  font-weight: 500;
  color: var(--warn-fg);
  background: var(--warn);
  box-shadow: 0 1px 6px rgba(0, 0, 0, 0.25);
}
.cmd-conn-banner.hidden {
  display: none;
}

/* The shell's one banner row holds a COLUMN of strips, not a single element:
   the connectivity warning and the subscription notice can both be true at once,
   and two grid items in the same area would sit on top of each other. The stack
   is created around the connectivity banner the first time a second strip needs
   the row (subscription.js), so the common case adds no element at all. */
.cmd-banner-stack {
  grid-area: banner;
  display: flex;
  flex-direction: column;
}

/* The non-subscribed notice. Quiet on purpose: this is a statement, not an
   alarm — SimpleVirt gates nothing on a subscription, so the strip must not borrow
   the warning colour that means "your fleet needs attention". It sits in the
   flow above the top bar, never over it, and is dismissed for good per operator. */
.cmd-sub-notice {
  display: flex;
  align-items: center;
  gap: var(--sp-3);
  padding: var(--sp-2) var(--sp-4);
  font-size: var(--fs-75);
  color: var(--text-dim);
  background: var(--bg-inset);
  border-bottom: 1px solid var(--border);
}
.cmd-sub-notice-text { flex: 1 1 auto; min-width: 0; }
.cmd-sub-notice-text strong { color: var(--text); font-weight: 600; }
.cmd-sub-notice-detail { color: var(--text-dim); }
.cmd-sub-notice-actions {
  display: flex;
  flex: 0 0 auto;
  gap: var(--sp-2);
}

/* Administration -> Subscription */
.cmd-sub-form { margin-top: var(--sp-4); max-width: 640px; }
.cmd-sub-actions { display: flex; gap: var(--sp-2); margin-top: var(--sp-3); flex-wrap: wrap; }
.cmd-sub-none { color: var(--text-dim); margin: var(--sp-3) 0; }
.cmd-sub-check { margin-top: var(--sp-3); }

.cmd-topbar {
  grid-area: topbar;
  position: relative;
  z-index: 20;
  display: flex;
  align-items: center;
  gap: var(--sp-4);
  height: 48px;
  padding: 0 14px;
  /* Nearly black, flat, with a single restrained bottom border — no gradient,
     no floating-plane shadow. The bar is part of the shell, not a taped-on plane. */
  background: var(--topbar-bg);
  border-bottom: 1px solid var(--topbar-border);
  color: var(--topbar-text);
}
.cmd-brand {
  display: flex; align-items: center; gap: var(--sp-2);
  padding-right: 14px;
  border-right: var(--sp-px) solid var(--topbar-border);
}
/* On the navy brand surface the mark takes the topbar's brand tint rather than
   the accent .brand-mark carries by default. Size comes from .brand-mark. */
.cmd-brand .brand-mark {
  color: var(--topbar-brand);
}
/* The topbar wears the real product shield (a raster mark) rather than the flat
   monogram. Sized by height so it stays crisp on HiDPI; width follows the art. */
.cmd-brand-logo {
  height: 30px;
  width: auto;
  display: block;
  flex: 0 0 auto;
}
.cmd-brand-name { font-weight: 700; font-size: var(--fs-200); letter-spacing: 0.2px; color: var(--topbar-text); }
/* The product wordmark is two-tone, as in the logo: "Simple" in the surface's own text
   colour, "Virt" in the brand blue. The bar is a fixed dark surface in both themes, so
   one blue serves both. The words stay one text run for assistive tech. */
.cmd-brand-name .wm-virt { color: var(--topbar-wordmark); }
.cmd-topbar-right { margin-left: auto; display: flex; align-items: center; gap: 10px; }

/* ---- Top-left hamburger + slide-out navigation drawer ---- */
.cmd-menu-btn { padding: 6px; color: var(--topbar-text-dim); }
.cmd-menu-btn:hover:not(:disabled) { color: var(--topbar-text); background: var(--topbar-hover); }
.cmd-menu-btn.active { color: var(--topbar-brand); }

/* ==========================================================================
   Global application shell — ONE intentional stacking hierarchy.
   The navigation drawer is a shell-root element (a sibling of #topbar in
   index.html, never inside route content), and it is namespaced `cmd-nav-*`
   so no route/component class (e.g. the right-side details `.cmd-drawer`) can
   ever clobber its z-index or geometry — the bug that let the scrim sit above
   the drawer and swallow every click.

   Layer                         z-index
   ---------------------------   -------
   base application content        0
   sticky/header (topbar)         50   (.cmd-topbar)
   Recent Tasks dock              20
   details drawer scrim/panel     50/51 (.cmd-drawer-scrim / .cmd-drawer)
   NAV MENU BACKDROP (scrim)      90   (.cmd-nav-scrim)   <- below the drawer
   NAV MENU DRAWER                95   (.cmd-nav-drawer)  <- ABOVE its backdrop
   modal/dialog backdrop + dialog 100+ (.cmd-modal*)      <- above the drawer

   Invariant: the nav drawer (95) is always ABOVE its own backdrop (90), and
   both are below modals. Do not "fix" stacking by bumping arbitrary z-index —
   keep these relationships. Sits below the 48px topbar so the hamburger stays
   visible to toggle the drawer closed.
   ========================================================================== */
.cmd-nav-scrim {
  position: fixed;
  inset: var(--sp-12) 0 0 0;
  background: rgba(4, 6, 9, 0.5);
  opacity: 0;
  transition: opacity var(--dur-3) var(--ease-standard);
  z-index: 90; /* below .cmd-nav-drawer */
  pointer-events: auto; /* catches clicks OUTSIDE the drawer to close it */
}
.cmd-nav-scrim.open { opacity: 1; }
.cmd-nav-scrim[hidden] { display: none; }

.cmd-nav-drawer {
  position: fixed;
  top: var(--sp-12);
  left: 0;
  bottom: 0;
  width: 288px;
  max-width: 86vw;
  background: var(--bg-rail);
  border-right: var(--sp-px) solid var(--border-strong);
  box-shadow: 2px 0 16px rgba(0, 0, 0, 0.35);
  transform: translateX(-100%);
  transition: transform var(--dur-3) var(--ease-standard);
  z-index: 95; /* ABOVE .cmd-nav-scrim (90); below modals (100+) */
  pointer-events: auto; /* its menu items receive clicks directly */
  overflow-y: auto;
  padding: 10px 0 var(--sp-4);
}
.cmd-nav-drawer.open { transform: translateX(0); }
.cmd-nav-drawer[hidden] { display: none; }

.cmd-nav-drawer-head {
  padding: 6px 18px 10px;
  font-size: var(--fs-50);
  font-weight: 700;
  letter-spacing: 0.08em;

  color: var(--text-muted);
}
.cmd-nav-drawer-group { padding: var(--sp-1) 0 var(--sp-2); }
.cmd-nav-drawer-group + .cmd-nav-drawer-group { border-top: var(--sp-px) solid var(--border); margin-top: var(--sp-1); padding-top: 10px; }
.cmd-nav-drawer-group-title {
  padding: var(--sp-1) 18px 6px;
  font-size: var(--fs-50);
  font-weight: 700;
  letter-spacing: 0.06em;

  color: var(--text-muted);
}
.cmd-nav-drawer-item {
  display: flex;
  align-items: center;
  gap: var(--sp-3);
  width: 100%;
  padding: var(--sp-2) 18px;
  background: transparent;
  border: 0;
  border-left: var(--sp-px) solid transparent;
  color: var(--text-dim);
  font-size: var(--fs-100);
  text-align: left;
  cursor: pointer;
}
.cmd-nav-drawer-item:hover { background: var(--bg-hover); color: var(--text); }
.cmd-nav-drawer-item.active {
  color: var(--text);
  background: var(--accent-tint);
  border-left-color: var(--accent);
}
.cmd-nav-drawer-item.active .cmd-nav-drawer-ic { color: var(--accent-hover); }
.cmd-nav-drawer-ic {
  flex: 0 0 auto;
  display: inline-flex;
  align-items: center;
  justify-content: center;
  width: 16px;
  height: 16px;
  color: var(--text-muted);
}
.cmd-nav-drawer-ic svg { width: 15px; height: 15px; }
.cmd-nav-drawer-item-label { flex: 1 1 auto; }
/* 'N/N hosts up' as a tonal status pill — the most-glanced datum in the chrome,
   so it reads as a colour-coded object, not flat grey text. */
.cmd-fleet-status {
  display: inline-flex; align-items: center; gap: var(--sp-2);
  padding: var(--sp-1) var(--sp-3) var(--sp-1) var(--sp-2);
  border-radius: var(--r-status);

  letter-spacing: 0.06em;
  border: 1px solid var(--topbar-border);
  background: rgba(255, 255, 255, 0.05);
  font-size: var(--fs-75);
  font-weight: var(--fw-semibold);
  font-variant-numeric: tabular-nums;
  color: var(--topbar-text-dim);
}
.cmd-fleet-status.is-ok {
  border-color: color-mix(in srgb, var(--ok) 45%, transparent);
  color: color-mix(in srgb, var(--ok) 72%, #fff);
  background: color-mix(in srgb, var(--ok) 16%, transparent);
}
.cmd-fleet-status.is-err {
  border-color: color-mix(in srgb, var(--err) 45%, transparent);
  color: color-mix(in srgb, var(--err) 80%, #fff);
  background: color-mix(in srgb, var(--err) 16%, transparent);
}
.current-user { font-size: var(--fs-100); color: var(--text-dim); }
.cmd-topbar .current-user { color: var(--topbar-text-dim); }

/* Controls on the brand surface: the generic .btn-ghost resolves to --text-dim
   and --row-hover, both of which are near-black under the light theme and would
   vanish against the navy bar. */
.cmd-topbar .btn-ghost {
  color: var(--topbar-text-dim);
}
.cmd-topbar .btn-ghost:hover:not(:disabled) {
  color: var(--topbar-text);
  background: var(--topbar-hover);
}
.cmd-topbar .btn-ghost:focus-visible {
  outline: 2px solid var(--topbar-brand);
  outline-offset: 1px;
}

.cmd-inventory {
  grid-area: inventory;
  /* Lifts a hair above the primary-nav shell so the three zones (nav → inventory
     → workspace) read apart by neutral value, never by blue. */
  background: var(--bg-inventory);
  border-right: 1px solid var(--border);
  overflow-y: auto;
  padding: var(--sp-2) 6px;
}
/* Inventory filter — sticks to the top of the scrolling rail so it stays reachable
   while the tree scrolls under it. type=search adds the native clear affordance. */
.cmd-tree-filter { position: sticky; top: 0; z-index: 1; background: var(--bg-inventory); padding: var(--sp-1) 6px var(--sp-2); }
.cmd-tree-filter-input {
  width: 100%; box-sizing: border-box;
  background: var(--bg-inset); border: var(--sp-px) solid var(--border); border-radius: var(--r-sm);
  color: var(--text); font: inherit; font-size: var(--fs-75); padding: var(--sp-1) var(--sp-2);
}
.cmd-tree-filter-input::placeholder { color: var(--text-muted); }
.cmd-tree-filter-input:focus { outline: none; border-color: var(--border-focus); box-shadow: 0 0 0 var(--sp-px) var(--focus-ring); }
.cmd-tree { font-size: var(--fs-100); }
.cmd-tree-row {
  position: relative;
  display: flex; align-items: center; gap: var(--sp-1);
  padding: var(--sp-1) 6px; border-radius: var(--r-sm); cursor: pointer; user-select: none;
  color: var(--text);
}
.cmd-tree-row:hover { background: var(--bg-hover); }
/* Selected node: a quiet charcoal surface + a bright leading keyline + brighter
   text. No blue wash — the keyline is what says "this one". */
.cmd-tree-row.active { background: var(--bg-selected); color: var(--text); }
/* The leading keyline is the SimpleVirt selection signature — a solid 2px SimpleVirt-blue
   bar. Used deliberately and ONLY for "this one, right here": the selected tree
   node, the active nav item, the selected table row. Repetition is what makes it
   read as an identity rather than decoration. */
.cmd-tree-row.active::before {
  content: "";
  position: absolute;
  left: 0; top: 0; bottom: 0;
  width: 2px;
  background: var(--accent);
}
.cmd-caret {
  width: 14px; height: 14px;
  display: inline-flex; align-items: center; justify-content: center;
  color: var(--text-muted); font-size: var(--fs-50);
  border-radius: 3px;
  transition: transform var(--dur-2), color var(--dur-2) var(--ease-standard);
  flex: none;
}
.cmd-tree-row:hover .cmd-caret { color: var(--text-dim); }
.cmd-tree-row.active .cmd-caret { color: var(--accent-hover); }
.cmd-caret:hover { background: var(--row-hover); color: var(--text); }
.cmd-caret.open { transform: rotate(90deg); }
.cmd-caret-none { visibility: hidden; }
.cmd-tree-ic-wrap { position: relative; display: inline-flex; align-items: center; flex: none; }
.cmd-tree-ic { width: 14px; height: 14px; color: var(--text-dim); flex: none; }
/* The label owns the flexible space; the trailing count takes a fixed gap. */
.cmd-tree-label { flex: 1 1 auto; min-width: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
/* State reads AS PART OF the icon: a round bead on the glyph's lower-right corner,
   so an operator takes in an object's type and its running state in a single glance
   rather than tracking a separate bar at the row's far edge. This overrides the
   shared .dot bar shape into a badge; its colour still comes from the state classes
   (.dot-ok running, .dot-warn paused, .dot-muted stopped, .dot-unknown unreachable).
   A ring in the tree's own background cuts the bead cleanly out of the glyph. */
.cmd-tree-statusdot {
  position: absolute;
  right: -3px;
  bottom: -3px;
  width: 7px;
  height: 7px;
  min-height: 0;
  margin: 0;
  /* Hard-edged square pip, not a round bead: the tree status mark now speaks the
     same squared status language as .dot / the badge keyline, instead of being
     the one round indicator that made the product read as two status vocabularies. */
  border-radius: var(--r-status);
  border: 1.5px solid var(--bg);
  box-sizing: border-box;
  transition: background-color var(--dur-2, 140ms) var(--ease-standard, ease);
}
:root[data-theme="light"] .cmd-tree-statusdot { box-shadow: none; }
.cmd-tree-badge { margin-left: var(--sp-2); flex: none; font-size: var(--fs-50); color: var(--text-muted); background: var(--bg-inset); padding: 0 6px; border-radius: 9999px; }
.cmd-tree-children { margin-left: var(--sp-3); border-left: var(--sp-px) solid color-mix(in srgb, var(--border) 70%, transparent); padding-left: var(--sp-2); }
/* Drag a host onto a cluster (or the datacenter, for standalone) to assign it. */
.cmd-tree-row.cmd-dragging { opacity: 0.45; }
.cmd-tree-row.cmd-drop-target {
  background: var(--accent-tint);
  outline: 1.5px dashed var(--accent);
  outline-offset: -2px;
}
.cmd-tree-empty, .cmd-tree-children .cmd-tree-empty { color: var(--text-muted); font-size: var(--fs-75); padding: var(--sp-1) var(--sp-2); }

/* The object shell is ONE coherent vertical region: a fixed header + tab band that
   stays put, and a single scrolling body below it. The shell itself never scrolls
   (only .cmd-obj-body does), so the header/tabs are pinned without position:sticky
   and there is exactly one vertical scrollbar for the workspace — no nested scroll,
   no header/tabs scrolling away. */
.cmd-object { grid-area: object; overflow: hidden; min-width: 0; background: var(--bg); display: flex; flex-direction: column; }
/* The content pane must never overflow its column and get clipped by the shell's
   overflow:hidden (the "right cards cut off / titles clipped" bug when a wide row
   or the menu drawer is up). Wide content (tables, diagrams) scrolls inside its
   OWN container, never the page. */
.cmd-obj-body, .cmd-obj-header, .cmd-tabs { min-width: 0; max-width: 100%; }
.cmd-object .cmd-grid, .cmd-object .cmd-band { min-width: 0; }
/* The header is now two stacked rows: the breadcrumb trail, then the title and
   its actions. */
.cmd-obj-header { flex: none; display: flex; flex-direction: column; gap: var(--sp-2); padding: var(--sp-3) var(--sp-5) 10px; }
.cmd-obj-headrow { display: flex; align-items: flex-start; gap: var(--sp-3); }

/* Breadcrumbs — the object's ancestry as a row of typed chips: each crumb is its
   type glyph + name, chevron-separated, so "where am I" reads as a drawn path
   rather than a grey slash-joined sentence. */
.cmd-crumbs { display: flex; align-items: center; flex-wrap: wrap; gap: var(--sp-px); min-height: 22px; margin: 0 calc(-1 * var(--sp-2)) var(--sp-px); }
.cmd-crumb {
  display: inline-flex;
  align-items: center;
  gap: var(--sp-1);
  font-size: var(--fs-75);
  line-height: 1.3;
  padding: var(--sp-px) var(--sp-2);
  border-radius: var(--r-sm);
  background: none;
  border: 0;
  color: var(--text-dim);
  max-width: 240px;
}
.cmd-crumb-ic { width: 13px; height: 13px; flex: none; opacity: 0.9; }
.cmd-crumb-label { overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.cmd-crumb-link { cursor: pointer; transition: background var(--dur-2) var(--ease-standard), color var(--dur-2) var(--ease-standard); }
.cmd-crumb-link:hover { background: var(--bg-hover); color: var(--text); }
.cmd-crumb-link:focus-visible { outline: 2px solid var(--focus-ring); outline-offset: 1px; }
/* The current object is stated, not linked — its glyph tints with the accent and
   the name carries more weight, so the trail resolves to a clear "you are here". */
.cmd-crumb-here { color: var(--text); font-weight: 600; cursor: default; }
.cmd-crumb-here .cmd-crumb-ic { color: var(--accent); opacity: 1; }
.cmd-crumb-sep { width: 12px; height: 12px; flex: none; color: var(--text-disabled); }
.cmd-obj-title { display: flex; align-items: center; gap: var(--sp-3); }
.cmd-obj-icon { width: 26px; height: 26px; color: var(--accent); flex: none; }
/* Tinted rounded badge behind the header glyph (mockup .oi). renderHeader wraps
   nodeIconSvg in .cmd-obj-badge. */
/* The object-header glyph sits in a SQUARE status plate that tints to the object's
   own status — the same plate language the Home / Topology / cluster heroes use —
   so the object spine reads consistently across the product. Neutral by default
   (no status); the -ok/-warn/-err/-unknown modifiers tint it from the object's
   own pill tone, never a re-derived verdict. */
.cmd-obj-badge {
  width: 36px;
  height: 36px;
  border-radius: var(--r-md);
  background: var(--bg-inset);
  border: 1px solid var(--border);
  box-sizing: border-box;
  display: flex;
  align-items: center;
  justify-content: center;
  color: var(--text-muted);
  flex: none;
}
.cmd-obj-badge-ok { background: color-mix(in srgb, var(--ok) 14%, var(--bg-inset)); color: var(--ok); border-color: color-mix(in srgb, var(--ok) 38%, var(--border)); }
.cmd-obj-badge-warn { background: color-mix(in srgb, var(--warn) 16%, var(--bg-inset)); color: var(--warn); border-color: color-mix(in srgb, var(--warn) 42%, var(--border)); }
.cmd-obj-badge-err { background: color-mix(in srgb, var(--err) 16%, var(--bg-inset)); color: var(--err); border-color: color-mix(in srgb, var(--err) 42%, var(--border)); }
.cmd-obj-badge-muted { color: var(--text-dim); }
.cmd-obj-badge-unknown { border-style: dashed; }
.cmd-obj-badge svg { width: 22px; height: 22px; }
/* Guest-OS brand logos (real SVGs under /img/os/) render as an <img>, unlike the
   crafted currentColor glyphs. object-fit keeps a non-square logo from distorting.
   Tree + breadcrumb already size it via the shared class the <img> carries
   (.cmd-tree-ic / .cmd-crumb-ic apply to any element); mini-name / header / search
   size the SVG via the parent, so those need an explicit img rule to match. */
.cmd-os-logo { object-fit: contain; flex: none; }
.cmd-obj-badge img.cmd-os-logo { width: 22px; height: 22px; }
.cmd-mini-name img.cmd-os-logo { width: 14px; height: 14px; }
.qf-row-icon img.cmd-os-logo { width: 16px; height: 16px; }
.qf-head-icon img.cmd-os-logo { width: 17px; height: 17px; }
/* Server-authoritative status shown as a pill in the title (mockup .pill). The
   tri-state colours mirror the fleet dots: ok / warn (degraded) / err. */
.cmd-pill {
  display: inline-flex;
  align-items: center;
  gap: var(--sp-2);
  padding: var(--sp-px) 10px var(--sp-px) var(--sp-2);
  border-radius: var(--r-status);
  font-size: var(--fs-50);
  font-weight: 600;

  letter-spacing: 0.07em;
  margin-left: 10px;
}
/* The pill carries a real .dot element (the tree's status bead, reused). Rather
   than hide it and draw a substitute, the bead itself is drawn as the keyline —
   so dot-unknown's "hollow, never filled" rule survives the restyle intact. */
.cmd-pill .dot,
.cmd-fleet-status .dot { height: 11px; }
.cmd-pill .dot::after,
.cmd-fleet-status .dot::after { display: none; }
.cmd-pill-ok { background: var(--ok-tint); color: var(--ok); }
.cmd-pill-warn { background: var(--warn-tint); color: var(--warn); }
.cmd-pill-err { background: var(--err-tint); color: var(--err); }
/* muted = a read state that is simply "off/idle" (a stopped VM); unknown = a
   state we could NOT read — the hollow amber keyline, never a filled claim
   (DESIGN_LANGUAGE §3.1), matching .badge-unknown and the hollow dot-unknown. */
.cmd-pill-muted { background: color-mix(in srgb, var(--grey) 12%, transparent); color: var(--text-dim); }
/* unknown = a state we could NOT read. Hollow (never a filled claim) AND neutral
   grey, not amber: amber is reserved for genuine caution (redesign brief). The
   attention a truly worrying "unknown" deserves is carried by the object it
   depends on (an unreachable host's own warn/err status + banner), not by
   colouring the absence of a reading amber. */
.cmd-pill-unknown { background: transparent; color: var(--text-muted); box-shadow: inset 0 0 0 1px var(--border-strong); }
.cmd-pill-info { background: var(--accent-tint); color: var(--info); }
/* Inline/band/table variant: the base pill has a header margin-left; drop it. */
.cmd-pill-inline { margin-left: 0; }
.cmd-pill-sm { font-size: var(--fs-25); letter-spacing: 0.05em; padding: 0 var(--sp-2) 0 6px; gap: 6px; }
.cmd-pill-sm .dot { height: 9px; }
.dot-info { background: var(--info); }
/* Unknown bead INSIDE a status pill/badge = hollow NEUTRAL (amber is caution
   only). Scoped to .cmd-pill-unknown so the inventory tree's own global
   .dot-unknown (its hollow-amber "state can't be read" safety mark, defined
   later) is left untouched — that shared class must not change here. */
.cmd-pill-unknown .dot { width: 3px; background: transparent; border: 0; box-shadow: inset 0 0 0 1.5px var(--grey); }
.cmd-obj-titletext h1 { font-size: var(--fs-600); font-weight: var(--fw-bold); letter-spacing: var(--ls-tight); line-height: var(--lh-tight); margin: 0; }
.cmd-obj-sub { font-size: var(--fs-75); color: var(--text-dim); margin-top: var(--sp-px); }
.cmd-obj-actions { margin-left: auto; display: flex; gap: var(--sp-2); flex-wrap: wrap; }
/* Enterprise tab navigation — a proper infrastructure-console tab strip, not tiny
   text links. ~40px interaction height, comfortable spacing, a strong SimpleVirt-Blue
   active indicator, and a firm hover state. The label weight is held CONSTANT
   (a hidden bold shadow reserves the width) so selecting a tab never nudges the
   row. It NEVER scrolls horizontally: it is a pinned band that uses the full width,
   and when the tabs genuinely cannot fit, lower-priority ones collapse into a
   "More ▾" overflow menu (fitObjectTabs in objectview.js) — the selected tab is
   always kept visible. No hidden scrollbar, no wheel-scroll, no clipping. */
.cmd-tabs {
  flex: none;
  display: flex;
  gap: var(--sp-1);
  padding: 0 var(--sp-6);
  border-bottom: var(--sp-px) solid var(--border);
  overflow: hidden;
}
/* The overflow menu trigger sits at the end of the strip, styled like a tab. */
.cmd-tab-more { position: relative; }
.cmd-tab-more[hidden] { display: none; }
.cmd-tab-more-caret { font-size: var(--fs-10); opacity: 0.7; margin-left: 2px; }
/* Dropdown of the collapsed tabs, anchored under the More button. */
.cmd-tab-overflow {
  position: absolute;
  top: calc(100% + var(--sp-1));
  right: 0;
  z-index: 20;
  min-width: 180px;
  padding: var(--sp-1);
  background: var(--bg-elevated, var(--bg-rail));
  border: var(--sp-px) solid var(--border);
  border-radius: var(--r-md);
  box-shadow: var(--shadow-md);
  display: flex;
  flex-direction: column;
  gap: 1px;
}
.cmd-tab-overflow[hidden] { display: none; }
.cmd-tab-overflow-item {
  display: flex;
  align-items: center;
  gap: var(--sp-2);
  padding: var(--sp-2) var(--sp-3);
  background: none;
  border: 0;
  border-radius: var(--r-sm);
  color: var(--text-muted);
  font-size: var(--fs-100);
  text-align: left;
  cursor: pointer;
  white-space: nowrap;
}
.cmd-tab-overflow-item:hover { background: var(--bg-hover); color: var(--text); }
.cmd-tab-overflow-item.active { color: var(--text); font-weight: 600; }
/* An author display rule beats the UA [hidden] rule, so hiding an overflowed tab
   needs this explicit override — otherwise .cmd-tab's inline-flex keeps it shown. */
.cmd-tab[hidden] { display: none; }
.cmd-tab {
  position: relative;
  display: inline-flex;
  align-items: center;
  gap: var(--sp-2);
  height: 40px;
  background: none;
  border: none;
  color: var(--text-muted);
  cursor: pointer;
  padding: 0 var(--sp-3);
  font-size: var(--fs-100);
  font-weight: 500;
  white-space: nowrap;
  margin-bottom: calc(-1 * var(--sp-px));
  border-radius: var(--r-sm) var(--r-sm) 0 0;
  transition: color var(--dur-2) var(--ease-standard), background var(--dur-2) var(--ease-standard);
}
/* Reserve the active (600) width up front so the weight change never reflows. */
.cmd-tab .cmd-tab-label { position: relative; }
.cmd-tab .cmd-tab-label::after {
  content: attr(data-label);
  display: block;
  height: 0;
  font-weight: 600;
  overflow: hidden;
  visibility: hidden;
  pointer-events: none;
}
.cmd-tab:hover { color: var(--text); background: var(--bg-hover); }
.cmd-tab:focus-visible { outline: 2px solid var(--focus-ring); outline-offset: -3px; border-radius: var(--r-sm); }
.cmd-tab.active { color: var(--text); font-weight: 600; }
.cmd-tab.active .cmd-tab-label { color: var(--text); }
/* The active-tab underline in SimpleVirt Blue. It grows from centre on change (a small,
   deliberate motion) and sits as a firm 2px bar when active. */
.cmd-tab::after {
  content: "";
  position: absolute;
  left: var(--sp-2);
  right: var(--sp-2);
  bottom: calc(-1 * var(--sp-px));
  height: 2px;
  border-radius: 2px 2px 0 0;
  background: var(--accent);
  transform: scaleX(0);
  transform-origin: center;
  transition: transform var(--dur-2) var(--ease-standard), opacity var(--dur-2) var(--ease-standard);
  opacity: 0;
}
.cmd-tab:hover::after { transform: scaleX(0.4); opacity: 0.45; }
.cmd-tab.active::after { transform: scaleX(1); opacity: 1; }
/* Tab-level identity: a topology glyph (⇄) for the Stretched Cluster tab and a small
   state dot (neutral / info / ok / warn) — restrained, never a fully-coloured label. */
.cmd-tab-ic { font-size: var(--fs-200); line-height: 1; opacity: 0.85; flex: none; }
.cmd-tab.active .cmd-tab-ic { opacity: 1; }
.cmd-tab-dot { width: 7px; height: 7px; border-radius: 50%; flex: none; background: var(--grey); }
.cmd-tab-dot.d-info { background: var(--info); }
.cmd-tab-dot.d-ok { background: var(--ok); }
.cmd-tab-dot.d-warn { background: var(--warn); }
.cmd-tab-dot.d-neutral { background: transparent; box-shadow: inset 0 0 0 1.5px var(--grey); }
@media (prefers-reduced-motion: reduce) {
  .cmd-tab::after { transition: none; }
}
/* The one scroll region of the object shell: it grows to fill the space under the
   pinned header/tabs and scrolls vertically. overflow-x is hidden — wide content
   (tables, diagrams) scrolls inside its OWN container, never the page. */
.cmd-obj-body { flex: 1 1 auto; min-height: 0; padding: var(--sp-5) var(--sp-6); overflow-y: auto; overflow-x: hidden; }
/* The object workspace is FLUID: it uses the full object column so dashboard grids, tables,
   charts and topology gain usable space on 1080p/1440p/4K displays — no centred island.
   (An earlier fixed 1560px cap here is what produced the narrow-centred-content regression.)
   Readable line length is a property of PROSE, not of the workspace: bare paragraphs get a
   measure below; grids/tables/cards stay full-width. */
.cmd-obj-body > * { width: 100%; min-width: 0; }
/* Readable measure for standalone prose only (a lead paragraph directly in the workspace),
   never for grids, tables, cards or rows. */
.cmd-obj-body > p,
.cmd-obj-body > .form-hint,
.cmd-obj-body > .cmd-section > p:first-child { max-width: 88ch; }

.cmd-kpi-row { display: grid; grid-template-columns: repeat(auto-fit, minmax(150px, 1fr)); gap: var(--sp-3); margin-bottom: var(--sp-4); }
.cmd-kpi {
  background: var(--bg-raised);
  border: 1px solid transparent;
  border-radius: var(--r-card);
  padding: var(--sp-3) var(--sp-4);
  position: relative;
  overflow: hidden;
  box-shadow:
    var(--shadow-sm),
    inset 0 1px 0 rgba(255, 255, 255, 0.07),
    0 0 0 1px color-mix(in srgb, var(--border) 70%, transparent);
  transition: border-color var(--dur-2) var(--ease-standard), transform var(--dur-2) var(--ease-standard);
}
/* A subtle accent hairline down the left edge lifts the KPI row out of "flat". */
.cmd-kpi::before {
  content: "";
  position: absolute;
  left: 0;
  top: 0;
  bottom: 0;
  width: 2px;
  background: var(--keyline);
  opacity: 1;
}
.cmd-kpi:hover { border-color: var(--border-strong); transform: translateY(-1px); }
.cmd-kpi-label { font-size: var(--fs-50); letter-spacing: 0.05em; color: var(--text-muted); }
.cmd-kpi-value { font-size: var(--fs-700); font-weight: 700; line-height: 1.05; letter-spacing: var(--ls-tight); margin-top: var(--sp-1); }
.cmd-kpi-sub { font-size: var(--fs-75); color: var(--text-dim); margin-top: var(--sp-1); }
/* A KPI figure placed INSIDE a card must not duplicate the card's chrome (border +
   shadow + keyline) one level down — that box-in-box is a core "assembled from
   snippets" tell. Nested, it sheds its tile frame and sits directly on the card. */
.cmd-card-body .cmd-kpi {
  background: transparent;
  box-shadow: none;
  border-color: transparent;
  padding: 0;
}
.cmd-card-body .cmd-kpi::before { display: none; }
.card { transition: border-color var(--dur-2) var(--ease-standard); }
.cmd-tree-row { transition: background var(--dur-1) var(--ease-standard); }
/* The selected row's OS/type icon takes the accent, not the dim default. */
.cmd-tree-row.active .cmd-tree-ic { color: var(--accent-hover); }
.cmd-cap { padding: var(--sp-3) var(--sp-4); margin-bottom: var(--sp-4); }
.cmd-cap-row { display: flex; justify-content: space-between; font-size: var(--fs-75); margin: var(--sp-2) 0 var(--sp-1); }
.cmd-section { margin-bottom: var(--sp-5); }

/* Live console preview tile on the VM Summary (click to open the full console).

   Two things here were making a wide card look broken. The frame was capped at
   max-width:560px inside a span-8 card, so roughly the right 40% of the card was
   simply empty; and the frame was painted --bg-inset, which is a LIGHT grey in
   light theme, so the letterbox bars a 4:3 guest leaves inside a 16:9 frame read
   as two bright gaps flanking the screen rather than as the edge of a monitor.

   The frame now takes the card's width, and it is dark in both themes — a
   console is a dark object regardless of the surrounding UI, and against a dark
   frame the guest's own letterboxing disappears into the bezel. */
/* ============================================================
   VM detail Overview — redesigned gold-standard layout: a compact
   operational health band + a deliberate asymmetric (main | side)
   grid, replacing the 9-card auto-flow "card soup".
   ============================================================ */
/* Health band: a fixed-height row of status cells. The async reads swap a
   value in place, never resize the band, so it never jumps on refresh. Dense,
   status-first, and it puts the operational verdict in the first viewport. */
.cmd-health-band {
  display: grid;
  /* Auto-columns so the band adapts to its cell count (VM: 4, host: 5) without a
     per-count class. */
  grid-auto-flow: column;
  grid-auto-columns: 1fr;
  gap: 1px;
  /* The divider colour the cells sit over — raised to --border so the 1px gaps
     actually read (was --border-subtle at ~1.1:1, effectively invisible). */
  background: var(--border);
  border-radius: var(--r-card);
  overflow: hidden;
  box-shadow: 0 0 0 1px color-mix(in srgb, var(--border) 70%, transparent);
  margin-bottom: var(--sp-4);
}
/* Dark: lift the whole band as one machined instrument plate (top-highlight +
   contact shadow), keeping the internal hairline cell dividers. */
:root:not([data-theme="light"]) .cmd-health-band {
  box-shadow: var(--shadow-rest), 0 0 0 1px var(--border-panel);
}
.cmd-hb-cell { background: var(--bg-panel); padding: 16px 18px; min-height: 72px; }
:root[data-theme="light"] .cmd-hb-cell { background: var(--bg-raised); }
.cmd-hb-k { font-size: var(--fs-50); font-weight: 600; letter-spacing: var(--ls-caps); color: var(--text-muted); margin-bottom: var(--sp-2); }
/* The first numbers a user sees after login — set as display numerals
   (Inter, semibold, tabular, tight) so the hero/label contrast reads premium. */
.cmd-hb-v { display: inline-flex; align-items: center; gap: var(--sp-2); font-family: var(--font-display); font-size: var(--fs-400); font-weight: 600; letter-spacing: var(--ls-tight); font-variant-numeric: tabular-nums; color: var(--text); min-height: 24px; }
.cmd-hb-v.cmd-hb-dim { color: var(--text-muted); font-weight: 500; }

/* The unified fleet-capacity instrument: CPU / Memory / Storage side by side,
   separated by hairline dividers so the card reads as one panel, not three. */
.cmd-capstrip { display: grid; grid-template-columns: repeat(3, 1fr); }
.cmd-capcol { min-width: 0; padding: 0 var(--sp-5); }
.cmd-capcol:first-child { padding-left: 0; }
.cmd-capcol:last-child { padding-right: 0; }
.cmd-capcol:not(:first-child) { border-left: 1px solid var(--border-subtle); }
.cmd-capcol-label { font-size: var(--fs-50); letter-spacing: var(--ls-caps); color: var(--text-muted); font-weight: 600; }
.cmd-capcol .cmd-stat-big { margin: var(--sp-1) 0 var(--sp-3); }
.cmd-capcol .meter { margin: 0; }
.cmd-capcol-sub { font-size: var(--fs-50); color: var(--text-dim); margin-top: var(--sp-2); line-height: var(--lh-snug); }
@media (max-width: 820px) {
  .cmd-capstrip { grid-template-columns: 1fr; gap: var(--sp-4); }
  .cmd-capcol { padding: var(--sp-3) 0 0; }
  .cmd-capcol:not(:first-child) { border-left: 0; border-top: 1px solid var(--border-subtle); }
}
/* Asymmetric object-detail overview (shared by VM + host): a wide main column and
   a dense summary column. Deliberate columns — NOT auto-flow-dense — so a card
   that grows after an async read never re-packs its siblings (a poll-jump source). */
.cmd-obj-overview { display: grid; grid-template-columns: minmax(0, 1.85fr) minmax(300px, 1fr); gap: var(--sp-4); align-items: start; }
.cmd-obj-main, .cmd-obj-side { display: flex; flex-direction: column; gap: var(--sp-4); min-width: 0; }
/* In the deliberate layout the drag grip is meaningless (nothing to reorder) and
   the grid-span classes are inert outside .cmd-grid; hide the grip, let each card
   fill its column. */
.cmd-obj-overview .cmd-card-grip { display: none; }
.cmd-obj-overview .cmd-card { width: 100%; }
/* The SUMMARY RAIL is a single property PANEL, not a stack of floating cards:
   the side column becomes one bordered surface, and its cards flatten into flush,
   divider-separated property GROUPS (a classic property panel). The main
   column keeps its floating panels — that asymmetry is the point. Pure CSS, so
   the card DOM (and every [data-card-id] fill target) is unchanged. */
.cmd-obj-side {
  gap: 0;
  background: var(--bg-panel);
  border: 1px solid var(--border);
  border-radius: var(--r-md);
  overflow: hidden;
}
:root[data-theme="light"] .cmd-obj-side { background: var(--bg-raised); box-shadow: var(--shadow-sm), 0 0 0 1px var(--border); }
.cmd-obj-side > .cmd-card {
  background: transparent;
  border: 0;
  border-bottom: 1px solid var(--border-subtle);
  border-radius: 0;
  box-shadow: none;
  padding: var(--sp-3) var(--sp-4) var(--sp-4);
}
.cmd-obj-side > .cmd-card:last-child { border-bottom: 0; }
.cmd-obj-side > .cmd-card:hover { border-color: var(--border-subtle); box-shadow: none; }

/* Capacity ring gauges — the signature "how full" read for host/cluster/fleet. */
.cmd-cap { padding: var(--sp-4) var(--sp-6); }
/* Capacity utilization bars — restrained operational readouts that replace the
   donut gauges. Neutral track, solid fill, no glow; the number carries the read. */
.cmd-capbars { display: flex; flex-direction: column; gap: var(--sp-4); }
.cmd-capbar-top { display: flex; align-items: baseline; justify-content: space-between; gap: var(--sp-3); }
.cmd-capbar-label { font-size: var(--fs-75); color: var(--text-dim); font-weight: 500; }
.cmd-capbar-pct { font-size: var(--fs-100); color: var(--text); font-weight: 600; font-variant-numeric: tabular-nums; }
.cmd-capbar-sub { font-size: var(--fs-50); color: var(--text-muted); margin: 2px 0 var(--sp-2); font-variant-numeric: tabular-nums; }
.cmd-capbar-track { height: 6px; border-radius: 3px; background: var(--track); overflow: hidden; }
/* Utilization is neutral steel; colour appears only when a threshold is crossed
   (warn/crit). Blue stays reserved for interaction, green for health — one
   capacity colour rule across cluster, host, datacenter, HCI and datastores. */
.cmd-capbar-fill { height: 100%; border-radius: 3px; background: var(--grey); transition: width var(--dur-3) var(--ease-standard), background var(--dur-3) var(--ease-standard); }
.cmd-capbar-fill.cmd-capbar-warn { background: var(--warn); }
.cmd-capbar-fill.cmd-capbar-crit { background: var(--err); }
/* Live VM Usage card: value-forward headline (absolute figure) + compact empty state. */
/* Failover log (cluster "Failover activity" card). */
.cmd-ha-log { display: flex; flex-direction: column; gap: var(--sp-3); }
.cmd-ha-ev { padding-bottom: var(--sp-2); border-bottom: 1px solid var(--border-subtle); }
.cmd-ha-ev:last-child { border-bottom: 0; padding-bottom: 0; }
.cmd-ha-ev-head { display: flex; align-items: center; gap: var(--sp-2); }
.cmd-ha-ev-vm { font-size: var(--fs-75); color: var(--text); font-weight: 600; }
.cmd-usage-empty { padding: var(--sp-2) 0 var(--sp-1); }
.cmd-usage-empty-title { font-size: var(--fs-75); color: var(--text-dim); font-weight: 600; margin-bottom: 2px; }
.cmd-cap-rings { display: flex; gap: var(--sp-8); flex-wrap: wrap; align-items: flex-start; }
.cmd-capring-wrap { display: flex; flex-direction: column; align-items: center; gap: 9px; }
.cmd-capring { width: 84px; height: 84px; border-radius: 50%; display: grid; place-items: center; }
.cmd-capring-hole { width: 62px; height: 62px; border-radius: 50%; background: var(--bg-raised); display: grid; place-items: center; box-shadow: inset 0 1px 0 rgba(255, 255, 255, 0.05); }
:root[data-theme="light"] .cmd-capring-hole { background: var(--bg-raised); box-shadow: none; }
.cmd-capring-pct { font-family: var(--font-display); font-size: var(--fs-500); font-weight: 600; color: var(--text); letter-spacing: -0.01em; }
.cmd-capring-pct small { font-size: var(--fs-25); color: var(--text-muted); font-weight: 500; margin-left: 1px; }
.cmd-capring-na { color: var(--text-muted); font-size: var(--fs-400); }
.cmd-capring-label { font-size: var(--fs-50); font-weight: 600; color: var(--text-dim); }
.cmd-capring-sub { font-size: var(--fs-25); color: var(--text-muted); font-variant-numeric: tabular-nums; }
@media (max-width: 1080px) {
  .cmd-obj-overview { grid-template-columns: 1fr; }
  /* auto-fit so an odd cell count (a 5-cell cluster band) packs without leaving a
     blank cell beside the last one. */
  .cmd-health-band { grid-auto-flow: row; grid-template-columns: repeat(auto-fit, minmax(150px, 1fr)); grid-auto-columns: auto; }
}

.cmd-console-preview { margin-bottom: 18px; }
/* Inline TEXT (serial) console embedded in the VM Console tab — a real terminal
   right in the page, FortiGate-style. Tall enough to work in, full width. */
.cmd-serial-embed {
  display: flex;
  flex-direction: column;
  gap: var(--sp-2);
}
.cmd-serial-bar {
  display: flex;
  gap: 6px;
  align-items: center;
  justify-content: flex-end;
}
.cmd-serial-wrap {
  width: 100%;
  height: 68vh;
  min-height: 420px;
  border: 1px solid var(--border);
  border-radius: 8px;
  background: #000000;
  padding: 6px 8px;
  box-sizing: border-box;
  overflow: hidden;
}
.cmd-serial-term { width: 100%; height: 100%; }

/* One-liner "enable the text console in the guest" hint on the VM Overview. Terminal-
   styled with the REAL theme tokens so it tracks light/dark: recessed inset surface, an
   accent keyline, an accent $ prompt, and an accent outline Copy button. */
.cmd-oneliner { margin-top: 12px; font-size: var(--fs-50); }
.cmd-oneliner-label { display: block; color: var(--text-dim); margin-bottom: 6px; }
.cmd-oneliner-row {
  display: flex;
  align-items: center;
  gap: 8px;
  background: var(--bg-inset);
  border: 1px solid var(--border);
  border-left: 2px solid var(--accent);
  border-radius: 6px;
  padding: 7px 10px;
  font-family: var(--mono);
}
.cmd-oneliner-prompt { color: var(--accent); font-weight: 600; user-select: none; }
.cmd-oneliner-cmd { flex: 1 1 auto; min-width: 0; color: var(--text); white-space: pre; overflow-x: auto; }
.cmd-oneliner-copy {
  flex: 0 0 auto;
  cursor: pointer;
  background: transparent;
  color: var(--accent);
  border: 1px solid var(--accent);
  border-radius: 4px;
  padding: 2px 10px;
  font-size: var(--fs-25);
  line-height: 1.6;
}
.cmd-oneliner-copy:hover { background: var(--accent-tint); }

.cmd-console-frame {
  position: relative;
  aspect-ratio: 16 / 9;
  width: 100%;
  /* Capped so the preview does not dominate the Overview: on a wide card a
     full-width 16:9 frame was ~1080px and pushed everything below the fold.
     It stays left-aligned at its natural size on narrow cards and stops
     growing past a comfortable reading size on wide ones. The full-screen
     pop-out console (.console-screen, its own document) is uncapped. */
  max-width: 760px;
  background: #0a0c14;
  border: 1px solid var(--border);
  border-radius: var(--r-card);
  overflow: hidden;
  cursor: pointer;
}
/* A powered-off / handed-off console is NOT given a big empty 16:9 region — it
   collapses to a compact poster line with its Open-console affordance. A running
   VM keeps the live 16:9 preview (capped above). */
.cmd-console-frame.cmd-console-off { cursor: default; display: flex; align-items: center; justify-content: center; aspect-ratio: auto; min-height: 96px; }
.cmd-console-frame.cmd-console-clickable { cursor: pointer; }
.cmd-console-frame.cmd-console-clickable:hover { border-color: var(--accent); }
.cmd-console-screen { position: absolute; inset: 0; }
/* The preview is view-only and its whole job is to be CLICKED to open the real
   console. noVNC's canvas otherwise swallows the click — it registers pointer
   handlers and stops propagation even in viewOnly — so only the sliver of frame
   NOT under the canvas responded, the "specific spot that's hard to guess". Make
   the screen (and its canvas) transparent to pointer events so a click ANYWHERE in
   the frame reaches its open-console handler. Hover still works: it applies to the
   frame ancestor, so the accent border + the "Open console" affordance still show. */
.cmd-console-screen, .cmd-console-screen canvas { pointer-events: none; }
.cmd-console-screen canvas { width: 100%; height: 100%; display: block; }
/* Give the pointer back over the PREVIEW.

   noVNC hides the local cursor and draws the guest's own in its place. When the
   guest sends a fully transparent cursor — which Windows does until the pointer
   crosses something that changes it — noVNC's Cursor.clear() runs and the result
   is a pointer that vanishes on entering the tile.

   Two reasons that is wrong here and not in the full console window: this tile is
   viewOnly, so the guest's cursor is not tracking anything the operator can do;
   and the tile's entire purpose is to be CLICKED to open the real console (the
   frame already sets cursor:pointer and a title). Only the preview is overridden;
   #console-screen in the pop-out window keeps noVNC's behaviour, because there
   the guest's cursor is the true one.

   !important is required, not lazy: Cursor.clear() writes `cursor: none` as an
   INLINE style on the canvas, which no ordinary rule can outrank. */
.cmd-console-screen canvas { cursor: pointer !important; }
/* The overlay and the not-connected message sit ON the dark frame, so they take
   their own light-on-dark colours rather than the theme's body text — which in
   light theme would be near-black on near-black. */
.cmd-console-overlay {
  position: absolute;
  inset: 0;
  display: flex;
  align-items: center;
  justify-content: center;
  color: rgba(255, 255, 255, 0.72);
  font-size: var(--fs-100);
  background: #0a0c14;
  pointer-events: none;
  text-align: center;
  padding: 0 var(--sp-3);
}
.cmd-console-overlay.hidden { display: none; }
.cmd-console-status { color: rgba(255, 255, 255, 0.72); font-size: var(--fs-100); text-align: center; padding: 0 var(--sp-3); }
.cmd-console-open {
  position: absolute;
  top: var(--sp-2);
  right: var(--sp-2);
  font-size: var(--fs-50);
  color: #fff;
  background: rgba(0, 0, 0, 0.62);
  border: 1px solid rgba(255, 255, 255, 0.22);
  padding: var(--sp-px) var(--sp-2);
  border-radius: 6px;
  /* Always visible so it reads as a real "click to open" affordance, not a
     hidden-until-hover secret; the whole frame is clickable, this just names it. */
  opacity: 0.9;
  transition: opacity var(--dur-2), background var(--dur-2);
  pointer-events: none;
}
.cmd-console-frame:hover .cmd-console-open { opacity: 1; background: var(--accent); }
.cmd-console-frame:hover { border-color: var(--accent); }
.cmd-clickable { cursor: pointer; }
.cmd-clickable:hover { background: var(--row-hover); }
.cmd-clickable:focus-visible { background: var(--row-hover); outline: 2px solid var(--focus-ring); outline-offset: -2px; }
/* The row's keyboard stop is the control in its first cell (parts.js rowLink), not
   the row: highlighting the whole row when that control has focus keeps the "you
   are on this row" feedback the old role="link" row gave, without the row having
   to pretend to be a link. */
.cmd-clickable:focus-within { background: var(--row-hover); }
/* rowLink reads as the row's text, not as a blue link in every first cell — a
   table of 200 underlined names is noise. It becomes a link on hover/focus. */
.cmd-rowlink {
  appearance: none;
  background: none;
  border: 0;
  padding: 0;
  margin: 0;
  font: inherit;
  color: inherit;
  text-align: left;
  cursor: pointer;
}
.cmd-rowlink:hover { color: var(--accent); }
.cmd-rowlink:focus-visible { outline: 2px solid var(--focus-ring); outline-offset: 2px; border-radius: var(--r-sm); }
.cmd-banner { padding: var(--sp-2) var(--sp-3); border-radius: var(--r-card); margin-bottom: 14px; font-size: var(--fs-100); }

/* The post-deploy "new version available" banner — tokenized + theme-aware (was built
   from a raw #2563eb/#fff + system-ui inline style that ignored the theme). */
.cmd-update-banner {
  position: fixed; inset: 0 0 auto 0; z-index: 99999;
  display: flex; gap: var(--sp-3); align-items: center; justify-content: center;
  padding: var(--sp-2) var(--sp-4);
  background: var(--accent); color: var(--accent-fg);
  font-family: var(--font); font-size: var(--fs-75); font-weight: 500;
  box-shadow: var(--shadow-lg);
}
.cmd-update-banner .cmd-update-reload {
  cursor: pointer; border: 1px solid color-mix(in srgb, var(--accent-fg) 80%, transparent);
  background: var(--accent-fg); color: var(--accent);
  border-radius: var(--r-control); padding: 3px var(--sp-3);
  font: 600 var(--fs-50) var(--font);
}
.cmd-update-banner .cmd-update-x {
  cursor: pointer; border: 0; background: transparent; color: inherit;
  font-size: var(--fs-400); line-height: 1; padding: 0 var(--sp-1);
}

/* Inventory filter "no matches" row — shown instead of a silently-blank tree. */
.cmd-tree-nomatch {
  display: flex; align-items: center; justify-content: space-between; gap: var(--sp-2);
  padding: var(--sp-2) var(--sp-3); margin: var(--sp-1) var(--sp-2);
  font-size: var(--fs-50); color: var(--text-muted);
}
.cmd-tree-nomatch-clear {
  cursor: pointer; border: 1px solid var(--border-strong); background: var(--bg-inset);
  color: var(--text); border-radius: var(--r-control); padding: 2px var(--sp-2); font-size: var(--fs-50);
}
.cmd-tree-nomatch-clear:hover { border-color: var(--border-focus); }
.cmd-banner-warn { background: var(--warn-tint); color: var(--warn); }
.cmd-banner-err { background: var(--err-tint); color: var(--err); }

/* Animated "connecting" banner: spinner + sliding indeterminate bar + phase. */
.cmd-connecting { display: flex; flex-direction: column; gap: var(--sp-2); }
.cmd-connecting-row { display: flex; align-items: center; gap: var(--sp-2); }
.cmd-connecting-msg { font-weight: 600; }
.cmd-connecting-elapsed { margin-left: auto; font-size: var(--fs-75); opacity: 0.85; font-variant-numeric: tabular-nums; }
.cmd-connecting-phase { font-size: var(--fs-75); opacity: 0.85; }
.meter-indeterminate { position: relative; overflow: hidden; }
.meter-indeterminate::after {
  content: "";
  position: absolute;
  top: 0;
  height: 100%;
  width: 38%;
  border-radius: inherit;
  background: var(--warn);
  animation: meter-slide 1.25s ease-in-out infinite;
}
@keyframes meter-slide {
  0% { left: -40%; }
  100% { left: 100%; }
}

/* ---------- Recent Tasks dock ------------------------------------------- *
 * The dock height is driven by --dock-h (set live by the drag handle in app.js)
 * and clamped to a sane range; collapsing drops it to just the bar.
 *
 * COLLAPSED it is a STATUS LINE, not a hidden panel: the title, count chips
 * (running / failed, or a quiet "all completed"), and a live one-line summary of
 * the most relevant task — so giving the page its space back never costs an
 * operator a failed migration, and the bar never looks empty. EXPANDED, the same
 * bar carries the filter and "Clear completed", and the table opens below it.
 * The dock follows the page theme; only the top bar is fixed-dark. */
.cmd-tasks {
  grid-area: tasks;
  background: var(--bg-rail);
  border-top: var(--sp-px) solid var(--border);
  height: var(--dock-h, 240px);
  min-height: 34px;
  max-height: 80vh;
  display: flex;
  flex-direction: column;
  position: relative;
}
.cmd-tasks.collapsed { height: auto; }
.cmd-tasks.collapsed .cmd-tasks-body { display: none; }
/* Author `display` rules on the dock's own parts must never defeat [hidden]. */
.cmd-tasks [hidden] { display: none !important; }

/* A tile FOOTNOTE: a caveat that must be said but must not outweigh the number
   it qualifies. Body-sized explanatory paragraphs inside a metric tile made one
   card twice the height of its row and buried the value it was explaining. */
.cmd-tile-note {
  margin: 10px 0 0;
  font-size: var(--fs-50);
  line-height: 1.4;
  color: var(--text-muted);
  border-top: var(--sp-px) solid var(--border);
  padding-top: var(--sp-2);
}

/* One keyline along the dock's top edge, and the bar's left edge, when a failure
   has been raised and not yet looked at — the same mark the rest of the product
   uses for "look here", at page scale. */
.cmd-tasks.has-failures { box-shadow: inset 0 2px 0 var(--err); }
.cmd-tasks.has-failures .cmd-tasks-bar { box-shadow: inset 3px 0 0 var(--err); }
/* Drag handle straddling the dock's top edge; grows the dock when dragged up. It
   is also a focusable separator: the arrow keys resize it. */
.cmd-tasks-resize {
  position: absolute;
  top: calc(-1 * var(--sp-px));
  left: 0;
  right: 0;
  height: 7px;
  cursor: ns-resize;
  z-index: 3;
  touch-action: none; /* claim the vertical drag; don't let it scroll the page */
  user-select: none;
}
.cmd-tasks-resize::after {
  content: "";
  position: absolute;
  left: 0; right: 0; top: var(--sp-px);
  height: 1px;
  background: transparent;
  transition: background var(--dur-2);
}
.cmd-tasks-resize:hover::after,
.cmd-tasks-resize.dragging::after { background: var(--accent); }
.cmd-tasks-resize:focus-visible { outline: none; }
.cmd-tasks-resize:focus-visible::after { height: 3px; background: var(--focus-ring); }
.cmd-tasks.collapsed .cmd-tasks-resize { display: none; }

/* The bar: one 34px line, collapsed or not. The toggle is a single button that
   fills it; the toolbar and the caret sit beside it (a button cannot contain
   buttons). */
.cmd-tasks-bar { display: flex; align-items: stretch; flex: none; height: 34px; min-width: 0; }
.cmd-tasks-head {
  flex: 1 1 auto;
  min-width: 0;
  display: flex;
  align-items: center;
  gap: 12px;
  padding: 0 var(--sp-3) 0 var(--sp-4);
  background: none;
  border: 0;
  color: var(--text);
  font: inherit;
  font-size: var(--fs-75);
  text-align: left;
  cursor: pointer;
}
/* Collapsed, the WHOLE line is the target, so the whole line reacts. */
.cmd-tasks.collapsed .cmd-tasks-bar:hover,
.cmd-tasks:not(.collapsed) .cmd-tasks-head:hover { background: var(--row-hover); }
.cmd-tasks-head:focus-visible,
.cmd-tasks-segbtn:focus-visible,
.cmd-tasks-link:focus-visible,
.cmd-te-btn:focus-visible,
.cmd-task-row:focus-visible { outline: 2px solid var(--focus-ring); outline-offset: -2px; }
.cmd-tasks-title { flex: none; font-weight: 600; }

/* Count chips. Colour is state: running is the interaction blue, failed is the
   error red, and idle is plain muted text — a calm bar says so quietly. */
.cmd-tasks-chips { display: inline-flex; align-items: center; gap: 6px; flex: none; }
.cmd-tasks-chip {
  display: inline-flex;
  align-items: center;
  gap: 6px;
  height: 20px;
  padding: 0 8px;
  border: 1px solid transparent;
  border-radius: var(--r-status);
  font-size: var(--fs-50);
  font-weight: 600;
  font-variant-numeric: tabular-nums;
  white-space: nowrap;
}
.cmd-tasks-chip.is-running { color: var(--accent); background: var(--accent-tint); border-color: color-mix(in srgb, var(--accent) 30%, transparent); }
.cmd-tasks-chip.is-failed { color: var(--err); background: var(--err-tint); border-color: color-mix(in srgb, var(--err) 32%, transparent); }
.cmd-tasks-chip .cmd-spin { width: 10px; height: 10px; border-color: color-mix(in srgb, var(--accent) 28%, transparent); border-top-color: var(--accent); }
.cmd-tasks-idle { color: var(--text-muted); white-space: nowrap; }
.cmd-tasks-idle.is-warn { color: var(--warn); }

/* The live line: what the most relevant task is doing right now. */
.cmd-tasks-live {
  flex: 1 1 auto;
  min-width: 0;
  display: flex;
  align-items: center;
  gap: 8px;
  height: 16px;
  padding-left: 12px;
  border-left: 1px solid var(--border-strong);
  color: var(--text-dim);
}
.cmd-tl-ic { flex: none; display: inline-flex; align-items: center; }
.cmd-tl-ic .cmd-ts-glyph { width: 13px; height: 13px; }
.cmd-tasks-live.is-done .cmd-tl-ic { color: var(--ok); }
.cmd-tasks-live.is-failed .cmd-tl-ic,
.cmd-tasks-live.is-failed .cmd-tl-meta { color: var(--err); }
.cmd-tasks-live.is-queued .cmd-tl-ic { color: var(--text-muted); }
.cmd-tl-name { flex: 0 1 auto; min-width: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; color: var(--text); font-weight: 500; }
.cmd-tl-target { flex: 0 2 auto; min-width: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.cmd-tl-sep { flex: none; color: var(--text-muted); }
.cmd-tl-meta { flex: none; white-space: nowrap; font-variant-numeric: tabular-nums; }
.cmd-tasks-live.is-running .cmd-tl-meta { color: var(--text); font-weight: 500; }
.cmd-tasks-live .cmd-tl-bar { flex: none; width: 96px; height: 4px; margin: 0 0 0 2px; }
/* Expanded, the table says all of this; the bar keeps only the title and tools. */
.cmd-tasks:not(.collapsed) .cmd-tasks-chips,
.cmd-tasks:not(.collapsed) .cmd-tasks-live { display: none; }

.cmd-tasks-caret {
  flex: none;
  width: 40px;
  display: grid;
  place-items: center;
  padding: 0;
  background: none;
  border: 0;
  color: var(--text-muted);
  cursor: pointer;
}
.cmd-tasks-caret:hover { color: var(--text); }
.cmd-tasks-caret svg { transition: transform var(--dur-2) var(--ease-standard); }
.cmd-tasks.collapsed .cmd-tasks-caret svg { transform: rotate(180deg); }

/* The toolbar: only while expanded. */
.cmd-tasks-tools { display: none; flex: none; align-items: center; gap: 8px; padding: 0 var(--sp-1) 0 var(--sp-3); }
.cmd-tasks:not(.collapsed) .cmd-tasks-tools { display: flex; }
.cmd-tasks-seg {
  display: inline-flex;
  height: 24px;
  border: 1px solid var(--border-strong);
  border-radius: var(--r-control);
  overflow: hidden;
  background: var(--bg-inset);
}
.cmd-tasks-segbtn {
  appearance: none;
  display: inline-flex;
  align-items: center;
  gap: 6px;
  padding: 0 10px;
  border: 0;
  border-right: 1px solid var(--border);
  background: transparent;
  color: var(--text-dim);
  font: inherit;
  font-size: var(--fs-50);
  font-weight: 500;
  cursor: pointer;
}
.cmd-tasks-segbtn:last-child { border-right: 0; }
.cmd-tasks-segbtn:hover { background: var(--bg-hover); color: var(--text); }
.cmd-tasks-segbtn[aria-pressed="true"] { background: var(--accent-tint); color: var(--accent-hover); font-weight: 600; }
.cmd-tasks-n { font-variant-numeric: tabular-nums; font-weight: 500; opacity: 0.75; }
/* The Failed count is red only while a failure is unread — a permanent red
   number about old work would be a scar, not a notice. */
.cmd-tasks.has-failures .cmd-tasks-segbtn[data-filter="failed"] .cmd-tasks-n { color: var(--err); font-weight: 700; opacity: 1; }
/* A quiet text button: "Clear completed", "Show N cleared". */
.cmd-tasks-link {
  appearance: none;
  height: 24px;
  padding: 0 8px;
  border: 0;
  border-radius: var(--r-control);
  background: transparent;
  color: var(--text-dim);
  font: inherit;
  font-size: var(--fs-50);
  font-weight: 500;
  white-space: nowrap;
  cursor: pointer;
}
.cmd-tasks-link:hover:not(:disabled) { color: var(--text); background: var(--row-hover); }
.cmd-tasks-link:disabled { color: var(--text-disabled); cursor: default; }

/* The dock is a fixed-height scroll region whose height is never a whole number
   of rows, so the row at the fold is always sliced. Three things make that read
   as "there is more" instead of "the rendering is broken":
     1. the header stays put, so a half-row still has columns;
     2. scroll-snap lands the gesture on a row boundary (below the header, via
        scroll-padding-top) rather than mid-row;
     3. a fade at the fold says the list continues.
   Scrolling is also confined to the dock — `overscroll-behavior: contain` stops
   a flick at the end of the task list from scrolling the page underneath it. */
.cmd-tasks { --fold: 22px; }
.cmd-tasks-body {
  flex: 1 1 auto;
  min-height: 0;
  overflow-y: auto;
  /* NO top padding. A sticky header sticks to the padding edge, so any padding
     here becomes a gap that rows scroll through in plain sight above it. */
  padding: 0;
  border-top: 1px solid var(--border-subtle);
  overscroll-behavior: contain;
  scroll-snap-type: y proximity;
  scroll-padding-top: 28px; /* the sticky header's height */
  scrollbar-gutter: stable;
}
.cmd-task-row { scroll-snap-align: start; }
/* The fold fade. Purely an affordance: while the list continues below the fold it
   sticks to the bottom edge over the clipped row, so a half-row reads as a
   continuation. It has NO negative margin, on purpose: at the end of the list (or
   when the list is short) it sits in normal flow beneath the last row instead of
   fading it, so the last task is never the dim one. pointer-events:none so it can
   never eat a click on the row beneath it. */
.cmd-tasks-body::after {
  content: "";
  position: sticky;
  bottom: 0;
  left: 0;
  display: block;
  height: var(--fold);
  pointer-events: none;
  background: linear-gradient(to bottom, transparent, var(--bg-rail));
}
/* Nothing to show: ONE quiet sentence, never a blank table. */
.cmd-tasks-empty { display: flex; align-items: center; gap: 10px; padding: 14px var(--sp-4); color: var(--text-muted); font-size: var(--fs-75); }
.cmd-tasks-empty .cmd-tasks-link { color: var(--accent); }
/* Visually hidden, still read: the column header over the chevron gutter and the
   polite live region for count changes. */
.cmd-tasks-sr {
  position: absolute;
  width: 1px;
  height: 1px;
  margin: -1px;
  padding: 0;
  overflow: hidden;
  clip-path: inset(50%);
  white-space: nowrap;
  border: 0;
}
/* Off-screen selection buffer for the clipboard fallback. */
.cmd-tasks-copybuf { position: fixed; left: -9999px; top: 0; opacity: 0; pointer-events: none; }

/* border-collapse: SEPARATE, and this is load-bearing rather than cosmetic.
   Chrome ignores `position: sticky` on a <th> inside a collapsed-border table,
   so the dock's sticky header silently did nothing: the column labels scrolled
   away and the row that had scrolled above them showed in the gap under the
   dock's title bar. Separate borders (with zero spacing, so the layout is
   unchanged) make sticky work. The row rule below is a top border on each cell,
   which does not double under `separate` because only one edge is drawn. */
.cmd-tasks-table { width: 100%; table-layout: fixed; font-size: var(--fs-75); border-collapse: separate; border-spacing: 0; }
/* Fixed layout: the number columns get a fixed width so a value changing width
   ("9s" -> "1m 42s") can never move a column, and Task takes what is left. */
.cmd-tasks-table col.c-exp { width: 32px; }
.cmd-tasks-table col.c-status { width: 124px; }
.cmd-tasks-table col.c-target { width: 17%; }
.cmd-tasks-table col.c-progress { width: 156px; }
.cmd-tasks-table col.c-started { width: 104px; }
.cmd-tasks-table col.c-duration { width: 88px; }
.cmd-tasks-table col.c-init { width: 15%; }
.cmd-tasks-table th {
  position: sticky;
  top: 0;
  z-index: 2;
  height: 28px;
  padding: 0 var(--sp-3);
  background: var(--bg-rail);
  box-shadow: inset 0 -1px 0 var(--border);
  text-align: left;
  font-size: var(--fs-50);
  font-weight: 600;
  letter-spacing: var(--ls-caps);

  color: var(--text-muted);
  white-space: nowrap;
}
.cmd-tasks-table td {
  padding: 5px var(--sp-3);
  border-top: 1px solid var(--border-subtle);
  vertical-align: middle;
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
  color: var(--text-dim);
}
.cmd-tasks-table tbody tr:first-child td { border-top: 0; }
.cmd-tasks-table th.c-exp,
.cmd-tasks-table td.c-exp { padding-right: 0; }
.cmd-tasks-table td.c-started,
.cmd-tasks-table td.c-duration { font-variant-numeric: tabular-nums; }
.cmd-tasks-table td.c-duration.is-live { color: var(--text); }

/* Rows. Only a failed row is interactive (it opens to its message). */
.cmd-task-row td { height: 36px; }
.cmd-task-row.is-failed { cursor: pointer; }
.cmd-task-row.is-failed:hover td,
.cmd-task-row.is-open td { background: var(--row-hover); }
.cmd-task-row.is-open td.c-exp,
.cmd-task-row.is-unread td.c-exp { box-shadow: inset 3px 0 0 var(--err); }
/* A failure raised and not yet looked at: a faint red wash until it is opened. */
.cmd-task-row.is-unread td { background: var(--err-tint); }
.cmd-tt-chev { display: inline-flex; visibility: hidden; color: var(--text-muted); transition: transform var(--dur-2) var(--ease-standard); }
.cmd-tt-chev svg { width: 14px; height: 14px; }
.cmd-task-row.is-failed .cmd-tt-chev { visibility: visible; }
.cmd-task-row.is-open .cmd-tt-chev { transform: rotate(90deg); color: var(--text); }

/* Status: a glyph and a word, coloured by state. */
.cmd-ts { display: inline-flex; align-items: center; gap: 7px; }
.cmd-ts-ic { flex: none; display: inline-flex; align-items: center; justify-content: center; width: 14px; height: 14px; }
.cmd-ts-glyph { width: 14px; height: 14px; }
.cmd-ts-label { font-weight: 500; }
.cmd-task-row.is-running .cmd-ts-label { color: var(--accent); }
.cmd-task-row.is-done .cmd-ts-ic { color: var(--ok); }
.cmd-task-row.is-failed .cmd-ts-ic,
.cmd-task-row.is-failed .cmd-ts-label { color: var(--err); }
.cmd-task-row.is-queued .cmd-ts-ic,
.cmd-task-row.is-queued .cmd-ts-label { color: var(--text-muted); }

/* Task: the verb carries the weight; a muted line beneath says what it is doing
   (running) or why it failed (first line of the message). */
.cmd-tt-name { display: block; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; color: var(--text); font-weight: 500; }
.cmd-tt-sub { display: block; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; margin-top: 1px; font-size: var(--fs-50); color: var(--text-muted); }
.cmd-task-row.is-failed .cmd-tt-sub { color: var(--text-dim); }

.cmd-tt-target { display: flex; align-items: center; gap: 7px; min-width: 0; }
.cmd-tt-kind { flex: none; display: inline-flex; color: var(--text-muted); }
.cmd-tt-kind svg { width: 14px; height: 14px; }
.cmd-tt-target-text { min-width: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }

/* Progress: the bar (determinate, or the indeterminate sweep) and the percent as
   text — digits are tabular so the column does not shimmer as it climbs. */
.cmd-tp { display: flex; align-items: center; gap: 10px; }
.cmd-tasks-table .cmd-progress { flex: 1 1 auto; width: auto; max-width: 84px; height: 4px; margin: 0; }
.cmd-task-row.is-failed .cmd-progress-fill { background: var(--err); }
.cmd-tp-pct { flex: none; min-width: 34px; text-align: right; font-variant-numeric: tabular-nums; color: var(--text-dim); }
.cmd-tp-pct.is-none { min-width: 0; text-align: left; color: var(--text-muted); }

/* Initiator: an operator, the controller itself (badged), or unrecorded. */
.cmd-ti-sys { max-width: 100%; }
.cmd-ti-sys::before { flex: none; }
.cmd-ti-sys-text { min-width: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }

/* An open failed row: the controller's message verbatim, monospace, with Copy. */
.cmd-tasks-table td.cmd-te-cell {
  padding: 0 var(--sp-4) 10px 46px;
  border-top: 0;
  background: var(--row-hover);
  box-shadow: inset 3px 0 0 var(--err);
  white-space: normal;
  overflow: visible;
}
.cmd-te { display: flex; flex-direction: column; gap: 8px; max-width: 980px; }
.cmd-te-msg {
  margin: 0;
  padding: 8px 10px;
  max-height: 136px;
  overflow: auto;
  background: var(--bg-inset);
  border: 1px solid var(--border);
  border-radius: var(--r-control);
  font-family: var(--mono);
  font-size: var(--fs-50);
  line-height: 1.5;
  color: var(--text);
  white-space: pre-wrap;
  overflow-wrap: anywhere;
  user-select: text;
  cursor: text;
}
.cmd-te-msg.is-none { font-family: var(--font); font-style: italic; color: var(--text-muted); }
.cmd-te-actions { display: flex; align-items: center; gap: 8px; }
.cmd-te-btn {
  appearance: none;
  height: 24px;
  padding: 0 10px;
  border: 1px solid var(--border-strong);
  border-radius: var(--r-control);
  background: var(--bg-hover);
  color: var(--text);
  font: inherit;
  font-size: var(--fs-50);
  font-weight: 500;
  cursor: pointer;
}
.cmd-te-btn:hover:not(:disabled) { border-color: var(--border-focus); }
.cmd-te-btn:disabled { color: var(--text-disabled); cursor: default; }
.cmd-te-meta { margin-left: 4px; color: var(--text-muted); font-size: var(--fs-50); font-variant-numeric: tabular-nums; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }

/* Narrower windows drop the Duration column first (Started still says when), then
   the bar's width, before anything is allowed to truncate the Task name to nothing. */
@media (max-width: 1000px) {
  .cmd-tasks-table col.c-duration,
  .cmd-tasks-table th.c-duration,
  .cmd-tasks-table td.c-duration { display: none; }
  .cmd-tasks-table th,
  .cmd-tasks-table td { padding-left: var(--sp-2); padding-right: var(--sp-2); }
  .cmd-tasks-table th.c-exp,
  .cmd-tasks-table td.c-exp { padding-left: var(--sp-2); padding-right: 0; }
  .cmd-tasks-table td.cmd-te-cell { padding: 0 var(--sp-2) 10px 34px; }
  .cmd-tasks-table col.c-exp { width: 26px; }
  .cmd-tasks-table col.c-status { width: 108px; }
  .cmd-tasks-table col.c-target { width: 15%; }
  .cmd-tasks-table col.c-progress { width: 120px; }
  .cmd-tasks-table col.c-started { width: 88px; }
  .cmd-tasks-table col.c-init { width: 14%; }
  .cmd-tp { gap: 6px; }
  .cmd-tp-pct { min-width: 30px; }
}
@media (max-width: 720px) {
  .cmd-tasks-live .cmd-tl-bar,
  .cmd-tasks-live .cmd-tl-target,
  .cmd-tasks-live .cmd-tl-name + .cmd-tl-sep { display: none; }
  .cmd-tasks-table col.c-target,
  .cmd-tasks-table th.c-target,
  .cmd-tasks-table td.c-target { display: none; }
}
.cmd-task-target, .cmd-task-time { color: var(--text-dim); }
.cmd-task-status { display: inline-flex; align-items: center; gap: 6px; }
.cmd-spin { width: 11px; height: 11px; border: 2px solid var(--border-strong); border-top-color: var(--accent); border-radius: 50%; display: inline-block; animation: cmd-spin 0.7s linear infinite; }
/* cmd-spin keyframes defined once near the top of the file (deduplicated). */

/* Real progress bar for a running task in the dock: a determinate fill bound to
   the reported percent (live migration, cold block-copy), or an indeterminate
   sweep when the operation reports no percent — so "running…" always looks like
   it is doing something rather than a spinner that could equally mean hung. */
.cmd-progress {
  position: relative;
  width: 96px;
  height: 5px;
  margin-left: var(--sp-1);
  border-radius: 9999px;
  background: var(--border-strong);
  overflow: hidden;
  flex: none;
}
.cmd-progress-fill {
  position: absolute;
  inset: 0 auto 0 0;
  width: 0;
  border-radius: inherit;
  background: var(--accent);
  transition: width 0.35s ease;
}
.cmd-progress.is-indeterminate .cmd-progress-fill {
  width: 40%;
  animation: cmd-prog-sweep 1.15s ease-in-out infinite;
}
@keyframes cmd-prog-sweep {
  0% { left: -40%; }
  100% { left: 100%; }
}
@media (prefers-reduced-motion: reduce) {
  .cmd-progress.is-indeterminate .cmd-progress-fill { animation: none; width: 100%; opacity: 0.5; }
}

/* A VM the task stream reports as relocating pulses in the tree until the move
   completes and the node re-homes under its new host. */
.cmd-tree-row.is-relocating .cmd-tree-label {
  color: var(--accent);
  animation: cmd-reloc-pulse 1.3s ease-in-out infinite;
}
.cmd-tree-row.is-relocating .cmd-tree-label::after {
  content: "→ migrating";
  margin-left: var(--sp-2);
  font-size: var(--fs-50);
  letter-spacing: 0.02em;
  color: var(--accent);
  opacity: 0.85;
}
@keyframes cmd-reloc-pulse { 0%, 100% { opacity: 1; } 50% { opacity: 0.55; } }
@media (prefers-reduced-motion: reduce) {
  .cmd-tree-row.is-relocating .cmd-tree-label { animation: none; }
}

/* Lifecycle & Updates.
   .cmd-lifecycle-upload and .cmd-staged-banner are gone: the first was spacing
   the band now owns, and the second was a bespoke bordered strip that duplicated
   — badly, and in a fifth radius — the .cmd-banner status vocabulary every other
   screen speaks. Staged state is a banner like any other state. */
.cmd-lifecycle-id { padding: var(--sp-3) var(--sp-4); margin-bottom: var(--sp-4); }

/* A centered content measure shared by every Administration section, so the
   controller's settings fill a normal monitor and sit centered on an ultra-wide
   one — never a narrow strip stranded in the left gutter. The section header and
   its body both live inside this wrapper, so they always share the same edges. */
.cmd-admin-page { max-width: 1320px; margin-inline: auto; }
/* Administration section header: the title with a one-line context beneath, so
   each settings section reads as a guided console, not a bare table-wall. */
.cmd-admin-head { align-items: flex-start; }
.cmd-admin-head-text { min-width: 0; }
.cmd-admin-head-sub { margin: 3px 0 0; font-size: var(--fs-100); color: var(--text-dim); line-height: var(--lh-snug); }

/* Lifecycle & Updates: a composed column that now fills the shared page measure.
   The posture card and the fleet table want the width; .cmd-admin-page centers
   the whole thing, so the old half-empty right gutter is gone. */
.cmd-lc-page { width: 100%; }

/* The at-a-glance posture card (lifecycleSummary). Reads left-to-right: what the
   answer is, why, and the version it's measured against. Coloured by state. */
.cmd-lc-summary {
  display: flex;
  align-items: center;
  gap: var(--sp-4);
  padding: var(--sp-4) var(--sp-5);
  margin-bottom: var(--sp-5);
  border-left: var(--sp-px) solid var(--border-strong);
}
.cmd-lc-summary-icon {
  flex: 0 0 auto;
  width: 42px;
  height: 42px;
  display: flex;
  align-items: center;
  justify-content: center;
  border-radius: var(--r-md);
  background: var(--bg-inset);
  color: var(--text-dim);
}
.cmd-lc-summary-icon svg { width: 22px; height: 22px; }
.cmd-lc-summary-text { flex: 1 1 auto; min-width: 0; }
.cmd-lc-summary-head { font-size: var(--fs-200); font-weight: 600; color: var(--text); }
.cmd-lc-summary-sub { font-size: var(--fs-75); color: var(--text-dim); margin-top: var(--sp-px); }
.cmd-lc-summary-ver { flex: 0 0 auto; text-align: right; }
.cmd-lc-ver-label { font-size: var(--fs-50); color: var(--text-muted); letter-spacing: 0.05em; }
.cmd-lc-ver-value { font-family: var(--mono); font-size: var(--fs-200); color: var(--text); font-variant-numeric: tabular-nums; }
/* state tints */
.cmd-lc-ok { border-left-color: var(--ok); }
.cmd-lc-ok .cmd-lc-summary-icon { color: var(--ok); background: var(--ok-tint); }
.cmd-lc-warn { border-left-color: var(--warn); }
.cmd-lc-warn .cmd-lc-summary-icon { color: var(--warn); background: var(--warn-tint); }
/* version + status badge inline on the controller card */
.cmd-lc-verrow { display: inline-flex; align-items: center; gap: var(--sp-2); }
@media (max-width: 620px) {
  .cmd-lc-summary { flex-wrap: wrap; }
  .cmd-lc-summary-ver { text-align: left; }
}

/* Directory (LDAP/AD) status view — the read-only summary that replaced the flat
   dual-mode form; editing happens in the setup wizard. */
.cmd-dir-empty {
  border: 1px dashed var(--border-strong);
  border-radius: var(--r-card);
  background: var(--bg-inset);
  padding: 26px 22px;
  text-align: center;
  margin-top: 6px;
}
.cmd-dir-empty h3 { margin: 0 0 6px; font-size: var(--fs-200); }
.cmd-dir-empty .form-hint { max-width: 52ch; margin: 0 auto var(--sp-4); }
.cmd-dir-card { padding: 14px var(--sp-4); margin: 6px 0 14px; }
.cmd-test-ok { color: var(--ok); }

/* Certificates: PEM paste/display areas, the fingerprint, and an expiry warning.*/
.cmd-pem {
  width: 100%;
  box-sizing: border-box;
  font-family: var(--mono);
  font-size: var(--fs-50);
  line-height: 1.45;
  resize: vertical;
  white-space: pre;
  overflow-wrap: normal;
  background: var(--bg-inset);
  color: var(--text);
  border: 1px solid var(--border);
  border-radius: var(--r-control);
  padding: var(--sp-2) 10px;
}
.cmd-pem:focus { outline: 2px solid var(--focus-ring); outline-offset: -1px; }
.cmd-fp { word-break: break-all; font-size: var(--fs-50); }
.cmd-expire-soon { color: var(--warn); font-weight: 600; }

/* Datacenter → Health: the fleet health readout. The verdict banner + KPI row +
   a list of named checks give the all-green case something substantial to show
   ("here is what I verified"), instead of a lone "everything is online" line. */
.cmd-health-verdict { margin-bottom: var(--sp-4); }
.cmd-health-checks {
  display: flex;
  flex-direction: column;
  border: 1px solid var(--border);
  border-radius: var(--r-card);
  overflow: hidden;
  margin-top: var(--sp-1);
}
.cmd-health-check {
  display: flex;
  align-items: flex-start;
  gap: var(--sp-3);
  padding: var(--sp-3) 14px;
}
.cmd-health-check + .cmd-health-check { border-top: var(--sp-px) solid var(--border); }
.cmd-health-check .dot { margin-top: var(--sp-1); flex: none; }
.cmd-health-check-body { display: flex; flex-direction: column; gap: var(--sp-px); min-width: 0; }
.cmd-health-check-label { font-weight: 600; font-size: var(--fs-100); }
.cmd-health-check-detail { font-size: var(--fs-75); color: var(--text-dim); }
.cmd-net-table .cmd-progress { width: 120px; }
.badge-accent { background: var(--accent-tint); color: var(--accent); }

/* The upload target. SimpleVirt takes exactly one upgrade artifact — the installer
   ISO — so this is a single, obvious, full-width place to put it, not a field
   in a form. The browser's native file widget was the loudest unfinished thing
   on this screen and the only one left in the product; every other upload
   already drives a hidden input from a styled control. */
.cmd-drop {
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: var(--sp-1);
  padding: 22px 18px;
  border: 1px dashed var(--border-strong);
  border-radius: var(--r-card);
  background: var(--bg-inset);
  cursor: pointer;
  text-align: center;
  transition: border-color var(--dur-2) var(--ease-standard), background var(--dur-2) var(--ease-standard);
}
.cmd-drop:hover,
.cmd-drop:focus-visible { border-color: var(--accent); background: var(--accent-tint); }
/* Dragging a file over it is the one moment the target must be unmistakable. */
.cmd-drop.is-over { border-color: var(--accent); border-style: solid; background: var(--accent-tint); }
.cmd-drop.is-busy { cursor: progress; border-style: solid; }
.cmd-drop.is-bad { border-color: var(--err); }
.cmd-drop.is-bad .cmd-drop-sub { color: var(--err); }
.cmd-drop-main { font-size: var(--fs-100); font-weight: 600; color: var(--text); }
.cmd-drop-sub { font-size: var(--fs-50); color: var(--text-dim); max-width: 62ch; }
.cmd-drop .cmd-progress { width: min(420px, 100%); margin-top: 6px; }
/* Cancel sits inside a busy zone, which is then a status plate rather than a
   button (role=button is only set while idle), so nesting a real button is fine. */
.cmd-drop-actions { margin-top: var(--sp-2); }
/* The one-line truth under each zone: what needs the tab and what does not. */
.cmd-lc-upnote { margin: 6px 0 var(--sp-4); }

/* A host's staging job in its Staged cell: phase badge, bar, one line of detail. */
.cmd-lc-job { display: flex; flex-direction: column; align-items: flex-start; gap: var(--sp-1); }
.cmd-lc-job .cmd-progress { margin-left: 0; }
.cmd-lc-stagefail { margin-top: var(--sp-1); }
.cmd-lc-stagefail .cmd-upload-err { margin-top: var(--sp-1); }
.cmd-lc-stagefail-acts { margin-top: 6px; }

/* "Stage on hosts…" checklist rows: name, then why it is (or is not) ticked. */
.cmd-lc-check {
  flex-wrap: wrap;
  margin-bottom: 0;
  padding: var(--sp-1) 6px;
  border-radius: 5px;
}
.cmd-lc-check:hover { background: var(--row-hover); }
.cmd-lc-check .cell-sub { flex-basis: 100%; margin: 0 0 0 23px; }
.cmd-lc-check.is-disabled { opacity: 0.62; cursor: default; }
.cmd-lc-check.is-disabled:hover { background: transparent; }

/* The pre-auth mount point paints the dark auth canvas immediately, so the very
   first frame is the finished environment — no theme-coloured flash before the
   login view mounts, and no flash on a 401 → showLogin() transition. It is pinned
   to the viewport rather than placed in the shell grid: the grid's fixed rail and
   inventory tracks (88px + --rail-w) are wider than a phone, and a grid-placed
   login would inherit that width and run off the right edge. Pinned, the sign-in
   is always exactly the visible screen (the dynamic viewport on mobile too). */
.cmd-login-root { position: fixed; inset: 0; background: #1d2025; }

/* ============================================================
   The lens rail: four icons over ONE inventory.
   Compute → workload → storage → network. This replaces the flat
   area list (Datastores / Networking / Administration) that used
   to sit above the tree — those were pages, not projections, so
   nothing connected to anything.
   ============================================================ */
/* The PRODUCT-NAV rail: a dedicated vertical column of destinations, its own
   surface distinct from the inventory explorer beside it. */
.cmd-lenses {
  grid-area: productnav;
  display: flex;
  flex-direction: column;
  gap: 2px;
  padding: var(--sp-3) 8px;
  background: var(--bg-nav);
  border-right: 1px solid var(--border);
  overflow-y: auto;
  overflow-x: hidden;
}
/* Hairline divider between rail groups (tree lenses vs managed pages vs Settings).
   Inset a little from the rail edges so it reads as a group separator, not a border. */
.cmd-lens-sep {
  height: 1px;
  margin: 6px 8px;
  background: var(--border-subtle);
}
.cmd-lens-btn {
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: 5px;
  padding: 11px 4px;
  border-radius: var(--r-sm);
  border: 1px solid transparent;
  background: none;
  color: var(--text-muted);
  font-size: 10.5px;
  font-weight: 600;
  letter-spacing: 0.01em;
  cursor: pointer;
  position: relative;
  transition: background var(--dur-2) var(--ease-standard), color var(--dur-2) var(--ease-standard);
}
.cmd-lens-ic { width: 20px; height: 20px; }
.cmd-lens-label { line-height: 1; text-align: center; }
.cmd-lens-btn:hover { background: var(--bg-hover); color: var(--text); }
/* Selected: a quiet graphite fill + bright text/icon + the blue leading keyline —
   NOT a blue wash. Same selection language as the inventory tree. */
.cmd-lens-btn.active { background: var(--bg-selected); color: var(--text); border-color: transparent; }
.cmd-lens-btn.active .cmd-lens-ic { color: var(--accent); }
/* The signature keyline — vertical, on the leading edge, the same solid SimpleVirt-blue
   mark the selected inventory row carries. One motif across the whole shell. */
.cmd-lens-btn.active::after {
  content: "";
  position: absolute;
  left: -8px;
  top: 0;
  bottom: 0;
  width: 2px;
  background: var(--accent);
}
.cmd-lens-btn:focus-visible { outline: 2px solid var(--focus-ring); outline-offset: -2px; }
.cmd-inv-heading {
  font-size: var(--fs-50);
  font-weight: 600;

  letter-spacing: 0.05em;
  color: var(--text-muted);
  padding: var(--sp-1) var(--sp-2) 6px;
}
/* Administration is not a lens: while it is open the rail shows nothing
   selected, rather than leaving the last lens looking current. */
.cmd-inventory-idle .cmd-tree { opacity: 0.72; }

/* Administration owns the whole pane and brings its own heading. */
.cmd-area > .view-header { margin-bottom: var(--sp-4); }

/* ---- tree: expand/collapse ----
   The 0fr→1fr grid transition animates the children's real height without any
   measured pixel value. The row is reconciled in place (app.js), so the node
   being animated is the same node — which is the only reason this can run at
   all. prefers-reduced-motion is handled globally and collapses it to a snap. */
.cmd-tree-kids {
  display: grid;
  grid-template-rows: 0fr;
  transition: grid-template-rows var(--dur-2) var(--ease-standard);
}
.cmd-tree-kids.open { grid-template-rows: 1fr; }
.cmd-tree-kids > .cmd-tree-children { overflow: hidden; min-height: 0; }
/* Rows are keyboard-reachable; the ring draws inside so the overflow clip above
   cannot slice it off. */
.cmd-tree-row:focus-visible { outline: 2px solid var(--focus-ring); outline-offset: -2px; }
.cmd-tree-row:hover .cmd-tree-label { color: var(--text); }

/* ---- status glyphs that CHANGE rather than snap ----
   Only possible because the tree patches dots in place instead of rebuilding
   them; a recreated node always starts at its final colour. */
.dot {
  transition: background-color var(--dur-4) var(--ease-standard), border-color var(--dur-4) var(--ease-standard);
}
.badge {
  transition: color 0.3s ease, background-color 0.3s ease, border-color 0.3s ease;
}

/*
 * dot-unknown — "we cannot see this VM's state".
 *
 * A hollow ring, never the filled grey dot: grey asserts "stopped", and the
 * truth for a VM on an unreachable or degraded host is that it is most likely
 * still RUNNING and merely invisible to us. Telling an operator a live VM is
 * stopped is how a VM ends up started twice. This one glyph is the safety model
 * made visible, which is why it is a distinct shape and not a shade.
 */
/* UNKNOWN stays hollow — that rule is the safety model, not a style choice: a
   filled mark asserts a state, and the truth for a VM on an unreachable host is
   that it is probably still RUNNING and merely invisible to us. Telling an
   operator a live VM is stopped is how a VM gets started twice.
   On a 3px bar a 1.5px border would meet in the middle and read as solid, so
   the hollow mark is widened and outlined at 1px instead. */
.dot-unknown {
  width: 5px;
  flex: 0 0 5px;
  background: transparent;
  border: 1px solid var(--warn);
  box-shadow: none;
}
.dot-unknown::after { display: none; } /* the hollow mark must stay empty */
.badge-unknown {
  color: var(--warn);
  background: transparent;
  border-color: var(--warn);
}
.badge-unknown::before {
  background: transparent;
  box-shadow: inset 0 0 0 1px currentColor;
}

/* An unknown VALUE (an unreported datastore id, an unreported pool). Never
   rendered as if it were a value, and never equal to another unknown. */
.cmd-unknown {
  display: inline-flex;
  align-items: center;
  height: 22px;
  padding: 0 var(--sp-2);
  border-radius: var(--r-status);
  font-size: var(--fs-50);
  font-style: italic;
  color: var(--text-muted);
  background: rgba(110, 118, 129, 0.12);
  border: 1px dashed var(--border-strong);
}
/* A figure that predates this poll: dotted underline + a title naming when it
   was actually read. */
.cmd-stale {
  color: var(--text-dim);
  border-bottom: var(--sp-px) dotted var(--text-muted);
  cursor: help;
}

/* ---- status banners (host degraded / unreachable / fan-out results) ---- */
.cmd-banner-ok { background: var(--ok-tint); color: var(--ok); }
.cmd-banner-title { font-weight: 600; margin-bottom: var(--sp-1); }
.cmd-banner-text { margin: 0 0 6px; opacity: 0.95; }
.cmd-banner-text:last-child { margin-bottom: 0; }
.cmd-banner-actions {
  display: flex;
  align-items: center;
  gap: var(--sp-2);
  flex-wrap: wrap;
  margin-top: 10px;
}
.cmd-banner-when { font-size: var(--fs-75); color: var(--text-dim); }

/* A datastore's name, shown exactly as the operator typed it — the shared
   .cmd-net-title uppercases, which is fine for a section heading and wrong for
   an identifier that must match an NFS export. */
.cmd-ds-name {
  font-size: var(--fs-100);
  font-weight: 600;
  margin: 0;
}

/* Cluster → Datastores: the declare form's three fields. */
.cmd-ds-form {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(180px, 1fr));
  gap: 0 var(--sp-3);
}

/* Skeleton placeholders (see skeletonCard in objectview.js). */
.cmd-detail-skel {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(280px, 1fr));
  gap: var(--sp-4);
  margin-bottom: var(--sp-4);
}
.cmd-skel {
  display: flex;
  flex-direction: column;
  gap: 10px;
}

/* Recent Tasks: who started it. */
.cmd-task-initiator { color: var(--text-dim); }
.cmd-task-unrecorded { color: var(--text-muted); font-style: italic; }

/* ============================================================
   Summary card grid.
   Two columns, asymmetric: a wide left (~62%) for the things you
   read, a narrow right (~38%) for the things you check. Collapses
   to one column below 1100px. Cards reorder by drag and NEVER
   resize — position is the operator's, dimensions are not.
   ============================================================ */
.cmd-grid {
  display: grid;
  grid-template-columns: repeat(12, 1fr);
  gap: var(--sp-4);
  align-content: start;
}
.cmd-col-3 { grid-column: span 3; }
.cmd-col-4 { grid-column: span 4; }
.cmd-col-5 { grid-column: span 5; }
.cmd-col-6 { grid-column: span 6; }
.cmd-col-8 { grid-column: span 8; }
.cmd-col-12 { grid-column: span 12; }
@media (max-width: 1100px) {
  .cmd-col-3, .cmd-col-4, .cmd-col-5 { grid-column: span 6; }
  .cmd-col-6, .cmd-col-8 { grid-column: span 12; }
}
@media (max-width: 640px) {
  .cmd-grid > * { grid-column: span 12; }
}
.cmd-card {
  padding: var(--card-pad);
  margin: 0;
  transition: border-color var(--dur-2) var(--ease-standard),
    box-shadow var(--dur-2) var(--ease-standard),
    transform var(--dur-2) var(--ease-standard);
}
.cmd-card:hover { border-color: var(--border-strong); }
:root[data-theme="light"] .cmd-card:hover { box-shadow: var(--shadow-sm); }
.cmd-card-head { display: flex; align-items: center; gap: var(--sp-2); margin-bottom: var(--sp-2); }
/* The card glyph USED to be a tinted rounded medallion. Together with a 1px box
   around every panel, that medallion is the most recognisable single detail of
   the heavily-styled enterprise card, and it was repeated a dozen times on every screen —
   which is why the product kept reading as generic no matter what the palette
   did. The glyph stays (it aids scanning); the medallion goes, and the accent
   keyline that marks status elsewhere marks the card title instead. One motif,
   used consistently, instead of a borrowed one. */
.cmd-card-ic {
  box-sizing: border-box;
  width: 17px;
  height: 17px;
  padding: 0;
  border-radius: 0;
  background: none;
  /* The glyph now takes the card's category colour (falling back to muted grey
     for structural cards that have no category). This is where most of the new
     colour lands — a grid of summary tiles becomes scannable by hue. */
  color: var(--card-accent, var(--text-muted));
  flex: none;
}
.cmd-card-head::before {
  content: "";
  width: 2px;
  align-self: stretch;
  min-height: 13px;
  border-radius: 1px;
  /* Neutral by default — a quiet structural tick, not a repeated blue stripe.
     A card that carries real status earns a coloured keyline via .is-ok/-warn/-crit
     (below); a genuinely clickable card opts into the accent via .cmd-card-nav. */
  background: var(--card-accent, var(--keyline));
  flex: none;
}
.cmd-card.is-ok .cmd-card-head::before { background: var(--ok); }
.cmd-card.is-warn .cmd-card-head::before { background: var(--warn); }
.cmd-card.is-crit .cmd-card-head::before { background: var(--err); }
.cmd-card-nav .cmd-card-head::before { background: var(--accent); }
/* Category tints, set by card() from the card's icon. Keyline + glyph only —
   the title text stays --text so legibility never rides on a colour. */
.cmd-accent-violet,
.cmd-accent-rose,
.cmd-accent-cyan,
.cmd-accent-blue,
.cmd-accent-k8s { --card-accent: var(--cat); }
.cmd-card-title {
  /* The display face carries the identity here (applied via the font-display group
     below). A touch larger + semibold + sentence case reads more deliberate than
     13px bold Inter — card titles were the most prominent text rendered in the body
     font. */
  font-size: var(--fs-100);
  font-weight: var(--fw-semibold);
  letter-spacing: var(--ls-tight);
  color: var(--text);
  text-transform: none;
  margin: 0;
  flex: 1 1 auto;
  min-width: 0;
}
/* Optional trailing count (mockup .ch .more) — e.g. "2" beside "Virtual machines". */
.cmd-card-count { margin-left: auto; font-size: var(--fs-50); color: var(--text-muted); font-variant-numeric: tabular-nums; }
.cmd-card-grip {
  /* Reorder is a power-user affordance, not something every card should shout.
     Hidden until the card is hovered so the header reads as an icon + title,
     not icon + clutter + title — the always-on grip was the visual noise the
     redesign set out to remove. Reserve its width so titles don't shift on
     hover. */
  color: var(--text-disabled);
  cursor: grab;
  font-size: var(--fs-75);
  line-height: 1;
  letter-spacing: -2px;
  user-select: none;
  flex: none;
  visibility: hidden;
}
.cmd-card-grip:active { cursor: grabbing; }
.cmd-card:hover .cmd-card-grip { visibility: visible; color: var(--text-muted); }
/* Keyboard users need it too: reveal when the grip itself takes focus. */
.cmd-card-grip:focus-visible { visibility: visible; color: var(--text-dim); }
.cmd-card-dragging { opacity: 0.45; }
/* Per-card staleness. It is a property of THIS card's numbers, which is the
   whole reason the label moved off the page and onto the card. */
.cmd-card-stale {
  font-size: var(--fs-50);
  color: var(--warn);
  background: var(--warn-tint);
  border-radius: 4px;
  padding: var(--sp-px) var(--sp-2);
  margin-bottom: var(--sp-2);
}
.cmd-card-body { min-width: 0; }
.cmd-card-body > .cmd-net-section:first-of-type { margin-top: 10px; }
.cmd-card-foot {
  margin-top: 10px;
  padding-top: var(--sp-2);
  border-top: var(--sp-px) solid var(--border);
  display: flex;
  gap: var(--sp-2);
  flex-wrap: wrap;
}
.cmd-grid-tools { display: flex; justify-content: flex-end; margin-top: var(--sp-3); }

/* ---- Bands ---------------------------------------------------------------
   Home used to be one flat grid: eight identical tiles, then six more cards,
   nothing saying which mattered or what any of it was for. A band gives a group
   of tiles a name and one line of plain English, which is what turns a wall of
   numbers into something a person who has never seen this product can read.
   The generous space ABOVE a band title is doing the real work — it is what
   separates three ideas without drawing a single box around them. */
.cmd-band + .cmd-band { margin-top: 30px; }
.cmd-band-head { margin: 0 0 10px; }
.cmd-band-title {
  margin: 0;
  font-size: var(--fs-50);
  font-weight: 700;

  letter-spacing: 0.1em;
  color: var(--text-dim);
}
/* The keyline again, at band scale: the same mark that means "this one" on a
   status plate and "you are here" in the tree now means "this group". */
.cmd-band-title::before {
  content: "";
  display: inline-block;
  width: 3px;
  height: 11px;
  border-radius: 1px;
  background: var(--accent);
  margin-right: var(--sp-2);
  vertical-align: -1px;
}
/* .cmd-band-cap intentionally removed: a paragraph of explanation under every
   section heading reads as a manual bolted onto the product. The heading states
   the grouping; that is enough. */

/* ---- Big stat (CPU / Memory / Storage / VMs summary tiles, mockup .stat).
   The bar itself comes from meter(pct) (parts.js) so the 75/90% warn/crit
   tints still fire — do NOT hardcode an accent gradient here. ---- */
.cmd-stat { display: flex; flex-direction: column; gap: var(--sp-2); }
/* Compact-but-prominent KPI figure (was 32px — "giant number" territory). The
   ring value tracks this so a count tile and a gauge tile read as one family. */
.cmd-stat-big { font-size: var(--fs-600); font-weight: var(--fw-bold); line-height: 1; letter-spacing: var(--ls-tight); font-variant-numeric: tabular-nums; }
/* Value echoes its meter tone: a 91% tile is red at the number, not only the bar. */
.cmd-stat-big.cmd-stat-warn { color: var(--warn); }
.cmd-stat-big.cmd-stat-crit { color: var(--err); }
/* A genuine all-clear reads green — "Alarms: None", "3/3 hosts up" at full health — so the
   fleet-health band shows its good news in colour, not grey. Pure status (var(--ok)), within
   the sanctioned status+interaction palette; a count that drops still escalates amber/red. */
.cmd-stat-big.cmd-stat-ok { color: var(--ok); }
.cmd-stat-big small { font-size: var(--fs-50); font-weight: var(--fw-regular); color: var(--text-dim); margin-left: var(--sp-1); }
.cmd-stat-sub { font-size: var(--fs-75); color: var(--text-dim); font-variant-numeric: tabular-nums; line-height: var(--lh-snug); }

/* Left-aligned action row (Backup & Restore panel, etc.). */
.cmd-inline-actions {
  display: flex;
  gap: var(--sp-2);
  margin: var(--sp-3) 0 var(--sp-6);
}

/* ---------- SimpleVirt Workload Distributor (DRS) tab ------------------------ */
.cmd-drs-head { display: flex; align-items: center; gap: var(--sp-3); margin-bottom: var(--sp-2); }
.cmd-drs-head .cmd-net-title { margin: 0; }
.cmd-drs-head .btn { margin-left: auto; }
.cmd-drs-gap { margin-top: var(--sp-5); }

.cmd-drs-modes { display: grid; grid-template-columns: 1fr 1fr; gap: var(--sp-3); }
.cmd-drs-mode {
  text-align: left;
  padding: var(--sp-3) var(--sp-4);
  border: 1px solid var(--border-strong);
  border-radius: var(--r-card);
  background: var(--bg-inset);
  color: var(--text);
  cursor: pointer;
  transition: border-color var(--dur-2) var(--ease-standard), background var(--dur-2) var(--ease-standard);
}
.cmd-drs-mode:hover { border-color: var(--border-focus); }
.cmd-drs-mode.active { border-color: var(--accent); background: var(--accent-tint); }
.cmd-drs-mode-t { font-weight: var(--fw-semibold); margin-bottom: var(--sp-px); }
.cmd-drs-mode-s { font-size: var(--fs-75); color: var(--text-dim); }

.cmd-drs-slider-row { display: flex; align-items: center; gap: var(--sp-3); margin-top: var(--sp-2); }
.cmd-drs-slider { flex: 1 1 auto; accent-color: var(--accent); }
.cmd-drs-slider-end { font-size: var(--fs-75); color: var(--text-muted); flex: none; }
.cmd-drs-slider-val { font-size: var(--fs-75); font-weight: var(--fw-semibold); color: var(--accent); min-width: 96px; text-align: right; }

.cmd-drs-rules { display: flex; flex-direction: column; gap: var(--sp-1); margin: var(--sp-2) 0 var(--sp-4); }
.cmd-drs-rule {
  display: flex;
  align-items: center;
  gap: var(--sp-3);
  padding: var(--sp-2) var(--sp-3);
  border: 1px solid var(--border);
  border-radius: var(--r-control);
  background: var(--bg-inset);
}
.cmd-drs-rule-vm { font-weight: var(--fw-medium); min-width: 120px; }
.cmd-drs-rule-host { color: var(--text-dim); }
.cmd-drs-rule .btn { margin-left: auto; }
.cmd-drs-none { margin: var(--sp-2) 0 var(--sp-3); }

.cmd-drs-addrule {
  display: grid;
  grid-template-columns: 2fr 1fr 1fr;
  gap: var(--sp-3);
  align-items: end;
}
.cmd-drs-addrule .form-check { grid-column: 1 / -1; margin-bottom: 0; }
.cmd-drs-addrule .cmd-inline-actions { grid-column: 1 / -1; margin: 0; }

.cmd-drs-recs { display: flex; flex-direction: column; gap: var(--sp-2); }
.cmd-drs-rec {
  padding: var(--sp-3);
  border: 1px solid var(--border);
  border-left: var(--sp-px) solid var(--accent);
  border-radius: var(--r-control);
  background: var(--bg-inset);
}
.cmd-drs-rec-head { display: flex; align-items: center; gap: var(--sp-3); }
.cmd-drs-rec-move { color: var(--text-dim); font-variant-numeric: tabular-nums; }
.cmd-drs-rec-head .btn { margin-left: auto; }
.cmd-drs-rec-reason { margin: var(--sp-2) 0 0; font-size: var(--fs-75); color: var(--text-muted); }
@media (max-width: 640px) {
  .cmd-drs-modes, .cmd-drs-addrule { grid-template-columns: 1fr; }
}

/* Landing overview subhead — a one-line summary + health chip, above the tiles. */
.cmd-pool-line {
  font-size: var(--fs-100);
  color: var(--text-dim);
  line-height: var(--lh-snug);
  margin: 0 0 var(--sp-4);
  padding-left: var(--sp-3);
  border-left: var(--sp-px) solid var(--accent);
}
.cmd-pool-line .badge {
  margin-left: var(--sp-2);
  vertical-align: middle;
}
/* Thin borderless fill in a stat tile (PatternFly/Cloudscape KPI), vs the heavier
   bordered gauges in the Monitor capacity rows. */
.cmd-stat .meter { height: 6px; border: none; }

/* Ring gauge — the conic-fill dial that replaces the flat proportion bar on
   utilization / ratio stat tiles, turning the Overview row from spreadsheet
   cells into instrument dials. The conic background is set inline via CSSOM (the
   one style the CSP permits, exactly like the meter's width); tone tracks the
   same warn@75 / crit@90 thresholds as meter(). The center keeps the tile's own
   value so no reading is lost. */
/* A ratio/count tile aligns to the START (its number reads left, like the label
   above it); only a ring tile centers, because a dial is a centered object. */
.cmd-stat-ring { align-items: center; text-align: center; gap: var(--sp-2); }
.cmd-ring {
  /* A capacity donut is a fuel gauge: brand blue while there is headroom, escalating to amber
     at 75% and red at 90% (the overrides below). It reads utilization, NOT a card's category
     hue — so it fills --accent directly rather than the (neutralized, graphite) --card-accent,
     matching the house capacityRing() primitive. */
  --ring-fill: var(--grey); /* utilization ring: neutral steel; colour only at the warn/crit threshold */
  --ring-track: color-mix(in srgb, var(--bg-inset) 78%, var(--text-muted));
  /* Sized for a utilization reading ("100%" at most) — ratios no longer use the
     ring, so it can be compact instead of the old 104px plate that made every
     summary row tall. */
  width: 84px;
  height: 84px;
  border-radius: 50%;
  display: grid;
  place-items: center;
  flex: none;
  box-shadow: inset 0 0 0 1px color-mix(in srgb, var(--border) 45%, transparent);
}
.cmd-ring.cmd-stat-warn { --ring-fill: var(--warn); }
.cmd-ring.cmd-stat-crit { --ring-fill: var(--err); }
.cmd-ring-hole {
  width: 64px;
  height: 64px;
  border-radius: 50%;
  background: var(--bg-raised);
  display: grid;
  place-items: center;
  box-shadow: inset 0 1px 3px rgba(0, 0, 0, 0.35);
}
.cmd-ring-val {
  font-size: var(--fs-500);
  font-weight: var(--fw-bold);
  line-height: 1;
  letter-spacing: var(--ls-tight);
  font-variant-numeric: tabular-nums;
  color: var(--text);
  max-width: 56px;
  text-align: center;
}
.cmd-ring-val small { font-size: var(--fs-100); font-weight: var(--fw-regular); color: var(--text-dim); margin-left: 1px; }
.cmd-ring.cmd-stat-warn .cmd-ring-val { color: var(--warn); }
.cmd-ring.cmd-stat-crit .cmd-ring-val { color: var(--err); }

/* Icon+label pairing (replaces the emoji prefixes in the ISO/CD-ROM pickers). */
.cmd-ic-label { display: inline-flex; align-items: center; gap: var(--sp-2); min-width: 0; }
.cmd-ic-label svg { flex: none; width: 15px; height: 15px; color: var(--text-muted); }
.cmd-ic-text { overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }

/* Utilization sparklines (Monitor → Utilization) — a live trend line, the shot a
   monitoring product leads with. The SVG stretches to width; non-scaling-stroke
   keeps the line crisp. Tone matches the meter thresholds (warn@75 / crit@90). */
.cmd-spark-row { display: grid; grid-template-columns: 1fr 1fr; gap: var(--sp-4); margin: var(--sp-2) 0 var(--sp-4); }
.cmd-spark-card {
  background: var(--bg-inset);
  border-radius: var(--r-sm);
  padding: var(--sp-3);
  box-shadow: inset 0 0 0 1px color-mix(in srgb, var(--border) 70%, transparent);
}
.cmd-spark-head { display: flex; align-items: baseline; justify-content: space-between; margin-bottom: var(--sp-2); }
.cmd-spark-label { font-size: var(--fs-50); letter-spacing: var(--ls-caps); color: var(--text-muted); }
.cmd-spark-pct { font-size: var(--fs-500); font-weight: var(--fw-bold); letter-spacing: var(--ls-tight); font-variant-numeric: tabular-nums; color: var(--text); }
.cmd-spark-pct small { font-size: var(--fs-75); font-weight: var(--fw-regular); color: var(--text-dim); margin-left: 1px; }
.cmd-spark-pct.cmd-stat-warn { color: var(--warn); }
.cmd-spark-pct.cmd-stat-crit { color: var(--err); }
.cmd-spark { width: 100%; height: 44px; display: block; }
.cmd-spark-line { fill: none; stroke: var(--accent); stroke-width: 1.75; vector-effect: non-scaling-stroke; stroke-linejoin: round; stroke-linecap: round; }
.cmd-spark-area { fill: color-mix(in srgb, var(--accent) 15%, transparent); stroke: none; }
.cmd-spark.cmd-stat-warn .cmd-spark-line { stroke: var(--warn); }
.cmd-spark.cmd-stat-warn .cmd-spark-area { fill: color-mix(in srgb, var(--warn) 15%, transparent); }
.cmd-spark.cmd-stat-crit .cmd-spark-line { stroke: var(--err); }
.cmd-spark.cmd-stat-crit .cmd-spark-area { fill: color-mix(in srgb, var(--err) 15%, transparent); }
@media (max-width: 640px) { .cmd-spark-row { grid-template-columns: 1fr; } }

/* ---- Key/value list (Configuration / Identity / Options, mockup .kv):
   label left, value right. ---- */
.cmd-kv { display: grid; grid-template-columns: auto 1fr; gap: var(--sp-2) var(--sp-4); font-size: var(--fs-75); }
.cmd-kv dt { color: var(--text-dim); }
.cmd-kv dd { margin: 0; text-align: right; color: var(--text); font-weight: 500; overflow-wrap: anywhere; }
.cmd-kv dd a, .cmd-kv dd .cmd-vm-link { color: var(--accent); }

/* ---- Dense mini-table (dataTable variant, mockup .mt). ---- */
.cmd-mini { width: 100%; border-collapse: collapse; font-size: var(--fs-75); }
.cmd-mini th {
  text-align: left;
  color: var(--text-muted);
  font-weight: 600;
  font-size: var(--fs-50);

  letter-spacing: 0.4px;
  padding: var(--sp-2) 14px;
  border-bottom: var(--sp-px) solid var(--border);
}
.cmd-mini td { padding: var(--sp-2) 14px; border-bottom: var(--sp-px) solid var(--border); }
.cmd-mini tr:last-child td { border-bottom: none; }
.cmd-mini-name { display: flex; align-items: center; gap: var(--sp-2); font-weight: 600; }
.cmd-mini-name svg { width: 14px; height: 14px; }

/* ---- Related-object rows with icon + link (mockup .ro). ---- */
.cmd-ro { display: flex; align-items: center; justify-content: space-between; padding: var(--sp-2) 0; border-bottom: var(--sp-px) solid var(--border); }
.cmd-ro:last-child { border-bottom: none; }
.cmd-ro .l { display: flex; align-items: center; gap: var(--sp-2); color: var(--text); }
.cmd-ro .l svg { width: 15px; height: 15px; color: var(--text-dim); }
.cmd-ro a, .cmd-ro .cmd-vm-link { color: var(--accent); font-weight: 600; }

/* ---- Footer text-link ("Configure ›") — a read-only jump alongside the
   button footers (mockup .cf a). ---- */
.cmd-foot-link { color: var(--accent); font-size: var(--fs-75); font-weight: 600; background: none; border: none; cursor: pointer; padding: 0; }
.cmd-foot-link::after { content: ""; }

/* Related Objects: every row is a real link to a real object. */
.cmd-related-row {
  display: flex;
  align-items: baseline;
  justify-content: space-between;
  gap: 10px;
  padding: var(--sp-1) 0;
  border-top: var(--sp-px) solid var(--border);
  font-size: var(--fs-75);
}
.cmd-related-row:first-child { border-top: none; }
.cmd-related-row-stack { flex-direction: column; align-items: flex-start; gap: var(--sp-px); }
.cmd-related-label { color: var(--text-muted); flex: none; }
.cmd-related-flat { color: var(--text-dim); }
.cmd-related-links { display: flex; flex-wrap: wrap; gap: var(--sp-1) 10px; justify-content: flex-end; }
.cmd-related-row-stack .cmd-related-links { justify-content: flex-start; }
.cmd-unknown-note { color: var(--warn); }
.cmd-monitor-note { margin-top: 18px; border-top: var(--sp-px) solid var(--border); padding-top: 10px; }
.cmd-fingerprint { font-size: var(--fs-50); word-break: break-all; }

/* ============================================================
   Monitor / Configure sub-navigation.
   Sub-tabs are ADDRESSABLE (…/configure/networking) — without
   that, a link into a sub-page is impossible.
   ============================================================ */
.cmd-subnav { display: grid; grid-template-columns: 172px 1fr; gap: var(--sp-5); align-items: start; }
@media (max-width: 900px) {
  .cmd-subnav { grid-template-columns: 1fr; gap: var(--sp-3); }
  .cmd-subrail { flex-direction: row; flex-wrap: wrap; border-right: none; border-bottom: var(--sp-px) solid var(--border); padding-bottom: var(--sp-2); }
}
.cmd-subrail {
  display: flex;
  flex-direction: column;
  gap: var(--sp-px);
  border-right: var(--sp-px) solid var(--border);
  padding-right: 10px;
  position: sticky;
  top: 0;
}
.cmd-subrail-btn {
  background: none;
  border: none;
  border-radius: 5px;
  color: var(--text-dim);
  font: inherit;
  font-size: var(--fs-75);
  text-align: left;
  padding: 6px var(--sp-2);
  cursor: pointer;
  transition: background var(--dur-2) var(--ease-standard), color var(--dur-2) var(--ease-standard);
}
.cmd-subrail-btn:hover { background: var(--bg-hover); color: var(--text); }
.cmd-subrail-btn.active { background: var(--accent-tint); color: var(--accent-hover); font-weight: 600; }
.cmd-subrail-btn:focus-visible { outline: 2px solid var(--focus-ring); outline-offset: -2px; }
.cmd-subpanel { min-width: 0; }

/* ============================================================
   ACTIONS — one registry, two surfaces: the header menu and the
   tree's right-click menu. Both render from actions.js.
   ============================================================ */
.cmd-actions-wrap { position: relative; }
.cmd-actions-trigger { display: inline-flex; align-items: center; gap: 6px; }
.cmd-actions-caret { font-size: var(--fs-50); color: var(--text-muted); }
.cmd-menu {
  min-width: 200px;
  background: var(--bg-raised);
  border: 1px solid var(--border-strong);
  border-radius: var(--r-card);
  box-shadow: var(--shadow);
  padding: var(--sp-1);
  display: flex;
  flex-direction: column;
  gap: var(--sp-px);
}
.cmd-actions-menu { position: absolute; right: 0; top: calc(100% + var(--sp-1)); z-index: 40; }
.cmd-context-menu { position: fixed; z-index: 60; }
.cmd-menu-item {
  background: none;
  border: none;
  border-radius: 4px;
  color: var(--text);
  font: inherit;
  font-size: var(--fs-75);
  text-align: left;
  padding: 6px 10px;
  cursor: pointer;
  white-space: nowrap;
}
.cmd-menu-item:hover { background: var(--bg-hover); }
.cmd-menu-item:focus-visible { outline: 2px solid var(--focus-ring); outline-offset: -2px; }
.cmd-menu-primary { color: var(--accent-hover); font-weight: 600; }
.cmd-menu-danger { color: var(--err); }
.cmd-menu-danger:hover { background: var(--err-tint); }

/* The destructive band's rule. Every object's ACTIONS menu draws exactly one,
   immediately above the verbs that make the object stop existing, so the
   pointer never lands on Delete by habit. */
.cmd-menu-sep {
  height: 1px;
  margin: 6px 0;
  background: var(--border);
}

/* Surface 3: the ⋯ row menu. It replaces the scatter of small buttons that used
   to sit in a table's last cell — one control, the object's real verb list. */
.cmd-rowmenu { position: relative; display: inline-block; }
.cmd-rowmenu-trigger {
  min-width: 28px;
  padding: var(--sp-px) var(--sp-2);
  line-height: 1.1;
  font-size: var(--fs-200);
  letter-spacing: 1px;
}
/* A row menu opens against the right edge of the table, so it is anchored right
   rather than left — a left-anchored menu on the last column runs off-screen. */
.cmd-menu-right { right: 0; left: auto; }

/* CD/DVD media dialog: a compact datastore ISO browser (folders to descend,
   .iso files to select), reusing the datastore file feed. */
.cmd-cdrom-crumb { display: flex; align-items: center; flex-wrap: wrap; gap: var(--sp-px); margin: var(--sp-2) 0 6px; }
.cmd-cdrom-link { background: none; border: none; color: var(--accent-hover); font: inherit; font-size: var(--fs-75); padding: var(--sp-px) var(--sp-1); cursor: pointer; border-radius: 4px; }
.cmd-cdrom-link:hover { background: var(--bg-hover); }
.cmd-cdrom-list {
  display: flex; flex-direction: column; gap: var(--sp-px);
  max-height: 260px; overflow-y: auto;
  border: 1px solid var(--border); border-radius: var(--r-card);
  background: var(--bg-inset); padding: var(--sp-1);
}
.cmd-cdrom-dir, .cmd-cdrom-file {
  background: none; border: none; border-radius: 4px;
  color: var(--text); font: inherit; font-size: var(--fs-75);
  text-align: left; padding: 6px 10px; cursor: pointer; white-space: nowrap;
}
.cmd-cdrom-dir:hover, .cmd-cdrom-file:hover { background: var(--bg-hover); }
.cmd-cdrom-file.is-selected { background: var(--accent-tint); color: var(--accent-hover); font-weight: 600; }
.cmd-cdrom-dir:focus-visible, .cmd-cdrom-file:focus-visible, .cmd-cdrom-link:focus-visible { outline: 2px solid var(--focus-ring); outline-offset: -2px; }

/* The Datastore Files tab's host picker: browsing is always through ONE host's
   mount, and the sentence next to the picker says which. */
.cmd-files-head { display: flex; align-items: flex-start; gap: var(--sp-4); flex-wrap: wrap; margin-bottom: 10px; }
.cmd-files-picker { min-width: 260px; margin-bottom: 0; }
.cmd-files-head > .form-hint { flex: 1 1 260px; margin: 0; align-self: center; }
.cmd-crumbs { justify-content: flex-start; flex-wrap: wrap; }

/* ---------- k8s provisioning progress timeline -------------------------- */
/* A live, phased view of provisioning: each step shows a status glyph, a title,
   the latest one-line detail, and expandable logs — so the operator can watch,
   open a step's logs, and leave/return without losing the thread. */
.k8s-progress { display: flex; flex-direction: column; gap: var(--sp-1); }
.k8s-step {
  padding: var(--sp-2) var(--sp-2) var(--sp-2) var(--sp-1);
  border-radius: var(--r-control);
}
.k8s-step.k8s-running { background: var(--accent-tint); }
.k8s-step-head { display: flex; align-items: center; gap: var(--sp-2); min-width: 0; }
.k8s-step-title { font-weight: var(--fw-semibold); flex: none; }
.k8s-step-detail {
  color: var(--text-dim);
  font-size: var(--fs-75);
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
  min-width: 0;
}
.k8s-step-elapsed {
  flex: none;
  font-size: var(--fs-75);
  font-variant-numeric: tabular-nums;
  color: var(--accent);
  background: var(--accent-tint);
  border-radius: var(--r-pill);
  padding: 0 var(--sp-2);
}
.k8s-logs-toggle { margin-left: auto; flex: none; }
.k8s-step-ic {
  width: 20px;
  height: 20px;
  flex: none;
  display: inline-flex;
  align-items: center;
  justify-content: center;
  border-radius: var(--r-pill);
  font-size: var(--fs-75);
  font-weight: var(--fw-bold);
  border: 1.5px solid var(--border-strong);
  color: var(--text-muted);
  background: var(--bg-raised);
}
.k8s-ic-done { color: var(--accent-fg); background: var(--ok); border-color: var(--ok); }
.k8s-ic-running { color: var(--accent); border-color: var(--accent); animation: cmd-spin 1.1s linear infinite; }
.k8s-ic-warn { color: var(--accent-fg); background: var(--warn); border-color: var(--warn); }
.k8s-ic-failed { color: var(--accent-fg); background: var(--err); border-color: var(--err); }
.k8s-step-logs {
  margin: var(--sp-2) 0 0 28px;
  padding: var(--sp-2) var(--sp-3);
  background: var(--bg-inset);
  border: 1px solid var(--border);
  border-radius: var(--r-control);
  font-family: var(--mono);
  font-size: var(--fs-50);
  line-height: 1.55;
  color: var(--text-dim);
  max-height: 190px;
  overflow: auto;
  white-space: pre-wrap;
  word-break: break-word;
}
.btn-xs { padding: var(--sp-px) var(--sp-2); font-size: var(--fs-50); height: auto; }
.k8s-join-cmd {
  width: 100%;
  font-family: var(--mono);
  font-size: var(--fs-75);
  min-height: 64px;
  resize: vertical;
}

/* ---------- k8s create: form + live YAML editor ------------------------- */
/* Widen only the create-cluster modal so the form + YAML sit side by side.
   Falls back gracefully (760px + column wrap) where :has() is unsupported. */
.modal-wide:has(.k8s-create-cols) {
  /* The cluster wizard is content-dense (a two-column form + live config), so it
     is sized to actually USE the screen instead of floating small: near-full width
     on a wide display, near-full height so the form scrolls less. */
  max-width: min(1520px, 95vw);
  max-height: calc(100vh - 40px);
}
.k8s-create-cols {
  display: flex;
  gap: var(--sp-6);
  align-items: flex-start;
}
/* The form column: stacked, breathing sections instead of one dense run. */
.k8s-create-cols > :first-child {
  flex: 1 1 55%;
  min-width: 0;
  display: flex;
  flex-direction: column;
  gap: var(--sp-4);
}
.k8s-form-section {
  background: var(--bg-inset);
  border: 1px solid var(--border);
  border-radius: var(--r-card);
  padding: var(--sp-4);
}
.k8s-form-section-head {
  margin-bottom: var(--sp-3);
}
.k8s-form-section-title {
  display: block;
  font-weight: var(--fw-semibold);
  font-size: var(--fs-100);
}
.k8s-form-section-sub {
  display: block;
  color: var(--text-dim);
  font-size: var(--fs-75);
  margin-top: 2px;
}
.k8s-size-grid {
  display: grid;
  grid-template-columns: 1fr 1fr;
  gap: var(--sp-4);
}
.k8s-size-h {
  font-weight: var(--fw-semibold);
  font-size: var(--fs-75);
  color: var(--text-dim);

  letter-spacing: 0.04em;
  margin-bottom: var(--sp-2);
}
.k8s-yaml-panel {
  flex: 1 1 45%;
  min-width: 0;
  display: flex;
  flex-direction: column;
  gap: var(--sp-2);
  /* Keep the live config in view while the form column scrolls (VCF-style). */
  position: sticky;
  top: 0;
  align-self: flex-start;
}
.k8s-yaml-head {
  display: flex;
  align-items: baseline;
  gap: var(--sp-2);
}
.k8s-yaml-title {
  font-weight: var(--fw-semibold);
}
.k8s-yaml-sub {
  color: var(--text-dim);
  font-size: var(--fs-75);
}
.k8s-yaml {
  width: 100%;
  flex: 1 1 auto;
  min-height: 420px;
  max-height: calc(100vh - 240px);
  margin: 0;
  /* Recessed code surface. Was a hard-coded near-black (#05070a) that never
     flipped: in light mode the background stayed near-black while --text-dim
     flipped to a dark slate, leaving dark-on-dark YAML that was unreadable.
     Use --bg-inset — the same recessed token the sibling .provision-yaml
     preview already rides on — so the panel tracks both themes (near-black in
     dark, pale grey in light) and stays legible either way. Dark mode is
     visually unchanged: --bg-inset is #070708, a hair off the old #05070a. */
  background: var(--bg-inset);
  border: 1px solid var(--border-strong);
  border-radius: var(--r-control);
  color: var(--text-dim);
  padding: var(--sp-3);
  font-family: ui-monospace, SFMono-Regular, Menlo, Consolas, monospace;
  font-size: var(--fs-75);
  line-height: 1.55;
  white-space: pre;
  overflow: auto;
  tab-size: 2;
}
@media (max-width: 900px) {
  .k8s-create-cols {
    flex-direction: column;
  }
  .k8s-size-grid {
    grid-template-columns: 1fr;
  }
}

/* ---------- Provisioning destination ------------------------------------ */
/* Two columns: the blueprint form on the left, a sticky live cloud-init preview
   on the right that stays in view while the form scrolls. Stacks under 820px. */
/* ---------- Provisioning page ---------------------------------------------
   A card-sectioned blueprint form (left) beside a sticky live cloud-init
   preview (right), reined to a comfortable measure so fields never sprawl the
   full width of an ultrawide display. Cards + a 2-column field grid give the
   page the density and hierarchy the rest of Command uses. */
.provision-page {
  width: 100%;
}
.provision-cols {
  display: grid;
  grid-template-columns: minmax(0, 1fr) minmax(380px, 460px);
  gap: var(--sp-5);
  align-items: start;
  padding: var(--sp-2) var(--sp-5) var(--sp-8);
}
.provision-form {
  min-width: 0;
  display: flex;
  flex-direction: column;
  gap: var(--sp-4);
}

/* one titled section */
.provision-card {
  background: var(--bg-panel);
  border: 1px solid var(--border);
  border-radius: var(--r-card);
  overflow: hidden;
}
.provision-card-head {
  padding: var(--sp-3) var(--sp-4);
  border-bottom: 1px solid var(--border-subtle);
  border-left: 3px solid var(--accent);
  background: linear-gradient(90deg, var(--accent-tint), transparent 60%), var(--bg-inset);
}
.provision-card-title {
  margin: 0;
  color: var(--text);
}
.provision-card-sub {
  margin: 3px 0 0;
  font-size: var(--fs-75);
  color: var(--text-muted);
}
.provision-card-body {
  padding: var(--sp-4);
}
.provision-card-body > * + * {
  margin-top: var(--sp-3);
}

/* field grid inside a card */
.provision-grid {
  display: grid;
  grid-template-columns: repeat(2, minmax(0, 1fr));
  gap: var(--sp-3) var(--sp-4);
}
.provision-grid--3 {
  grid-template-columns: repeat(3, minmax(0, 1fr));
}
.provision-grid > .span-2 {
  grid-column: 1 / -1;
}
.provision-grid .form-field {
  margin: 0;
}

/* action bar under the cards: error to the left, primary action right */
.provision-actions {
  display: flex;
  align-items: center;
  justify-content: flex-end;
  gap: var(--sp-3);
  flex-wrap: wrap;
  margin-top: var(--sp-1);
}
.provision-actions .form-error {
  margin: 0 auto 0 0;
  flex: 1 1 12rem;
}

/* sticky live preview */
.provision-aside {
  position: sticky;
  top: var(--sp-4);
  align-self: start;
  min-width: 0;
}
.provision-yaml-panel {
  display: flex;
  flex-direction: column;
  background: var(--bg-panel);
  border: 1px solid var(--border);
  border-radius: var(--r-card);
  overflow: hidden;
}
.provision-yaml-head {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: var(--sp-3);
  padding: var(--sp-3) var(--sp-4);
  border-bottom: 1px solid var(--border-subtle);
  background: var(--bg-inset);
}
.provision-yaml {
  width: 100%;
  flex: 1 1 auto;
  min-height: 460px;
  max-height: calc(100vh - 210px);
  resize: vertical;
  background: var(--bg-inset);
  border: 0;
  border-radius: 0;
  color: var(--text-dim);
  padding: var(--sp-3) var(--sp-4);
  font-family: ui-monospace, SFMono-Regular, Menlo, Consolas, monospace;
  font-size: var(--fs-75);
  line-height: 1.55;
  white-space: pre;
  overflow: auto;
  tab-size: 2;
}
.provision-yaml-foot {
  margin: 0;
  padding: var(--sp-2) var(--sp-4) var(--sp-3);
  border-top: 1px solid var(--border-subtle);
}

/* ---------- provisioning wizard (stepped layout) ----------------------- */
.provision-wizard {
  display: flex;
  flex-direction: column;
  gap: var(--sp-4);
  padding: var(--sp-2) var(--sp-5) var(--sp-8);
}
.provision-steps {
  display: flex;
  align-items: center;
  gap: var(--sp-2);
  flex-wrap: wrap;
}
.provision-stepchip {
  display: inline-flex;
  align-items: center;
  gap: 8px;
  background: transparent;
  border: 1px solid var(--border);
  border-radius: var(--r-pill);
  padding: 5px 12px 5px 6px;
  color: var(--text-muted);
  cursor: pointer;
  font-size: var(--fs-75);
  transition: border-color var(--dur-2) var(--ease-standard), color var(--dur-2) var(--ease-standard), background var(--dur-2) var(--ease-standard);
}
.provision-stepchip:disabled { cursor: default; opacity: 0.6; }
.provision-stepnum {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  width: 20px;
  height: 20px;
  border-radius: 50%;
  font-size: var(--fs-25);
  font-weight: 600;
  background: var(--border);
  color: var(--text-dim);
  flex-shrink: 0;
}
.provision-steplabel { white-space: nowrap; }
.provision-stepchip.is-active { border-color: var(--accent); color: var(--text); background: var(--accent-tint); }
.provision-stepchip.is-active .provision-stepnum { background: var(--accent); color: #fff; }
.provision-stepchip.is-done { color: var(--text-dim); border-color: var(--border-strong); }
.provision-stepchip.is-done .provision-stepnum { background: var(--ok); color: #fff; }
.provision-stepchip.is-done:hover:not(:disabled) { color: var(--text); border-color: var(--accent); }
.provision-stepsep { flex: 0 0 16px; height: 1px; background: var(--border); }

.provision-wizard-body {
  display: grid;
  grid-template-columns: minmax(0, 1fr) minmax(360px, 440px);
  gap: var(--sp-5);
  align-items: start;
}
.provision-main { min-width: 0; display: flex; flex-direction: column; gap: var(--sp-4); }
.provision-step { min-width: 0; }
.provision-steppane { display: none; flex-direction: column; gap: var(--sp-4); }
.provision-steppane.is-active { display: flex; animation: provision-step-in var(--dur-3) var(--ease-standard); }
@keyframes provision-step-in { from { opacity: 0; transform: translateY(4px); } to { opacity: 1; transform: none; } }
.provision-wizard-nav {
  display: flex;
  align-items: center;
  justify-content: flex-end;
  gap: var(--sp-3);
  flex-wrap: wrap;
}
.provision-wizard-nav .form-error { margin: 0 auto 0 0; flex: 1 1 12rem; }
.provision-wizard-btns { display: inline-flex; gap: var(--sp-2); }

.provision-review { background: var(--bg-panel); border: 1px solid var(--border); border-radius: var(--r-card); overflow: hidden; }
.provision-review-grid { display: grid; grid-template-columns: max-content minmax(0, 1fr); gap: 10px var(--sp-4); }
.provision-review-k { color: var(--text-muted); font-size: var(--fs-75); }
.provision-review-v { color: var(--text); min-width: 0; overflow-wrap: anywhere; }

/* Create-VM-from-OVF/OVA Configure step: a section subheading over each populated
   block (disks, firmware, networks) and the product/vendor header above them. */
.ova-cfg-subhead {
  margin: var(--sp-4) 0 var(--sp-2);
  font-size: var(--fs-75);
  font-weight: 600;

  letter-spacing: 0.04em;
  color: var(--text-muted);
}
.ova-cfg-head { margin-bottom: var(--sp-3); }
.ova-cfg-prod { font-size: var(--fs-200); font-weight: 600; color: var(--text); }

@media (prefers-reduced-motion: reduce) {
  .provision-steppane.is-active { animation: none; }
}

@media (max-width: 960px) {
  .provision-cols,
  .provision-wizard-body {
    grid-template-columns: 1fr;
  }
  .provision-aside {
    position: static;
  }
  .provision-grid,
  .provision-grid--3 {
    grid-template-columns: 1fr;
  }
}

/* ---------- v2: motion & micro-interactions ----------------------------- */
/* Tasteful, GPU-friendly (transform/opacity) interactions so surfaces feel
   tangible and responsive. The reduced-motion guard above neutralizes them all.
   Button motion is defined once in the canonical .btn block above. */

/* Cards + KV sections: a gentle hover lift so the surface feels alive
   (translateY is composited — no reflow). */
.card,
.cmd-section {
  transition: border-color var(--dur-2) var(--ease-standard), box-shadow var(--dur-2) var(--ease-standard), transform var(--dur-2) var(--ease-standard);
}
/* Hover deepens the resting elevation instead of jumping the card — a translate
   on every panel (most aren't clickable) reads as gimmicky and twitches a dense
   grid under the cursor. Depth now lives in the at-rest shadow; hover just
   strengthens it. */
.card:hover {
  border-color: var(--border-strong);
  box-shadow:
    var(--shadow-md),
    inset 0 1px 0 rgba(255, 255, 255, 0.05),
    0 0 0 1px var(--border-strong);
}

/* Rows / nav / badges ease their state changes instead of snapping. */
tbody tr { transition: background var(--dur-2) var(--ease-standard); }
.nav-item,
.drawer-item,
.vm-quick-item,
.tree-row {
  transition: background var(--dur-2) var(--ease-standard), color var(--dur-2) var(--ease-standard);
}
.badge,
.status-dot {
  transition: background var(--dur-2) var(--ease-standard), color var(--dur-2) var(--ease-standard), border-color var(--dur-2) var(--ease-standard);
}

/* Skeleton shimmer — a premium loading state. Give .skeleton a width/height. */
.skeleton {
  position: relative;
  overflow: hidden;
  background: var(--bg-inset);
  border-radius: var(--r-control);
}
.skeleton::after {
  content: "";
  position: absolute;
  inset: 0;
  transform: translateX(-100%);
  background: linear-gradient(90deg, transparent, var(--skeleton-sheen), transparent);
  animation: skeleton-shimmer 1.4s var(--ease-standard) infinite;
}
@keyframes skeleton-shimmer { to { transform: translateX(100%); } }

/* Content entrance — a soft fade + rise. Add .fade-in to a freshly mounted root. */
@keyframes fade-in-up {
  from { opacity: 0; transform: translateY(7px); }
  to { opacity: 1; transform: none; }
}
.fade-in { animation: fade-in-up var(--dur-4) var(--ease-entrance) both; }

/* Object-view navigation enter (host→VM etc.): a shorter fade-rise applied by
   route() ONLY on a real navigation, never a poll repaint. */
/* Navigating between objects AND between an object's tabs used to snap: the
   pane rose 4px over ~140ms, imperceptible enough to read as an instant jump —
   and once it was cut to an opacity-only fade it vanished entirely on table and
   form tabs. Now every tab switch and every object change slides the whole pane
   up a clear ~12px and fades it in over ~300ms with a springy ease, so moving
   through the product has a consistent, felt motion whatever the tab contains —
   a summary of cards, a settings form, or a table. It plays ONLY on a real
   navigation (the class is added on nav and removed on the 5s poll), so live
   data never re-triggers it. One motion for the whole pane, deliberately NOT
   compounded with a per-card stagger, which read as a double-slide. */
@keyframes cmd-view-enter {
  from { opacity: 0; transform: translateY(12px); }
  to { opacity: 1; transform: none; }
}
.cmd-view-enter { animation: cmd-view-enter 0.3s var(--ease-entrance) both; }
@media (prefers-reduced-motion: reduce) {
  @keyframes cmd-view-enter { from { opacity: 0; } to { opacity: 1; } }
  .cmd-view-enter { animation-duration: var(--dur-2); }
}

/* v4 polish: interactive elements that used to SNAP now cross-fade like the rest
   of the app (matching the ~140ms ease already on tree rows / lens buttons). */
.crumb,
.cmd-nav-drawer-item,
.cmd-menu-item,
.cmd-menu-btn,
.cmd-tab,
.cmd-cdrom-link {
  transition: background var(--dur-2) var(--ease-standard),
    color var(--dur-2) var(--ease-standard),
    border-color var(--dur-2) var(--ease-standard);
}

/* Lens + segmented controls get the same tactile press as buttons. */
.cmd-lens-btn:active,
.seg-btn:active {
  transform: scale(0.97);
}

/* KPI / summary meters ease in — but ONLY on a real navigation. Scoping under
   .cmd-view-enter is HALF the story: the class must also be short-lived, or it
   lingers on the pane and every 5s-poll rebuild of a meter matches this selector
   and replays the fill (the "bars reload every few seconds" flicker). app.js adds
   the class on navigation and strips it ~420ms later (see navEnterToken), so it is
   present only during the entrance and a background poll re-render never sees it. */
@keyframes meter-fill-in {
  from { transform: scaleX(0.03); }
  to { transform: scaleX(1); }
}
.cmd-view-enter .meter-fill {
  transform-origin: left center;
  animation: meter-fill-in var(--dur-4) var(--ease-entrance) both;
}

/* Staggered list entrance: children fade in in sequence (cap the depth so a big
   table doesn't ripple forever). Add .stagger-in to the container. */
.stagger-in > * { animation: fade-in-up 0.26s cubic-bezier(0.16, 1, 0.3, 1) both; }
.stagger-in > :nth-child(1) { animation-delay: 0.02s; }
.stagger-in > :nth-child(2) { animation-delay: 0.05s; }
.stagger-in > :nth-child(3) { animation-delay: 0.08s; }
.stagger-in > :nth-child(4) { animation-delay: 0.11s; }
.stagger-in > :nth-child(5) { animation-delay: var(--dur-2); }
.stagger-in > :nth-child(6) { animation-delay: 0.17s; }
.stagger-in > :nth-child(n + 7) { animation-delay: var(--dur-3); }

/* ---------- v3: Home (the fleet dashboard) ------------------------------ */
/* Home is denser than an object Summary: it is a whole-fleet read at a glance,
   so the grid gap and card padding tighten. Scoped under .cmd-home so no other
   page's rhythm moves — there is no global density modifier and adding one
   would silently restyle every Summary in the product. */
.cmd-home .cmd-grid { gap: 10px; }
.cmd-home .cmd-card { padding: var(--sp-2) var(--sp-3); }
.cmd-home .cmd-card-body { gap: var(--sp-2); }
.cmd-home .cmd-pool-line { margin-bottom: var(--sp-3); }
.cmd-home .cmd-section { margin-top: var(--sp-3); }

/* ---------- the fleet command hero -------------------------------------- *
 * One full-width instrument at the top of Home that subsumes the old
 * "Fleet health" and "Capacity" bands: a square status plate + headline drawn
 * from fleetIssues() (honest — no fleet issues reads "Operational"), the host /
 * VM counts, and CPU / Memory / Storage as neutral-steel rings. It is the first
 * thing the operator sees, so it carries the weight the flat tile trio could not. */
.cmd-hero {
  background:
    linear-gradient(180deg, color-mix(in srgb, var(--bg-raised) 60%, transparent), transparent 120px),
    var(--bg-panel);
  border: 1px solid var(--border);
  border-radius: var(--r-lg);
  padding: var(--sp-5) var(--sp-5) var(--sp-4);
  margin-bottom: var(--sp-4);
  box-shadow: var(--shadow-rest);
  display: flex;
  flex-direction: column;
  gap: var(--sp-4);
}
.cmd-hero-top { display: flex; align-items: center; gap: var(--sp-4); }
.cmd-hero-plate {
  width: 46px; height: 46px; border-radius: var(--r-md);
  display: grid; place-items: center; flex: none;
  font-size: var(--fs-500); font-weight: var(--fw-bold); line-height: 1;
  border: 1px solid var(--border);
  background: var(--bg-inset); color: var(--text-muted);
}
.cmd-hero-plate-ok { background: color-mix(in srgb, var(--ok) 15%, var(--bg-inset)); color: var(--ok); border-color: color-mix(in srgb, var(--ok) 42%, var(--border)); }
.cmd-hero-plate-warn { background: color-mix(in srgb, var(--warn) 17%, var(--bg-inset)); color: var(--warn); border-color: color-mix(in srgb, var(--warn) 45%, var(--border)); }
.cmd-hero-plate-crit { background: color-mix(in srgb, var(--err) 17%, var(--bg-inset)); color: var(--err); border-color: color-mix(in srgb, var(--err) 45%, var(--border)); }
.cmd-hero-headline { flex: 1; min-width: 0; }
.cmd-hero-title { font-size: var(--fs-600); font-weight: var(--fw-bold); letter-spacing: var(--ls-tight); line-height: 1.1; margin: 0; color: var(--text); }
.cmd-hero-ok .cmd-hero-title { color: var(--text); }
.cmd-hero-warn .cmd-hero-title { color: var(--warn); }
.cmd-hero-crit .cmd-hero-title { color: var(--err); }
.cmd-hero-sub { font-size: var(--fs-100); color: var(--text-dim); margin: var(--sp-1) 0 0; line-height: var(--lh-snug); }
.cmd-hero-top .cmd-foot-link { margin-left: auto; flex: none; white-space: nowrap; }
.cmd-hero-instruments {
  display: grid;
  grid-template-columns: repeat(5, minmax(0, 1fr));
  gap: var(--sp-4);
  padding-top: var(--sp-4);
  border-top: 1px solid var(--border-subtle);
  align-items: start;
}
.cmd-hero-kpi, .cmd-instrument {
  display: flex; flex-direction: column; align-items: center; text-align: center;
  gap: var(--sp-2); min-width: 0;
}
.cmd-hero-kpi .cmd-stat { align-items: center; text-align: center; gap: var(--sp-1); }
.cmd-instrument-label { font-size: var(--fs-50); letter-spacing: var(--ls-caps); color: var(--text-muted); font-weight: 600; }
.cmd-instrument-sub { font-size: var(--fs-50); color: var(--text-dim); line-height: var(--lh-snug); max-width: 15ch; }
.cmd-hero-instruments .cmd-ring { width: 94px; height: 94px; }
.cmd-hero-instruments .cmd-ring-hole { width: 72px; height: 72px; }
.cmd-hero-note { font-size: var(--fs-50); color: var(--text-muted); margin: 0; }
@media (max-width: 1080px) {
  .cmd-hero-instruments { grid-template-columns: repeat(3, minmax(0, 1fr)); row-gap: var(--sp-5); }
}
@media (max-width: 640px) {
  .cmd-hero-instruments { grid-template-columns: repeat(2, minmax(0, 1fr)); }
  .cmd-hero-top { flex-wrap: wrap; }
  .cmd-hero-top .cmd-foot-link { margin-left: 0; }
}

/* ---------- the slim finish-setup strip --------------------------------- *
 * Shown above the command centre when the platform exists (hosts + storage) but
 * setup is unacknowledged and incomplete — a non-blocking nudge with the next
 * step's action, NOT the full-screen wizard. One click dismisses it for good. */
.setup-strip {
  display: flex; align-items: center; justify-content: space-between; gap: var(--sp-4);
  background: var(--bg-inset);
  border: 1px solid var(--border);
  border-left: 3px solid var(--accent);
  border-radius: var(--r-md);
  padding: var(--sp-3) var(--sp-4);
  margin-bottom: var(--sp-4);
}
.setup-strip-main { display: flex; align-items: center; gap: var(--sp-3); min-width: 0; flex-wrap: wrap; }
.setup-strip-eyebrow { font-size: var(--fs-50); letter-spacing: var(--ls-caps); color: var(--accent); font-weight: 700; }
.setup-strip-count { font-size: var(--fs-75); color: var(--text-dim); font-variant-numeric: tabular-nums; }
.setup-strip-bar { width: 120px; height: 5px; background: var(--bg-raised); border-radius: var(--r-pill); overflow: hidden; flex: none; }
.setup-strip-fill { height: 100%; background: var(--accent); border-radius: var(--r-pill); }
.setup-strip-next { font-size: var(--fs-75); color: var(--text-dim); min-width: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.setup-strip-actions { display: flex; gap: var(--sp-2); flex: none; }

/* ---------- cluster Topology tab -------------------------------------- *
 * The fabric as hosts-as-columns with their VMs placed on them. Columns echo the
 * surface ladder; plates carry a tone by power state (neutral unless something is
 * wrong). Blue stays interaction-only; status uses ok/warn/err/grey. */
.cmd-topo-hero .cmd-hero-instruments { grid-template-columns: repeat(2, minmax(0, 220px)); justify-content: start; }
.cmd-topo-wrap { overflow-x: auto; padding-bottom: var(--sp-2); margin: 0 calc(-1 * var(--sp-1)); }
.cmd-topo-board { display: flex; gap: var(--sp-3); align-items: flex-start; padding: 0 var(--sp-1) var(--sp-1); min-width: min-content; }
.cmd-topo-col {
  flex: 0 0 236px;
  display: flex;
  flex-direction: column;
  gap: var(--sp-2);
  background: var(--bg-panel);
  border: 1px solid var(--border);
  border-radius: var(--r-md);
  padding: var(--sp-3);
  box-shadow: var(--shadow-rest);
}
.cmd-topo-col.is-offline { opacity: 0.74; }
.cmd-topo-col-head {
  display: flex; align-items: center; gap: var(--sp-2);
  background: none; border: none; width: 100%; padding: 0 0 var(--sp-2);
  border-bottom: 1px solid var(--border-subtle);
  color: var(--text); font-family: inherit; font-size: var(--fs-100); font-weight: 600; cursor: pointer; text-align: left;
}
.cmd-topo-col-head:hover .cmd-topo-col-name { color: var(--accent); }
.cmd-topo-col-name { overflow: hidden; text-overflow: ellipsis; white-space: nowrap; min-width: 0; flex: 1; }
.cmd-topo-maint { flex: none; }
.cmd-topo-cap { display: flex; flex-direction: column; gap: 5px; }
.cmd-topo-metric { display: grid; grid-template-columns: 34px 1fr; align-items: center; gap: var(--sp-2); }
.cmd-topo-metric .meter { grid-column: 1 / -1; margin: 0; }
.cmd-topo-metric-l { font-size: var(--fs-50); letter-spacing: var(--ls-caps); color: var(--text-muted); }
.cmd-topo-metric-v { font-size: var(--fs-75); color: var(--text); font-variant-numeric: tabular-nums; text-align: right; }
.cmd-topo-lane-head { font-size: var(--fs-50); letter-spacing: var(--ls-caps); color: var(--text-muted); margin-top: var(--sp-1); }
.cmd-topo-lane { display: flex; flex-direction: column; gap: 5px; }
.cmd-topo-empty { font-size: var(--fs-75); color: var(--text-dim); padding: var(--sp-2); text-align: center; border: 1px dashed var(--border); border-radius: var(--r-sm); }
.cmd-topo-vm {
  display: flex; align-items: center; gap: var(--sp-2);
  width: 100%; text-align: left; cursor: pointer;
  background: var(--bg-inset); border: 1px solid var(--border);
  border-left: 3px solid var(--grey);
  border-radius: var(--r-sm); padding: 5px var(--sp-2);
  color: var(--text); font-family: inherit; font-size: var(--fs-75);
  transition: border-color var(--dur-2) var(--ease-standard), background-color var(--dur-2) var(--ease-standard);
}
.cmd-topo-vm:hover { background: var(--bg-raised); border-color: var(--border-strong); }
.cmd-topo-vm-ok { border-left-color: var(--ok); }
.cmd-topo-vm-warn { border-left-color: var(--warn); }
.cmd-topo-vm-err { border-left-color: var(--err); }
.cmd-topo-vm-muted { border-left-color: var(--grey); }
.cmd-topo-vm-unknown { border-left-color: var(--text-muted); border-left-style: dashed; }
.cmd-topo-vm-os { flex: none; display: inline-flex; color: var(--text-dim); }
.cmd-topo-vm-os svg { width: 14px; height: 14px; }
.cmd-topo-vm-name { overflow: hidden; text-overflow: ellipsis; white-space: nowrap; min-width: 0; flex: 1; }
.cmd-topo-vm-rel { flex: none; font-size: var(--fs-50); color: var(--text-muted); font-variant-numeric: tabular-nums; }
.cmd-topo-foot { margin-top: var(--sp-3); }

/* Segmented composition bar. .meter and .rbar-track support exactly ONE fill; a
   VM-state mix needs N, and the mix IS the fact — "12 running / 3 stopped / 4 we
   cannot see" is a different statement from one percentage. Widths are set
   inline, the one inline style the CSP allows, exactly as .meter-fill does. */
.cmd-seg {
  display: flex;
  height: 10px;
  border-radius: var(--r-pill);
  overflow: hidden;
  background: var(--bg-inset);
  border: 1px solid var(--border);
}
.cmd-seg-part { height: 100%; min-width: 0; transition: width var(--dur-2) var(--ease-standard); }
.cmd-seg-ok { background: var(--ok); }
.cmd-seg-warn { background: var(--warn); }
.cmd-seg-err { background: var(--err); }
.cmd-seg-muted { background: var(--grey); }
/* "unknown" is HATCHED, never a solid colour: a VM on a host we cannot reach has
   no known power state, and a solid block would read as one more state. */
.cmd-seg-unknown {
  background: repeating-linear-gradient(45deg, var(--warn-tint) 0 4px, var(--bg-inset) 4px 8px);
}
.cmd-seg-legend {
  display: flex;
  flex-wrap: wrap;
  gap: var(--sp-1) var(--sp-3);
  margin-top: var(--sp-2);
  font-size: var(--fs-75);
  color: var(--text-dim);
}
.cmd-seg-key { display: inline-flex; align-items: center; gap: 6px; }
.cmd-seg-swatch { width: 9px; height: 9px; border-radius: 2px; flex: 0 0 auto; }

/* ===================================================================== *
 *  Host → Configure → Networking: switch-centric layout (.hnet-*)
 *  Networks nested under their switch, uplink in the header, service
 *  adapters on their network; physical adapters as cards with usage.
 * ===================================================================== */

/* Loading state — the networking feed is a separate ~30s lane, so the first
 * visit shows this: a live spinner plus shimmer placeholders shaped like the
 * real layout, so the wait reads as in-progress, never stuck or empty. */
.hnet-loading {
  display: flex;
  flex-direction: column;
  gap: var(--sp-4);
}
.hnet-loading-head {
  display: flex;
  align-items: center;
  gap: var(--sp-3);
  color: var(--text-dim);
  font-size: var(--fs-100);
}
.hnet-loading-msg {
  letter-spacing: 0.01em;
}
.hnet-loading-stats {
  display: grid;
  grid-template-columns: repeat(4, 1fr);
  gap: var(--sp-3);
}
.hnet-sk-stat {
  height: 76px;
}
.hnet-loading-cards {
  display: flex;
  flex-direction: column;
  gap: var(--sp-3);
}
.hnet-sk-card {
  height: 132px;
  border-radius: var(--r-card);
}
@media (max-width: 720px) {
  .hnet-loading-stats {
    grid-template-columns: repeat(2, 1fr);
  }
}

/* View toggle — List / Topology segmented control */
.hnet-toolbar {
  display: flex;
  align-items: center;
  margin-bottom: var(--sp-4);
}
.hnet-toolbar .spacer {
  flex: 1;
}
.hnet-viewtoggle {
  display: inline-flex;
  gap: var(--sp-px);
  padding: var(--sp-px);
  background: var(--bg-inset);
  border: 1px solid var(--border);
  border-radius: var(--r-card);
}
.hnet-vt {
  display: inline-flex;
  align-items: center;
  gap: 6px;
  padding: var(--sp-1) var(--sp-3);
  font: inherit;
  font-size: var(--fs-75);
  font-weight: var(--fw-medium);
  color: var(--text-muted);
  background: transparent;
  border: 0;
  border-radius: calc(var(--r-card) - 3px);
  cursor: pointer;
  transition: background var(--dur-2), color var(--dur-2);
}
.hnet-vt:hover {
  color: var(--text);
}
.hnet-vt.on {
  background: var(--bg-raised);
  color: var(--text);
  box-shadow: var(--shadow-xs);
}
.hnet-vt-ico {
  font-size: var(--fs-75);
  line-height: 1;
}

/* Topology map — path-flow lanes with SVG connectors */
.hnet-topo {
  position: relative;
  overflow-x: auto;
  padding: var(--sp-2) 0 var(--sp-4);
}
.hnet-topo-links {
  position: absolute;
  inset: 0;
  width: 100%;
  height: 100%;
  z-index: 0;
  pointer-events: none;
  overflow: visible;
}
.hnet-topo-grid {
  position: relative;
  z-index: 1;
  display: flex;
  align-items: center;
  min-width: 760px;
}
.hnet-topo-bands {
  flex: 1;
  display: flex;
  flex-direction: column;
  gap: var(--sp-5);
}
.hnet-topo-band {
  display: grid;
  grid-template-columns: minmax(190px, 1.2fr) minmax(170px, 1fr) minmax(180px, 1fr);
  align-items: center;
  column-gap: 60px;
}
.hnet-topo-col {
  display: flex;
  flex-direction: column;
  gap: var(--sp-3);
}
.hnet-topo-phys {
  flex: none;
  width: 170px;
  margin-left: 60px;
  display: flex;
  align-items: center;
}
.hnet-topo-empty {
  margin: 0;
}

.hnet-node {
  position: relative;
  display: flex;
  align-items: center;
  gap: var(--sp-3);
  background: var(--bg-raised);
  border: 1px solid var(--border);
  border-radius: var(--r-card);
  padding: 10px var(--sp-3);
  box-shadow: var(--shadow-sm);
  transition: border-color var(--dur-2), box-shadow var(--dur-2), transform var(--dur-2), opacity var(--dur-2);
}
.hnet-node:hover {
  border-color: var(--accent);
  transform: translateY(-1px);
}
.hnet-node.hl {
  border-color: var(--accent);
  box-shadow: 0 0 0 3px var(--accent-tint), var(--shadow-sm);
}
.hnet-node.dim {
  opacity: 0.4;
}
.hnet-node-main {
  flex: 1;
  min-width: 0;
}
.hnet-node-title {
  font-weight: 600;
  font-size: var(--fs-75);
  line-height: 1.25;
  color: var(--text);
}
.hnet-node-sub {
  font-size: var(--fs-50);
  color: var(--text-muted);
  margin-top: var(--sp-px);
}
.hnet-node-lead {
  width: 22px;
  height: 22px;
  flex: none;
  display: grid;
  place-items: center;
  font-size: var(--fs-50);
  font-weight: 700;
  border-radius: 7px;
  background: var(--accent-tint);
  color: var(--accent);
}
.hnet-node-lead.sw {
  background: var(--accent);
  color: var(--accent-fg);
}
.hnet-node-lead.ico {
  background: var(--bg-inset);
  color: var(--text-muted);
}
/* #35 — the node/badge/toggle marks are now inline SVG (nodeIconSvg) rather than
   raw unicode glyphs; size them to their centered boxes, tinting via currentColor. */
.hnet-node-lead svg { width: 14px; height: 14px; }
.hnet-sw-badge svg { width: 18px; height: 18px; }
.hnet-uplink-ico svg { width: 14px; height: 14px; vertical-align: middle; }
.hnet-vt-ico svg { width: 14px; height: 14px; display: block; }
.hnet-node-badge,
.hnet-node .badge {
  margin-left: auto;
  flex: none;
  white-space: nowrap;
}
.hnet-node-badge {
  font-size: var(--fs-50);
  font-weight: 700;
  letter-spacing: 0.02em;

  padding: var(--sp-px) var(--sp-2);
  border-radius: 6px;
  background: var(--bg-inset);
  color: var(--text-muted);
}
.hnet-node.phys {
  background: transparent;
  border-style: dashed;
  flex-direction: column;
  align-items: flex-start;
  gap: var(--sp-px);
}

/* Per-uplink upstream lane — the real switch/port each uplink's cable lands on,
   learned from LLDP/CDP. Present only when at least one uplink advertised a
   neighbor, so the lab (no advertisements) keeps the original three-lane shape. */
.hnet-topo-band.has-upstream {
  grid-template-columns: minmax(160px, 1.1fr) minmax(145px, 0.85fr) minmax(145px, 0.85fr) minmax(170px, 1fr);
  column-gap: 40px;
}
.hnet-topo-col.upstream {
  gap: var(--sp-3);
}
.hnet-topo-upstream-gap {
  min-height: 46px;
}
.hnet-node.upstream-nb {
  border-style: dashed;
}
.hnet-node.upstream-nb .hnet-node-lead.ico {
  background: var(--accent-tint);
  color: var(--accent);
}
.hnet-node-badge.nb-src {
  margin-left: auto;
  background: var(--accent-tint);
  color: var(--accent);
}
/* A team cabled across two different upstream switches, flagged on the switch node. */
.hnet-topo-teamsplit {
  margin-top: var(--sp-px);
  font-size: var(--fs-50);
  font-weight: 600;
  color: var(--warn);
}

.hnet-topo-link {
  fill: none;
  stroke: var(--border-strong);
  stroke-width: 1.6;
  transition: stroke var(--dur-2), stroke-width var(--dur-2), opacity var(--dur-2);
}
.hnet-topo-link.hl {
  stroke: var(--accent);
  stroke-width: 2.4;
}
.hnet-topo-link.dim {
  opacity: 0.25;
}
@media (prefers-reduced-motion: reduce) {
  .hnet-node,
  .hnet-topo-link {
    transition: none;
  }
}

/* (.hnet-summary / .hnet-stat removed — the host networking summary is now the
   shared command hero, hnSummary() in hostnetworking.js, reusing .cmd-hero*.) */

.hnet-switch {
  background: var(--bg-raised);
  border: 1px solid var(--border);
  border-radius: var(--r-card);
  margin-bottom: var(--sp-4);
  overflow: hidden;
}
.hnet-switch.system { border-style: dashed; }
.hnet-sw-head {
  display: flex;
  align-items: center;
  gap: var(--sp-3);
  flex-wrap: wrap;
  padding: var(--sp-3) var(--sp-4);
  border-bottom: var(--sp-px) solid var(--border);
}
.hnet-sw-head .spacer { flex: 1; min-width: 8px; }
.hnet-sw-id { display: flex; align-items: center; gap: var(--sp-3); min-width: 0; }
.hnet-sw-badge {
  flex: none;
  width: 34px; height: 34px;
  display: grid; place-items: center;
  border-radius: var(--r-sm);
  background: var(--accent-tint);
  color: var(--accent);
  border: 1px solid var(--border);
  font-size: var(--fs-200);
}
.hnet-sw-name {
  display: flex; align-items: center; gap: var(--sp-2);
  font-size: var(--fs-300);
  font-weight: var(--fw-semibold);
}
.hnet-sw-sub {
  margin-top: var(--sp-px);
  font-size: var(--fs-50);
  color: var(--text-muted);
}

.hnet-uplink {
  display: inline-flex;
  align-items: center;
  gap: var(--sp-2);
  padding: var(--sp-1) var(--sp-2);
  background: var(--bg-inset);
  border: 1px solid var(--border-strong);
  border-radius: var(--r-sm);
}
.hnet-uplink-ico { color: var(--text-muted); }
.hnet-uplink .u-meta { display: flex; flex-direction: column; line-height: 1.25; }
.hnet-uplink .u-name { font-weight: var(--fw-semibold); font-size: var(--fs-75); }
.hnet-uplink .u-dev { font-family: var(--mono); font-size: var(--fs-50); color: var(--text-muted); }

.hnet-pgs { padding: var(--sp-2) var(--sp-3) var(--sp-3); }
.hnet-pg-sys:hover { background: transparent; }
.hnet-pg-name {
  display: flex; align-items: center; gap: var(--sp-2);
  min-width: 0;
  font-weight: var(--fw-semibold);
}
.hnet-pg-lead {
  flex: none;
  width: 8px; height: 8px;
  border-radius: 50%;
  background: var(--accent);
  box-shadow: 0 0 0 3px var(--accent-tint);
}
.hnet-pg-lead.sys { background: var(--text-muted); box-shadow: 0 0 0 3px var(--bg-inset); }
.hnet-tag {
  justify-self: start;
  font-family: var(--mono);
  font-size: var(--fs-50);
  font-weight: var(--fw-semibold);
  color: var(--accent);
  background: var(--accent-tint);
  border: 1px solid var(--border);
  border-radius: var(--r-pill);
  padding: var(--sp-px) var(--sp-2);
  white-space: nowrap;
}
.hnet-attach { display: flex; flex-direction: column; gap: var(--sp-1); min-width: 0; font-size: var(--fs-75); }
.hnet-attach .none { color: var(--text-muted); }
.hnet-svc { display: inline-flex; align-items: center; gap: var(--sp-2); color: var(--text-dim); min-width: 0; }
.hnet-svc .n { font-weight: var(--fw-medium); }
.hnet-svc .ip { font-family: var(--mono); color: var(--text-muted); }

.hnet-addpg {
  display: flex; align-items: center; gap: var(--sp-2);
  width: 100%;
  margin-top: var(--sp-2);
  padding: var(--sp-3);
  border: 1px dashed var(--border-strong);
  border-radius: var(--r-sm);
  background: transparent;
  color: var(--text-dim);
  font-size: var(--fs-75);
  font-weight: var(--fw-semibold);
  text-align: left;
}
.hnet-addpg:hover { border-color: var(--accent); color: var(--accent); background: var(--accent-tint); }
.hnet-addpg-plus {
  flex: none;
  width: 20px; height: 20px;
  display: grid; place-items: center;
  border-radius: var(--r-sm);
  background: var(--bg-inset);
  color: var(--accent);
  font-weight: var(--fw-bold);
}
.hnet-addpg:hover .hnet-addpg-plus { background: var(--accent); color: var(--accent-fg, #fff); }
.hnet-sysnote { padding: var(--sp-2) var(--sp-3) 0; font-size: var(--fs-50); color: var(--text-muted); }
/* Read-only marker in a distributed network's action cell (managed by SimpleVirt-Command). */
.hnet-managed-note { font-size: var(--fs-50); color: var(--text-muted); font-style: italic; white-space: nowrap; }

.hnet-nic-grid {
  display: grid;
  grid-template-columns: repeat(auto-fill, minmax(250px, 1fr));
  gap: var(--sp-3);
}
.hnet-nic {
  display: flex; flex-direction: column; gap: var(--sp-2);
  background: var(--bg-raised);
  border: 1px solid var(--border);
  border-radius: var(--r-card);
  padding: var(--sp-3) var(--sp-4);
}
.hnet-nic.free { border-style: dashed; border-color: var(--border-strong); }
.hnet-nic-top { display: flex; align-items: center; gap: var(--sp-2); }
.hnet-nic-top .n { font-weight: var(--fw-semibold); }
.hnet-nic-top .d { font-family: var(--mono); font-size: var(--fs-50); color: var(--text-muted); }
.hnet-nic-top .spacer { flex: 1; }
.hnet-nic-meta { display: flex; flex-wrap: wrap; gap: var(--sp-1) var(--sp-4); font-size: var(--fs-50); color: var(--text-dim); }
.hnet-nic-meta .lbl { color: var(--text-muted); }
.hnet-nic-meta .mono { font-family: var(--mono); }
.hnet-nic-use {
  display: flex; align-items: center; gap: var(--sp-2); flex-wrap: wrap;
  border-top: var(--sp-px) solid var(--border);
  padding-top: var(--sp-2);
  font-size: var(--fs-50);
  color: var(--text-dim);
}
.hnet-nic-use.free-txt { color: var(--text-muted); }
.hnet-nic-use .arrow { color: var(--text-muted); }

.hnet-modal-preview {
  display: flex; gap: var(--sp-2);
  padding: var(--sp-3);
  background: var(--bg-inset);
  border: 1px solid var(--border);
  border-radius: var(--r-sm);
  font-size: var(--fs-75);
  color: var(--text-dim);
}

@media (max-width: 680px) {
  .hnet-tag, .hnet-attach { grid-column: 1 / -1; justify-self: start; }
}

/* ---------- global quick-find palette (⌘K / Ctrl-K) --------------------- */
/* A jump-to-anything command palette: floats near the top over a dimmed scrim,
   above modals, theme-aware via tokens. Behaviour lives in quickfind.js. */
.qf-scrim {
  position: fixed;
  inset: 0;
  z-index: 400;
  display: flex;
  justify-content: center;
  align-items: flex-start;
  padding: 12vh var(--sp-4) var(--sp-4);
  background: rgba(4, 6, 14, 0.55);
  backdrop-filter: blur(2px);
}
.qf-panel {
  width: min(640px, 100%);
  max-height: 68vh;
  display: flex;
  flex-direction: column;
  background: var(--bg-raised);
  border: 1px solid var(--border-strong);
  border-radius: var(--r-lg);
  box-shadow: 0 24px 70px rgba(0, 0, 0, 0.55);
  overflow: hidden;
}
.qf-head {
  display: flex;
  align-items: center;
  gap: 10px;
  padding: var(--sp-3) 14px;
  border-bottom: var(--sp-px) solid var(--border);
}
.qf-head-icon { color: var(--text-muted); display: flex; }
.qf-head-icon svg { width: 17px; height: 17px; }
.qf-input {
  flex: 1 1 auto;
  min-width: 0;
  border: 0;
  background: transparent;
  color: var(--text);
  font-size: var(--fs-200);
  font-family: var(--font);
  outline: none;
}
.qf-input::placeholder { color: var(--text-muted); }
.qf-kbd {
  flex: 0 0 auto;
  font-size: var(--fs-50);
  color: var(--text-muted);
  border: 1px solid var(--border-strong);
  border-radius: var(--r-xs);
  padding: var(--sp-px) 6px;
}
.qf-list { overflow-y: auto; padding: 6px; }
.qf-none { padding: 22px 14px; text-align: center; color: var(--text-muted); font-size: var(--fs-100); }
.qf-row {
  display: flex;
  align-items: center;
  gap: 10px;
  padding: var(--sp-2) 10px;
  border-radius: var(--r-sm);
  cursor: pointer;
  color: var(--text);
}
.qf-row.active { background: var(--accent-tint); }
.qf-row-icon { display: flex; flex: 0 0 auto; color: var(--text-muted); }
.qf-row-icon svg { width: 16px; height: 16px; }
.qf-row.active .qf-row-icon { color: var(--accent-hover); }
/* Category tints so the eye sorts results by what a thing IS, not just its label. */
.qf-cat-compute,
.qf-cat-storage,
.qf-cat-net,
.qf-cat-k8s { color: var(--cat); }
.qf-row-name { flex: 0 1 auto; font-weight: 500; white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }
.qf-row-sub { flex: 1 1 auto; color: var(--text-muted); font-size: var(--fs-50); white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }
.qf-row-meta { flex: 0 0 auto; color: var(--warn); font-size: var(--fs-50); text-transform: capitalize; }
.qf-foot {
  border-top: var(--sp-px) solid var(--border);
  padding: var(--sp-2) 14px;
  display: flex;
  justify-content: flex-end;
}
.qf-foot-hint { font-size: var(--fs-50); color: var(--text-muted); }

/* ---------- top-bar search pill ----------------------------------------- */
.cmd-search-btn {
  display: flex;
  align-items: center;
  gap: var(--sp-2);
  height: 30px;
  padding: 0 var(--sp-2) 0 10px;
  background: var(--topbar-hover);
  border: 1px solid var(--topbar-border);
  border-radius: var(--r-pill);
  color: var(--topbar-text-dim);
  cursor: pointer;
  font-family: var(--font);
  font-size: var(--fs-75);
}
.cmd-search-btn:hover { color: var(--topbar-text); border-color: var(--topbar-brand); }
.cmd-search-ic { flex: 0 0 auto; }
.cmd-search-label { flex: 0 0 auto; }
.cmd-search-kbd {
  flex: 0 0 auto;
  font-size: var(--fs-50);
  color: var(--topbar-text-dim);
  border: 1px solid var(--topbar-border);
  border-radius: var(--r-xs);
  padding: var(--sp-px) var(--sp-1);
}
@media (max-width: 720px) {
  .cmd-search-label, .cmd-search-kbd { display: none; }
  .cmd-search-btn { padding: 0 var(--sp-2); }
}

/* #42 — the keyboard cheat-sheet (opened with "?"). Two columns of grouped rows;
   each shortcut shows its real key chips beside a plain description. */
.cmd-kbd-grid {
  display: grid;
  grid-template-columns: repeat(2, minmax(0, 1fr));
  gap: var(--sp-4) var(--sp-5);
}
.cmd-kbd-grouptitle {
  margin: 0 0 var(--sp-2);
  font-size: var(--fs-50);
  font-weight: var(--fw-bold);
  letter-spacing: 0.04em;

  color: var(--text-dim);
}
.cmd-kbd-row {
  display: flex;
  align-items: baseline;
  gap: var(--sp-3);
  padding: var(--sp-1) 0;
}
.cmd-kbd-keys { flex: 0 0 auto; display: inline-flex; align-items: center; gap: 3px; min-width: 96px; }
.cmd-kbd-plus { color: var(--text-muted); font-size: var(--fs-50); }
.cmd-kbd {
  font-family: var(--mono, ui-monospace, monospace);
  font-size: var(--fs-50);
  color: var(--text);
  background: var(--bg-raised);
  border: 1px solid var(--border-strong);
  border-radius: var(--r-xs);
  padding: var(--sp-px) 6px;
  line-height: 1.4;
}
.cmd-kbd-desc { color: var(--text-dim); font-size: var(--fs-75); }
@media (max-width: 640px) {
  .cmd-kbd-grid { grid-template-columns: minmax(0, 1fr); }
}

/* ---------- empty / error / loading placeholders ------------------------ */
/* emptyState and errorState MUST read differently (see parts.js): one is a
   settled empty, one is a failed read, and the operator has to tell them apart
   at a glance or they stop trusting any empty panel. */
.cmd-state {
  display: flex;
  flex-direction: column;
  align-items: center;
  text-align: center;
  /* A composed rhythm (big beat under the icon, tight title→body, clear gap before
     the action) rather than one even gap that reads templated. Per-child margins. */
  gap: 0;
  padding: var(--sp-12) var(--sp-5);
  color: var(--text-dim);
}
/* The icon sits in a soft framed plate so the state reads as a composed element,
   not a lonely glyph floating in whitespace. */
.cmd-state-icon {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  width: 56px;
  height: 56px;
  border-radius: var(--r-pill);
  background: var(--bg-inset);
  border: 1px solid var(--border);
  box-shadow: var(--shadow-inset);
  margin-bottom: var(--sp-5);
}
.cmd-state-icon svg { width: 26px; height: 26px; }
.cmd-state-empty .cmd-state-icon { color: var(--text-muted); }
.cmd-state-error .cmd-state-icon {
  color: var(--warn);
  background: var(--warn-tint);
  border-color: color-mix(in srgb, var(--warn) 30%, transparent);
}
.cmd-state-title { font-size: var(--fs-300); font-weight: 600; color: var(--text); margin-bottom: var(--sp-2); }
.cmd-state-msg { font-size: var(--fs-75); line-height: var(--lh-normal); color: var(--text-dim); max-width: 440px; margin-bottom: var(--sp-5); }
.cmd-state-action { margin-top: 0; }
.cmd-state-buttons { display: inline-flex; gap: var(--sp-2); flex-wrap: wrap; justify-content: center; }

/* Loading rows: a short stack of shimmer bars at row height, so a table holds its
   shape while it loads instead of collapsing and jumping. Reuses .skel-bar. */
.cmd-skel-rows { display: flex; flex-direction: column; gap: 10px; padding: var(--sp-2) var(--sp-px); }
.skel-row { width: 100%; height: 14px; }

/* About panel — the controller's build provenance and authorship. A narrow plate:
   it is a handful of facts, not a table, so it should not stretch to full width. */
.cmd-about { max-width: 560px; margin-inline: auto; }
.cmd-about-hero { display: flex; align-items: center; gap: var(--sp-3); margin-bottom: var(--sp-5); }
.cmd-about-logo { width: 44px; height: 44px; flex: 0 0 auto; }
.cmd-about-name { margin: 0; font-size: var(--fs-400); font-weight: var(--fw-semibold); color: var(--text); }
.cmd-about-tag { margin: var(--sp-px) 0 0; font-size: var(--fs-75); color: var(--text-muted); }
.cmd-about-author { margin: var(--sp-5) 0 0; padding-top: var(--sp-4); border-top: var(--sp-px) solid var(--border); font-size: var(--fs-75); color: var(--text-dim); }

/* ---------- SimpleVirt HCI management page (hciview.js) ---------- */
.cmd-hci { display: flex; flex-direction: column; gap: 18px; }
.cmd-hci-empty { text-align: center; padding: 32px 16px; display: flex; flex-direction: column; gap: 10px; align-items: center; }
.cmd-hci-empty h3 { margin: 0; }
.cmd-hci-head { display: flex; align-items: center; gap: 12px; }
.cmd-hci-badge { font-weight: 700; font-size: var(--fs-50); letter-spacing: .04em; border-radius: var(--r-sm); padding: 4px 12px; }
.cmd-hci-fsid { font-family: ui-monospace, monospace; font-size: var(--fs-50); color: var(--text-muted); }
.cmd-hci-stats { display: grid; grid-template-columns: repeat(auto-fill, minmax(170px, 210px)); gap: 12px; }
.cmd-hci-graphs { display: grid; grid-template-columns: repeat(2, minmax(0, 360px)); gap: 12px; }
.cmd-hci-graphs { grid-template-columns: repeat(2, 1fr); }
.cmd-hci-stat, .cmd-hci-metric { background: var(--bg-inset); border: 1px solid var(--border); border-radius: var(--r-md); padding: var(--sp-3) var(--sp-4); }
.cmd-hci-stat-k { font-size: var(--fs-25); font-weight: 600; letter-spacing: var(--ls-caps); color: var(--text-muted); }
/* HCI tile figures read as the same engineered numerals as every other hero number. */
.cmd-hci-stat-v { font-family: var(--font-display); font-size: var(--fs-600); font-weight: 600; letter-spacing: var(--ls-tight); font-variant-numeric: tabular-nums; margin-top: var(--sp-1); color: var(--text); }
.cmd-hci-stat-s { font-size: var(--fs-50); color: var(--text-dim); margin-top: var(--sp-px); }
/* Recessed HCI tiles read as genuinely carved-in, not merely darker (dark only). */
:root:not([data-theme="light"]) .cmd-hci-stat,
:root:not([data-theme="light"]) .cmd-hci-metric,
:root:not([data-theme="light"]) .cmd-hci-cap,
:root:not([data-theme="light"]) .cmd-hci-rec { box-shadow: var(--shadow-inset); }
.cmd-hci-spark { display: block; width: 100%; height: 46px; margin-top: 8px; }
.cmd-hci-block { display: flex; flex-direction: column; gap: 8px; }
.cmd-hci-caplabel { font-size: var(--fs-50); color: var(--text-dim); }
.cmd-hci-bar { height: 9px; border-radius: var(--r-sm); background: var(--bg-inset); overflow: hidden; }
.cmd-hci-bar i { display: block; height: 100%; background: var(--grey); transition: background var(--dur-3) var(--ease-standard); }
.cmd-hci-pools h4 { margin: 0 0 8px; }
.cmd-hci-table { width: 100%; border-collapse: collapse; font-size: var(--fs-75); }
.cmd-hci-table th { text-align: left; font-size: var(--fs-25); letter-spacing: .05em; color: var(--text-muted); padding: 8px 12px; border-bottom: 1px solid var(--border); }
.cmd-hci-table td { padding: 9px 12px; border-bottom: 1px solid var(--border); }
.cmd-hci-actions { display: flex; flex-direction: column; gap: 10px; border-top: 1px solid var(--border); padding-top: 16px; }
.cmd-hci-danger { display: flex; flex-direction: column; gap: 8px; }
.cmd-hci-confirm { display: flex; gap: 8px; }
.cmd-hci-confirm input { flex: 0 0 160px; }
.cmd-hci-danger-toggle { background: none; border: none; color: var(--err); font-size: var(--fs-75); font-weight: 600; cursor: pointer; padding: 4px 0; text-align: left; align-self: flex-start; }
.cmd-hci-danger-toggle:hover { text-decoration: underline; }
.cmd-hci-danger-body { display: flex; flex-direction: column; gap: 8px; border: 1px solid var(--err); border-radius: var(--r-md); padding: 12px 14px; margin-top: 6px; background: var(--err-tint); }
.cmd-hci-teardown { display: flex; flex-direction: column; gap: 6px; margin-top: 10px; }
.cmd-hci-td-host { border: 1px solid var(--border); border-radius: var(--r-md); padding: 8px 10px; }
.cmd-hci-td-name { font-weight: 600; font-size: var(--fs-75); margin-bottom: 4px; }
.cmd-hci-td-phase { font-size: var(--fs-75); }
.cmd-hci-td-done { color: var(--ok); font-weight: 600; font-size: var(--fs-75); margin-top: 6px; }

/* ---- SimpleVirt HCI console — sections (Overview, Hosts & Disks) ---- */
/* State chip: the human health headline, driven (like the badge) by the computed
   rollup — never a raw st.health passthrough. */
.cmd-hci-chip { font-size: var(--fs-75); font-weight: 600; padding: 4px 12px; border-radius: 999px; border: 1px solid transparent; }
.cmd-hci-chip.is-ok { color: var(--ok); background: var(--ok-tint); border-color: color-mix(in srgb, var(--ok) 30%, transparent); }
.cmd-hci-chip.is-warn { color: var(--warn); background: var(--warn-tint); border-color: color-mix(in srgb, var(--warn) 30%, transparent); }
.cmd-hci-chip.is-err { color: var(--err); background: var(--err-tint); border-color: color-mix(in srgb, var(--err) 30%, transparent); }

/* Resiliency line — human language, read from the actual pool.size / failure domain. */
.cmd-hci-resil { display: flex; align-items: center; gap: 8px; font-size: var(--fs-75); color: var(--text-dim); }
.cmd-hci-resil.is-warn { color: var(--warn); font-weight: 600; }

/* Capacity headline: usable is the big number, raw is the caption. */
.cmd-hci-cap { background: var(--bg-inset); border: 1px solid var(--border); border-radius: var(--r-md); padding: 14px 16px; display: flex; flex-direction: column; gap: 7px; }
.cmd-hci-cap-big { font-size: 26px; font-weight: 700; color: var(--text); }

/* Fill-state modifiers for the shared cmd-hci-bar meter (default fill is --ok). */
.cmd-hci-bar i.cmd-hci-fill-warn { background: var(--warn); }
.cmd-hci-bar i.cmd-hci-fill-crit { background: var(--err); }

/* Recovery / resync strip. */
.cmd-hci-rec { background: var(--bg-inset); border: 1px solid var(--border); border-radius: var(--r-md); padding: 12px 15px; display: flex; flex-direction: column; gap: 7px; }
.cmd-hci-rec-line { font-size: var(--fs-75); }

/* Hosts & Disks. */
.cmd-hci-hd-head { display: flex; align-items: center; justify-content: space-between; gap: 12px; }
.cmd-hci-hd-head h4 { margin: 0; }
.cmd-hci-hd-summary { margin: 0; }
.cmd-hci-hosts { display: flex; flex-direction: column; gap: 12px; }
.cmd-hci-osdgroup { border: 1px solid var(--border); border-radius: var(--r-md); background: var(--bg-inset); overflow-x: auto; }
.cmd-hci-osdhead { display: flex; align-items: center; gap: 8px; padding: 10px 12px; border-bottom: 1px solid var(--border); }
.cmd-hci-osdhost { font-weight: 600; font-size: var(--fs-75); }
.cmd-hci-osdsub { margin-left: auto; font-size: var(--fs-50); color: var(--text-muted); }
.cmd-hci-osdrows { min-width: 620px; }
.cmd-hci-osdrow { display: grid; grid-template-columns: 92px minmax(90px, 1fr) 54px 108px 64px minmax(140px, 200px) auto; align-items: center; gap: 10px; padding: 7px 12px; border-top: 1px solid var(--border); font-size: var(--fs-75); }
.cmd-hci-osdact { text-align: right; }
.cmd-hci-osdrow.is-head { border-top: none; color: var(--text-muted); font-size: var(--fs-25); letter-spacing: .05em; font-weight: 600; }
.cmd-hci-osddev { overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.cmd-hci-badges { display: flex; gap: 4px; flex-wrap: wrap; }
.cmd-hci-classbadge { display: inline-block; font-size: var(--fs-10); font-weight: 700; letter-spacing: .05em; padding: 2px 7px; border-radius: var(--r-sm); border: 1px solid var(--border); color: var(--text-muted); background: var(--bg-raised); }
.cmd-hci-classbadge.is-ssd { color: var(--accent); border-color: color-mix(in srgb, var(--accent) 35%, transparent); background: var(--accent-tint); }
.cmd-hci-util { display: flex; align-items: center; gap: 8px; min-width: 0; }
.cmd-hci-utilbar { flex: 1; min-width: 40px; }
.cmd-hci-utiltext { font-size: var(--fs-25); color: var(--text-dim); white-space: nowrap; }

/* Coming-soon placeholder (and the Advanced fsid stub). */
.cmd-hci-soon { text-align: center; padding: 44px 16px; display: flex; flex-direction: column; gap: 8px; align-items: center; }
.cmd-hci-soon-title { margin: 0; font-size: var(--fs-200); font-weight: 600; color: var(--text); }
.cmd-hci-kv { display: flex; align-items: baseline; gap: 10px; }

/* Capacity headroom hint — a subtle sub-caption under the raw/replication caption. */
.cmd-hci-caphint { font-size: var(--fs-50); color: var(--text-muted); }

/* ---- Datastores section (a card per user-facing datastore) ---- */
.cmd-hci-dscards { display: flex; flex-direction: column; gap: 14px; }
/* The datastore card is the flagship HCI surface — it LIFTS like other primary
   cards (not recessed), so the console's main cards share one depth language. */
.cmd-hci-dscard { border: 1px solid var(--border); border-radius: var(--r-md); background: var(--bg-panel); padding: var(--sp-4); display: flex; flex-direction: column; gap: var(--sp-3); }
:root:not([data-theme="light"]) .cmd-hci-dscard { border-color: var(--border-panel); box-shadow: var(--shadow-rest); }
.cmd-hci-dshead { display: flex; align-items: center; gap: 10px; }
.cmd-hci-dsname { font-weight: 600; font-size: var(--fs-200); color: var(--text); margin-right: auto; }
.cmd-hci-dscap { display: flex; flex-direction: column; gap: 6px; }
.cmd-hci-dsline { display: flex; align-items: baseline; gap: 12px; font-size: var(--fs-75); }
.cmd-hci-dslabel { flex: 0 0 96px; font-size: var(--fs-25); font-weight: 600; letter-spacing: var(--ls-caps); color: var(--text-muted); }
.cmd-hci-dsval { display: flex; align-items: center; flex-wrap: wrap; gap: 8px; color: var(--text-dim); min-width: 0; }
.cmd-hci-dsnote { margin-left: 2px; }
.cmd-hci-dsactions { display: flex; flex-wrap: wrap; gap: 8px; border-top: 1px solid var(--border); padding-top: 12px; }

/* Collapsible "Technical details" expander (Ceph pool detail on a datastore card). */
.cmd-hci-expander { display: flex; flex-direction: column; gap: 8px; }
.cmd-hci-expander-toggle { background: none; border: none; color: var(--text-dim); font-size: var(--fs-75); font-weight: 600; cursor: pointer; padding: 2px 0; text-align: left; align-self: flex-start; }
.cmd-hci-expander-toggle:hover { color: var(--text); text-decoration: underline; }
.cmd-hci-expander-body { display: flex; flex-direction: column; gap: 8px; }

/* ---- Advanced (raw Ceph) section ---- */
.cmd-hci-adv-block { border: 1px solid var(--border); border-radius: var(--r-md); background: var(--bg-inset); padding: 13px 15px; display: flex; flex-direction: column; gap: 10px; }
.cmd-hci-adv-block h4 { margin: 0; }
.cmd-hci-adv-kv { display: flex; align-items: baseline; gap: 12px; font-size: var(--fs-75); }
.cmd-hci-adv-k { flex: 0 0 140px; font-size: var(--fs-25); font-weight: 600; letter-spacing: .05em; color: var(--text-muted); }
.cmd-hci-adv-v { color: var(--text); overflow-wrap: anywhere; min-width: 0; }
.cmd-hci-adv-tablewrap { overflow-x: auto; }

/* ---- SimpleVirt HCI preflight (readiness checks before provisioning) ---- */
.cmd-hci-preflight { display: flex; flex-direction: column; gap: 10px; }
.cmd-hci-pf-host { border: 1px solid var(--border); border-radius: var(--r-md); padding: 10px 12px; background: var(--bg-raised); }
.cmd-hci-pf-host.is-bad { border-color: var(--err); background: var(--err-tint); }
.cmd-hci-pf-head { display: flex; align-items: center; gap: 8px; margin-bottom: 8px; }
.cmd-hci-pf-badge { font-size: var(--fs-10); font-weight: 700; letter-spacing: 0.06em; padding: 2px 7px; border-radius: var(--r-sm); }
.cmd-hci-pf-badge.ok { color: var(--ok); background: var(--ok-tint); }
.cmd-hci-pf-badge.bad { color: var(--err); background: var(--err-tint); }
.cmd-hci-pf-name { font-weight: 600; font-size: var(--fs-75); }
.cmd-hci-pf-check { display: grid; grid-template-columns: 16px minmax(0, 190px) 1fr; align-items: baseline; gap: 8px; font-size: 12.5px; padding: 2px 0; }
.cmd-hci-pf-mark { color: var(--ok); font-weight: 700; text-align: center; }
.cmd-hci-pf-check.is-bad .cmd-hci-pf-mark { color: var(--err); }
.cmd-hci-pf-cname { color: var(--text); }
.cmd-hci-pf-check.is-bad .cmd-hci-pf-cname { font-weight: 600; }
.cmd-hci-pf-detail { color: var(--text-dim); overflow-wrap: anywhere; }
.cmd-hci-pf-check.is-bad .cmd-hci-pf-detail { color: var(--err); }
.cmd-hci-preflight-running { display: flex; align-items: center; gap: 10px; font-size: var(--fs-75); color: var(--text-dim); padding: 10px 0; }
.wizard-form-actions { display: flex; gap: 8px; margin-top: 12px; }
.cmd-spinner { width: 16px; height: 16px; border: 2px solid var(--border); border-top-color: var(--accent); border-radius: 50%; display: inline-block; flex: none; animation: cmd-spin 0.8s linear infinite; }
@media (prefers-reduced-motion: reduce) { .cmd-spinner { animation-duration: 2.4s; } }

/* ---- SimpleVirt HCI live provisioning / teardown ---- */
.cmd-hci-prov { display: flex; flex-direction: column; gap: 12px; }
.cmd-hci-prov-head { display: flex; align-items: center; gap: 10px; }
.cmd-hci-prov-title { margin: 0; font-size: var(--fs-300); font-weight: 600; }
.cmd-hci-td-current { display: flex; align-items: center; gap: 8px; font-size: var(--fs-75); color: var(--text-dim); padding: 2px 0; }

/* ---- SimpleVirt HCI: Data & Resync section ---- */
.cmd-hci-resync-idle { display: flex; align-items: flex-start; gap: 10px; background: var(--bg-inset); border: 1px solid var(--border); border-radius: var(--r-md); padding: 16px 18px; }
.cmd-hci-resync-idle .dot { margin-top: 5px; }
.cmd-hci-resync-idle-t { font-size: var(--fs-200); font-weight: 600; color: var(--text); }
.cmd-hci-resync-card { background: var(--bg-inset); border: 1px solid var(--border); border-radius: var(--r-md); padding: 16px 18px; display: flex; flex-direction: column; gap: 14px; }
.cmd-hci-resync-cardhead { display: flex; align-items: baseline; justify-content: space-between; gap: 12px; }
.cmd-hci-resync-title { font-size: var(--fs-300); font-weight: 600; color: var(--text); }
.cmd-hci-resync-pct { font-size: 26px; font-weight: 700; color: var(--accent); }
.cmd-hci-resync-bar { height: 13px; }
.cmd-hci-resync-bar i { background: var(--accent); }
.cmd-hci-resync-stats { display: grid; grid-template-columns: repeat(3, 1fr); gap: 12px; }
.cmd-hci-resync-states { display: flex; flex-direction: column; gap: 5px; }
.cmd-hci-resync-state { font-size: var(--fs-75); color: var(--text-dim); }

/* ---- SimpleVirt HCI: Health section ---- */
.cmd-hci-hz-summary { margin: 0; }
.cmd-hci-hz-groups { display: flex; flex-direction: column; gap: 14px; }
.cmd-hci-hz-group { border: 1px solid var(--border); border-radius: var(--r-md); background: var(--bg-inset); padding: 12px 15px; display: flex; flex-direction: column; gap: 6px; }
.cmd-hci-hz-grouphead { display: flex; align-items: center; gap: 8px; }
.cmd-hci-hz-grouphead h4 { margin: 0; font-size: var(--fs-100); }
.cmd-hci-hz-count { margin-left: auto; font-size: var(--fs-25); font-weight: 600; letter-spacing: .05em; color: var(--text-muted); }
.cmd-hci-hz-tech { font-family: ui-monospace, monospace; font-size: var(--fs-50); color: var(--text-muted); }

/* ---- SimpleVirt HCI: Performance section ---- */
.cmd-hci-seg { display: inline-flex; border: 1px solid var(--border); border-radius: var(--r-md); overflow: hidden; }
.cmd-hci-seg-btn { background: none; border: none; border-right: 1px solid var(--border); padding: 5px 12px; font-size: var(--fs-50); font-weight: 600; color: var(--text-dim); cursor: pointer; }
.cmd-hci-seg-btn:last-child { border-right: none; }
.cmd-hci-seg-btn:hover { background: var(--bg-hover); color: var(--text); }
.cmd-hci-seg-btn.active { background: var(--accent-tint); color: var(--accent-hover); }
.cmd-hci-seg-btn:focus-visible { outline: 2px solid var(--focus-ring); outline-offset: -2px; }
.cmd-hci-perf-charts { display: flex; flex-direction: column; gap: 14px; }
.cmd-hci-perf-chart { border: 1px solid var(--border); border-radius: var(--r-md); background: var(--bg-inset); padding: 13px 15px; display: flex; flex-direction: column; gap: 8px; }
.cmd-hci-perf-charthead { display: flex; align-items: center; justify-content: space-between; gap: 12px; }
.cmd-hci-perf-title { font-size: var(--fs-25); font-weight: 600; letter-spacing: .05em; color: var(--text-muted); }
.cmd-hci-perf-legend { display: flex; gap: 14px; }
.cmd-hci-perf-key { display: inline-flex; align-items: center; gap: 5px; font-size: var(--fs-50); color: var(--text-dim); }
.cmd-hci-perf-swatch { width: 10px; height: 10px; border-radius: 2px; }
.cmd-hci-perf-swatch.is-read { background: var(--accent); }
.cmd-hci-perf-swatch.is-write { background: var(--warn); }
.cmd-hci-perf-plot { display: flex; align-items: stretch; gap: 8px; }
.cmd-hci-perf-yaxis { display: flex; flex-direction: column; justify-content: space-between; align-items: flex-end; width: 68px; padding: 3px 0; font-size: var(--fs-25); color: var(--text-muted); font-variant-numeric: tabular-nums; }
.cmd-hci-perf-svg { flex: 1; min-width: 0; display: block; height: 140px; }
.cmd-hci-perf-grid { stroke: var(--border); stroke-width: 1; }
.cmd-hci-perf-read { stroke: var(--accent); }
.cmd-hci-perf-write { stroke: var(--warn); }
.cmd-hci-perf-cap { font-size: var(--fs-50); color: var(--text-muted); font-variant-numeric: tabular-nums; }
.cmd-hci-perf-msg { font-size: var(--fs-75); color: var(--text-dim); text-align: center; padding: 18px; border: 1px dashed var(--border); border-radius: var(--r-md); background: var(--bg-inset); }
.cmd-hci-perf-msg.is-err { color: var(--err); border-color: color-mix(in srgb, var(--err) 40%, transparent); }
.cmd-hci-perf-subh { margin: 4px 0 0; }

/* ======================================================================== *
 * ENTERPRISE MATURITY PASS — Slice 1 foundation                            *
 * Density + hierarchy + consistency primitives that the per-view slices     *
 * adopt. Tokens only; no literals. Builds on the existing system, never a    *
 * parallel one. See docs/DESIGN_LANGUAGE.md and SIMPLEVIRT_UI_UX_AUDIT.md.        *
 * ======================================================================== */

/* --- Object header reads as a distinct BAND above the content layer ------ *
 * The header + tab strip carry the rail tone (a subtle raised band); the
 * object body sits on the canvas below, so a host/VM/cluster page has the
 * four clear levels the mission asks for: breadcrumb, identity, tabs, content.
 * All three share sp-6 horizontal padding so their left edges align exactly. */
/* The command header BAND: the object's identity + status + inline facts + the
   command bar, held in a distinct raised band with a lit top edge and a hard
   bottom border — the strongest thing on the page, not a line of text. */
.cmd-obj-header {
  /* Flat graphite identity area — no navy gradient band. The object name carries
     the weight; a single bottom border separates it from the content below. */
  background: var(--bg);
  padding: var(--sp-4) var(--sp-6) var(--sp-4);
  border-bottom: 1px solid var(--border);
}
/* The inline FACT STRIP: the object's key facts as label-over-value pairs,
   divider-separated, on one line below the identity row — the vСenter/Prism
   "object summary" line, replacing a dim grey run-on sentence. */
.cmd-factstrip { display: flex; flex-wrap: wrap; align-items: stretch; gap: var(--sp-2) 0; margin-top: var(--sp-3); padding-top: var(--sp-3); border-top: 1px solid var(--border-subtle); }
.cmd-factstrip .ms-item { display: flex; flex-direction: column; gap: 3px; white-space: nowrap; padding: 0 var(--sp-4); border-left: 1px solid var(--border-subtle); }
.cmd-factstrip .ms-item:first-child { padding-left: 0; border-left: 0; }
.cmd-factstrip .ms-item + .ms-item::before { content: none; }
.cmd-factstrip .ms-k { font-size: var(--fs-10); letter-spacing: 0.06em; color: var(--text-muted); }
.cmd-factstrip .ms-v { font-size: var(--fs-100); color: var(--text); font-weight: 600; }
.cmd-tabs {
  background: var(--bg-rail);
  padding-left: var(--sp-6);
  padding-right: var(--sp-6);
}
.cmd-obj-body { background: var(--bg); }
/* The identity name is the biggest thing on the page (mission #3): hold it in
   the 23-26px band and let the status pill ride beside it, not under it. */
.cmd-obj-titletext h1 { font-size: var(--fs-500); }

/* --- Metric strip: an inline row of spec pairs (VM header, cluster rollup) - *
 * "2 vCPU · 2 GiB · 20 GiB · Network 99" as scannable key/value pairs with a
 * thin dot separator, instead of one dim grey sentence. */
.metric-strip { display: flex; flex-wrap: wrap; align-items: baseline; gap: var(--sp-1) var(--sp-3); }
.metric-strip .ms-item { display: inline-flex; align-items: baseline; gap: var(--sp-1); font-size: var(--fs-75); white-space: nowrap; }
.metric-strip .ms-k { color: var(--text-muted); }
.metric-strip .ms-v { color: var(--text); font-weight: 600; font-variant-numeric: tabular-nums; }
.metric-strip .ms-item + .ms-item::before { content: "·"; color: var(--text-disabled); margin-right: var(--sp-2); }
/* In the object header the strip sits a hair below the type/placement sub-line. */
.cmd-obj-metrics { margin-top: var(--sp-1); }

/* --- Metric band: a grouped, dense horizontal metric row (VM Overview #5,   *
 * Datacenter summary #4). Cells share one plate, split by a 1px token gap —
 * one grouped instrument, not four floating cards. */
.metric-band { display: grid; grid-auto-flow: column; grid-auto-columns: 1fr; gap: 1px; background: var(--border); border-radius: var(--r-md); overflow: hidden; box-shadow: 0 0 0 1px color-mix(in srgb, var(--border) 70%, transparent); }
.metric-band .mb-cell { background: var(--bg-raised); padding: var(--sp-3) var(--sp-4); display: flex; flex-direction: column; gap: 3px; min-width: 0; }
.metric-band .mb-k { font-size: var(--fs-50); letter-spacing: var(--ls-caps); color: var(--text-muted); }
.metric-band .mb-v { font-size: var(--fs-400); font-weight: 700; letter-spacing: var(--ls-tight); line-height: 1.15; font-variant-numeric: tabular-nums; }
.metric-band .mb-sub { font-size: var(--fs-50); color: var(--text-dim); overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
@media (max-width: 900px) { .metric-band { grid-auto-flow: row; grid-auto-columns: auto; grid-template-columns: 1fr 1fr; } }

/* --- Enterprise inventory tables: selected row, sortable header, row motion - */
tbody tr { transition: background var(--dur-2) var(--ease-standard); }
/* Selected row: a quiet charcoal fill + the SimpleVirt-blue leading keyline — the same
   selection language as the nav and inventory tree, never a blue wash. */
tbody tr.selected,
tbody tr[aria-selected="true"] {
  background: var(--bg-selected);
  box-shadow: inset 2px 0 0 var(--accent);
}
tbody tr.selected:hover,
tbody tr[aria-selected="true"]:hover { background: var(--bg-hover); }
th.sortable { cursor: pointer; user-select: none; transition: color var(--dur-2) var(--ease-standard); }
th.sortable:hover { color: var(--text-dim); }
th[aria-sort] { color: var(--text-dim); }
th[aria-sort]::after { content: "↕"; margin-left: var(--sp-1); font-size: 0.85em; opacity: 0.5; }
th[aria-sort="ascending"]::after { content: "↑"; opacity: 1; color: var(--accent); }
th[aria-sort="descending"]::after { content: "↓"; opacity: 1; color: var(--accent); }

/* --- Table toolbar: filter + count + right-aligned actions above a table -- */
.table-toolbar { display: flex; align-items: center; gap: var(--sp-2); margin-bottom: var(--sp-3); flex-wrap: wrap; }
.table-toolbar .tt-search { flex: 0 1 300px; min-width: 160px; }
.table-toolbar .tt-count { font-size: var(--fs-75); color: var(--text-muted); font-variant-numeric: tabular-nums; }
.table-toolbar .tt-spacer { margin-left: auto; }

/* --- Details drawer: a right-side panel for a NIC / disk / task / alarm, so
 * inspecting an object is dense in-place, not another full page (mission #14). */
.cmd-drawer-scrim { position: fixed; inset: 0; background: rgba(0, 0, 0, 0.45); opacity: 0; pointer-events: none; transition: opacity var(--dur-3) var(--ease-standard); z-index: 50; }
.cmd-drawer-scrim.open { opacity: 1; pointer-events: auto; }
.cmd-drawer { position: fixed; top: 0; right: 0; bottom: 0; width: min(440px, 94vw); background: var(--bg-raised); border-left: 1px solid var(--border-strong); box-shadow: var(--shadow-lg); transform: translateX(100%); transition: transform var(--dur-3) var(--ease-entrance); z-index: 51; display: flex; flex-direction: column; }
.cmd-drawer.open { transform: translateX(0); }
.cmd-drawer-head { display: flex; align-items: center; gap: var(--sp-3); padding: var(--sp-4) var(--sp-5); border-bottom: 1px solid var(--border); }
.cmd-drawer-titles { min-width: 0; }
.cmd-drawer-title { font-size: var(--fs-300); font-weight: 600; }
.cmd-drawer-sub { font-size: var(--fs-75); color: var(--text-dim); }
.cmd-drawer-close { margin-left: auto; flex: none; }
.cmd-drawer-body { padding: var(--sp-5); overflow-y: auto; flex: 1; }
@media (prefers-reduced-motion: reduce) { .cmd-drawer, .cmd-drawer-scrim { transition: none; } }

/* --- Three reusable page densities (mission #17) -------------------------- *
 * OVERVIEW: a dashboard grid that packs summary panels but never lets one
 * stretch to an unreadable width. INVENTORY: tables/trees (the table rules
 * above). CONFIG: forms bounded to a reading column, not stretched full-bleed. */
.dash-grid { display: grid; grid-template-columns: repeat(auto-fit, minmax(260px, 1fr)); gap: var(--sp-4); align-items: start; }
.dash-grid.cols-wide { grid-template-columns: repeat(auto-fit, minmax(380px, 1fr)); }
.split-2 { display: grid; grid-template-columns: 1fr 1fr; gap: var(--sp-4); align-items: start; }
.split-main { display: grid; grid-template-columns: minmax(0, 1.9fr) minmax(260px, 1fr); gap: var(--sp-4); align-items: start; }
@media (max-width: 1080px) { .split-2, .split-main { grid-template-columns: 1fr; } }
.config-col { max-width: 800px; }

/* Small shared classes the view-polish pass adopted (kept token-based). */
.cell-strong { font-weight: var(--fw-semibold); }     /* a table cell's primary value, without .cell-name's flex overload */
.cmd-num-narrow { max-width: 84px; }                   /* compact numeric inputs (HA admission control) that otherwise stretch full-width */
.cv-review { display: flex; flex-direction: column; gap: var(--sp-4); }  /* Create-VM Review: stack of grouped change-summary blocks */

/* ======================================================================== *
 * OPERATIONAL DELIGHT — guided fresh-install setup (setup.js)               *
 * Calm, confident onboarding: live progress, steps that turn green on their *
 * own, a strong "SimpleVirt is ready" finish. Restrained motion only.            *
 * ======================================================================== */
.setup-guide { max-width: 720px; margin: var(--sp-8) auto var(--sp-6); }
.setup-head { margin-bottom: var(--sp-5); }
.setup-eyebrow { font-size: var(--fs-50); letter-spacing: var(--ls-caps); color: var(--accent); font-weight: var(--fw-semibold); }
.setup-title { font-size: var(--fs-600); font-weight: var(--fw-bold); letter-spacing: var(--ls-tight); line-height: var(--lh-tight); margin: var(--sp-1) 0 var(--sp-2); }
.setup-lead { font-size: var(--fs-200); color: var(--text-dim); margin: 0 0 var(--sp-4); }
.setup-progress { display: flex; align-items: center; gap: var(--sp-3); }
.setup-bar { flex: 1; height: 6px; background: var(--bg-inset); border: 1px solid var(--border); border-radius: var(--r-pill); overflow: hidden; }
.setup-bar-fill { height: 100%; background: var(--accent); border-radius: var(--r-pill); transition: width var(--dur-4) var(--ease-standard); }
.setup-progress-label { font-size: var(--fs-75); color: var(--text-muted); font-variant-numeric: tabular-nums; white-space: nowrap; }

.setup-steps { display: flex; flex-direction: column; gap: var(--sp-px); }
.setup-step { display: flex; align-items: flex-start; gap: var(--sp-3); padding: var(--sp-3); border: 1px solid transparent; border-radius: var(--r-md); transition: background var(--dur-2) var(--ease-standard), border-color var(--dur-2) var(--ease-standard), opacity var(--dur-2) var(--ease-standard); }
.setup-step.current { background: var(--bg-raised); border-color: var(--border); box-shadow: inset 2px 0 0 var(--accent); }
.setup-step.todo { opacity: 0.66; }

.setup-mark { flex: none; width: 26px; height: 26px; border-radius: var(--r-pill); display: inline-flex; align-items: center; justify-content: center; font-size: var(--fs-75); font-weight: var(--fw-semibold); font-variant-numeric: tabular-nums; background: var(--bg-inset); color: var(--text-muted); border: 1px solid var(--border); transition: background var(--dur-2) var(--ease-standard), color var(--dur-2) var(--ease-standard); }
.setup-mark-current { background: var(--accent); color: var(--accent-fg); border-color: var(--accent); }
.setup-mark-done { background: var(--ok-tint); color: var(--ok); border-color: color-mix(in srgb, var(--ok) 45%, transparent); }

.setup-step-main { flex: 1; min-width: 0; }
.setup-step-titlerow { display: flex; align-items: baseline; gap: var(--sp-2); flex-wrap: wrap; }
.setup-step-title { font-size: var(--fs-200); font-weight: var(--fw-semibold); color: var(--text); }
.setup-step-donetext { font-size: var(--fs-75); color: var(--ok); font-weight: var(--fw-medium); }
.setup-step-desc { font-size: var(--fs-75); color: var(--text-dim); margin: 2px 0 0; }
.setup-step > .btn { flex: none; align-self: center; }

.setup-foot { margin-top: var(--sp-4); }
.setup-skip { background: none; border: none; color: var(--text-muted); font-size: var(--fs-75); font-family: inherit; cursor: pointer; padding: var(--sp-1) 0; text-decoration: underline; text-underline-offset: 2px; transition: color var(--dur-2) var(--ease-standard); }
.setup-skip:hover { color: var(--text-dim); }

/* "SimpleVirt is ready" finish — calm, centred, one clear button into the console. */
.setup-ready { max-width: 520px; text-align: center; }
.setup-ready-mark { width: 60px; height: 60px; margin: 0 auto var(--sp-4); border-radius: var(--r-pill); display: flex; align-items: center; justify-content: center; font-size: var(--fs-600); font-weight: var(--fw-bold); background: var(--ok-tint); color: var(--ok); border: 1px solid color-mix(in srgb, var(--ok) 45%, transparent); }
/* Honest finish: when the fleet has live issues, the mark is attention-amber, not a reassuring green. */
.setup-ready-mark-attn { background: var(--warn-tint); color: var(--warn); border-color: color-mix(in srgb, var(--warn) 45%, transparent); }
.setup-ready .setup-title { font-size: var(--fs-700); }
.setup-ready .setup-lead { margin-bottom: var(--sp-3); }
.setup-ready-recap { font-size: var(--fs-100); color: var(--text-dim); font-variant-numeric: tabular-nums; margin-bottom: var(--sp-5); }
.setup-ready--enter .setup-ready-mark { animation: setup-mark-in var(--dur-4) var(--ease-entrance); }
@keyframes setup-mark-in { from { transform: scale(0.7); opacity: 0; } to { transform: scale(1); opacity: 1; } }
@media (prefers-reduced-motion: reduce) {
  .setup-bar-fill { transition: none; }
  .setup-ready--enter .setup-ready-mark { animation: none; }
}

/* --- Smooth, restrained STATE transitions ------------------------------- *
 * A status change should ease, not snap: a dot going green, a badge/pill
 * changing tone, a meter filling as capacity moves. All inside the 120-250ms
 * budget. These fire only on REAL change — the calm-poll discipline
 * (viewDataSig / unchanged DOM) means a 5s tick that changes nothing repaints
 * nothing, so nothing animates unless a value actually moved. */
.dot,
.status-dot,
.cmd-tree-statusdot,
.cmd-fleet-dot { transition: background var(--dur-2) var(--ease-standard), box-shadow var(--dur-2) var(--ease-standard); }
.badge,
.cmd-pill,
.cmd-card-badge { transition: background var(--dur-2) var(--ease-standard), color var(--dur-2) var(--ease-standard), box-shadow var(--dur-2) var(--ease-standard); }
/* Capacity/utilisation bars ease their fill AND their tone (ok→warn→crit). */
.meter-fill { transition: width var(--dur-4) var(--ease-standard), background var(--dur-3) var(--ease-standard); }
.rbar-fill { transition: width var(--dur-4) var(--ease-standard), background var(--dur-3) var(--ease-standard); }
@media (prefers-reduced-motion: reduce) {
  .dot, .status-dot, .cmd-tree-statusdot, .cmd-fleet-dot,
  .badge, .cmd-pill, .cmd-card-badge,
  .meter-fill, .rbar-fill { transition: none; }
}

/* Validation-turns-green readiness checklist (san/HA wizards): a met requirement
   reads full-strength, an unmet one stays quiet — so a wizard visibly turns green
   as its prerequisites are satisfied. */
.cmd-san-reqs { display: flex; flex-direction: column; gap: var(--sp-2); margin: var(--sp-3) 0 var(--sp-1); }
.cmd-san-req { display: flex; align-items: center; gap: var(--sp-3); font-size: var(--fs-100); color: var(--text-dim); }
.cmd-san-req.is-ok { color: var(--text); }

/* ======================================================================== *
 * LIGHT-MODE MATURITY PASS                                                   *
 * Light is a first-class theme, not dark-inverted: deliberate surfaces,      *
 * readable secondary text, defined table headers, bordered chips + controls, *
 * restrained selection. Scoped to [data-theme="light"] so dark never moves.  *
 * ======================================================================== */

/* Table header: the recessed --bg-inset is nearly invisible on white, so give the
   header a real band with a firmer foot and stronger label contrast; row
   separators use the subtle hairline so a row stays easy to track. */
:root[data-theme="light"] th {
  background: #e9ecf2;
  color: var(--text-dim);
  box-shadow: inset 0 -1px 0 var(--border-strong);
}
:root[data-theme="light"] td { border-bottom-color: var(--border-subtle); }
:root[data-theme="light"] tbody tr.selected,
:root[data-theme="light"] tbody tr[aria-selected="true"] { background: var(--bg-selected); box-shadow: inset 3px 0 0 var(--accent); }

/* Status chips: a status-toned border so a chip reads as deliberate on white,
   not faint text on a pale wash. The base badge/pill already carries a (usually
   transparent) border, so this only COLOURS it — no double edge. */
:root[data-theme="light"] .badge-ok,   :root[data-theme="light"] .cmd-pill-ok   { border-color: color-mix(in srgb, var(--ok) 42%, transparent); }
:root[data-theme="light"] .badge-warn, :root[data-theme="light"] .cmd-pill-warn { border-color: color-mix(in srgb, var(--warn) 44%, transparent); }
:root[data-theme="light"] .badge-err,  :root[data-theme="light"] .cmd-pill-err  { border-color: color-mix(in srgb, var(--err) 44%, transparent); }
:root[data-theme="light"] .badge-info, :root[data-theme="light"] .badge-accent  { border-color: color-mix(in srgb, var(--accent) 40%, transparent); }
:root[data-theme="light"] .badge-muted { border-color: var(--border-strong); }

/* Secondary/ghost buttons must read as controls on white, not plain browser text. */
:root[data-theme="light"] .btn-ghost {
  background: var(--bg-raised);
  border-color: var(--border-strong);
  color: var(--text-dim);
}
:root[data-theme="light"] .btn-ghost:hover:not(:disabled) { background: var(--bg-hover); color: var(--text); }

/* Form controls: a visible boundary without heaviness on white. The sign-in's
   fields are left out: that screen stays dark in either theme, and at (0,2,1) this
   rule would outrank their own resting, hover and focus borders (0,1,0 / 0,2,0),
   leaving a focused field with the same grey border as a resting one. :where()
   adds no specificity, so every other input matches exactly as before. */
:root[data-theme="light"] input:where(:not(.login-input)),
:root[data-theme="light"] select,
:root[data-theme="light"] textarea { border-color: var(--border-strong); }

/* Inventory selection: a restrained accent wash + the existing keyline, never a
   saturated blue rectangle. */
:root[data-theme="light"] .cmd-tree-row.active { background: var(--bg-selected); }
.cmd-hci-perf-avglat { font-size: var(--fs-75); color: var(--text-dim); }
/* #25 — the per-disk table's search toolbar sits between the average line and the table. */
.cmd-hci-perf-osdtools { margin: var(--sp-2) 0; }
.cmd-hci-perf-osdtools .cmd-search { max-width: 320px; }

/* ---- SimpleVirt HCI: Network section ---- */
.cmd-hci-net-line { display: flex; align-items: baseline; gap: 8px; font-size: var(--fs-75); color: var(--text-dim); }
.cmd-hci-net-line.is-warn { color: var(--warn); font-weight: 600; }
.cmd-hci-net-mark { flex: 0 0 auto; }

.cmd-net-metrics { margin: var(--sp-2) 0 var(--sp-3); gap: var(--sp-4) var(--sp-5); }

/* SimpleVirt Guest Tools status pill (VM detail → Guest card). Tones reuse the shared
   --ok/--warn tokens; "muted" is a neutral grey for not-installed/unknown. */
.cmd-gt-status {
  display: inline-block;
  padding: 1px 8px;
  border-radius: 10px;
  font-size: var(--fs-50);
  font-weight: 600;
  line-height: 18px;
}
.cmd-gt-ok { color: var(--ok); background: var(--ok-tint); }
.cmd-gt-warn { color: var(--warn); background: var(--warn-tint); }
.cmd-gt-muted { color: var(--muted-chip-fg); background: var(--muted-chip-bg); }
.cmd-gt-actions { margin-top: 10px; display: flex; flex-direction: column; gap: var(--sp-2); align-items: flex-start; }
/* Fallback shown when there is no guest agent to drive the install: the exact
   one-time in-guest commands, copy-paste ready. */
.cmd-gt-manual { width: 100%; }
.cmd-gt-cmd {
  margin: 6px 0 0;
  padding: 8px 10px;
  background: var(--bg-inset);
  border: 1px solid var(--border);
  border-radius: 6px;
  font-family: ui-monospace, monospace;
  font-size: var(--fs-50);
  white-space: pre-wrap;
  word-break: break-word;
  overflow-x: auto;
}
/* Numbered one-time in-guest steps shown after the tools CD is mounted on a
   Windows VM (vmconfigure.js windowsGuestToolsSteps). */
.cmd-gt-steps { margin: 4px 0 10px; padding-left: 20px; color: var(--text-dim); font-size: var(--fs-50); line-height: 1.7; }
.cmd-gt-steps li { margin: 0; }
.cmd-card-subhead { margin: 12px 0 6px; font-size: var(--fs-50); font-weight: 600; color: var(--text-dim); letter-spacing: var(--ls-caps); }

/* Upstream switch/port an adapter is cabled to, learned from its advertisements.
   Quieter than the adapter's own facts: it describes the far end, not this host. */
.hnet-nic-neighbor {
  margin-top: 4px;
  font-size: var(--fs-50);
  color: var(--text-muted);
}
.hnet-nic-neighbor .mono {
  color: var(--text);
}
/* Role (switch/router) and discovery-protocol (LLDP/CDP) tags on a neighbor line. */
.hnet-nb-kind,
.hnet-nb-src {
  display: inline-block;
  margin-left: 6px;
  padding: 0 5px;
  border-radius: 5px;
  font-size: var(--fs-10);
  font-weight: 700;
  letter-spacing: 0.02em;

  background: var(--bg-inset);
  color: var(--text-muted);
  vertical-align: middle;
}
.hnet-nb-src {
  background: var(--accent-tint);
  color: var(--accent);
}

/* A distributed network's row: the managed marker and the one verb that is still
   this host's to run, side by side. */
.hnet-managed-cell {
  display: flex;
  align-items: center;
  gap: var(--sp-2);
  justify-content: flex-end;
}

/* ---------- editorial identity layer: type ---------------------------------
   The identity carrier. One family — Inter, carried to display sizes with weight
   and tight tracking on names, section heads and the dominant metric — so the app
   stops reading as a generic system-font admin. This is TYPE ONLY and safe to apply
   globally; the structural editorial pass (un-carded hairline sections, the
   asymmetric lede band, the masthead spine) lands per-view on top of it. */
h1, h2, h3,
.cmd-obj-titletext h1,
.card-title, .section-title, .panel-head .section-title,
.cmd-card-title, .cmd-stat-big, .kpi-stat,
.cmd-kpi-value {
  font-family: var(--font-display);
}
/* Every dominant figure reads as one engineered numeric family: display face,
   tabular so digits align, tight tracking. (Labels/sub stay in Inter.) */
.cmd-stat-big, .kpi-stat { font-variant-numeric: tabular-nums; letter-spacing: var(--ls-tight); }
/* The object name is the page identity — semibold, bright, tight. */
.cmd-obj-titletext h1 { font-weight: var(--fw-semibold); letter-spacing: var(--ls-tight); color: var(--text); }
/* Section heads: small, tracked, upper — editorial labels rather than card chrome. */
.card-title, .section-title, .panel-head .section-title {

  letter-spacing: 0.08em;
}
/* Dominant figures read as set numerals: display face, tabular so columns align. */
.cmd-kpi-value { letter-spacing: -0.01em; font-variant-numeric: tabular-nums; }

/* ---------- monitoring: retained-series charts, alarms ------------------- */
/* Every colour here is a token, so light mode needs no second palette: the
   :root[data-theme="light"] block above already redefines all of them. */

/* The retained-history chart (metricschart.js). Shares the plot geometry with
   the HCI performance chart it was lifted from and adds the one thing that one
   did not need: a real, labelled time axis under the plot. */
.cmd-mchart { border: 1px solid var(--border); border-radius: var(--r-md); background: var(--bg-inset); padding: 13px 15px; display: flex; flex-direction: column; gap: 8px; }
.cmd-mchart + .cmd-mchart { margin-top: 14px; }
.cmd-mchart-plot { display: flex; align-items: stretch; gap: 8px; }
.cmd-mchart-yaxis { display: flex; flex-direction: column; justify-content: space-between; align-items: flex-end; width: 68px; padding: 3px 0; font-size: var(--fs-25); color: var(--text-muted); font-variant-numeric: tabular-nums; }
.cmd-mchart-svg { flex: 1; min-width: 0; display: block; height: 150px; }
.cmd-mchart-grid { stroke: var(--border); stroke-width: 1; }
/* The x-axis ticks are evenly spaced in TIME, so space-between puts each label
   on its own gridline without a single inline style (the CSP forbids those).
   The 68px offset matches the y-axis gutter so tick 1 sits under the plot's
   left edge, not under the y labels. */
.cmd-mchart-xaxis { display: flex; justify-content: space-between; margin-left: 76px; font-size: var(--fs-25); color: var(--text-muted); font-variant-numeric: tabular-nums; }
.cmd-mchart-tick { flex: 0 0 auto; }
.cmd-mchart-cap { font-size: var(--fs-50); color: var(--text-muted); font-variant-numeric: tabular-nums; }
.cmd-mchart-state { padding: 4px 0; }
.cmd-mchart-swatch { width: 10px; height: 10px; border-radius: 2px; display: inline-block; }
/* Three series tones. Blue is the primary metric, amber the second, and the
   third a desaturated slate — status green/red stay reserved for status. */
.cmd-mchart-line.is-s1, .cmd-mchart-swatch.is-s1 { stroke: var(--accent); background: var(--accent); }
.cmd-mchart-line.is-s2, .cmd-mchart-swatch.is-s2 { stroke: var(--warn); background: var(--warn); }
.cmd-mchart-line.is-s3, .cmd-mchart-swatch.is-s3 { stroke: var(--text-dim); background: var(--text-dim); }
.cmd-mchart-line { fill: none; }
.cmd-host-history { margin-top: 18px; }

/* Alarms. The tables are the shared dataTable; only the page furniture is new. */
/* Alarms hero KPI tiles: the count tints only when non-zero. */
.cmd-alarm-kpi-crit { color: var(--err); }
.cmd-alarm-kpi-warn { color: var(--warn); }
/* Firing-alarm rows carry a severity accent rail; an acknowledged one dims. */
.cmd-alarm-row-crit td:first-child { box-shadow: inset 3px 0 0 var(--err); }
.cmd-alarm-row-warn td:first-child { box-shadow: inset 3px 0 0 var(--warn); }
.cmd-alarm-row.is-ack { opacity: 0.7; }
.cmd-alarms { display: flex; flex-direction: column; gap: 14px; }
.cmd-alarms-panel { display: flex; flex-direction: column; gap: 12px; }
.cmd-alarms-history { margin-top: 10px; display: flex; flex-direction: column; gap: 10px; }
.cmd-alarm-where { text-align: left; }
.cmd-hc-alarms { margin: 0 0 18px; display: flex; flex-direction: column; gap: 10px; }
.cmd-hc-alarms-head { display: flex; align-items: center; gap: 9px; }
.cmd-hc-alarms-title { margin: 0; font-size: var(--fs-200); font-weight: 600; flex: 1; }

/* The unacknowledged-alarm count on the Health rail button. It has to be
   readable at a glance from across a desk and must never push the rail wider,
   so it rides the corner of the icon rather than taking a line of its own. */
.cmd-lens-badge {
  /* The two foreground literals are deliberate: this text sits ON a status fill,
     so it must stay legible when the theme flips the surfaces underneath it. */
  position: absolute; top: 4px; right: 6px; min-width: 16px; height: 16px; padding: 0 4px;
  border-radius: 8px; background: var(--warn); color: #0b0f14;
  font-size: var(--fs-10); font-weight: 700; line-height: 16px; text-align: center;
  font-variant-numeric: tabular-nums; pointer-events: none;
}
.cmd-lens-badge.is-crit { background: var(--err); color: #fff; }

/* VM → Snapshots: the retention-schedule card between the note and the snapshot list.
   A quiet inset panel; the leading badge says whether an automatic schedule is on. */
.cmd-snap-sched {
  display: flex; align-items: center; justify-content: space-between; gap: var(--sp-4);
  padding: var(--sp-3) var(--sp-4); margin: var(--sp-3) 0 var(--sp-4);
  background: var(--bg-inset); border: 1px solid var(--border-subtle); border-radius: var(--r-md);
}
.cmd-snap-sched-main { min-width: 0; display: flex; flex-direction: column; gap: 3px; }
.cmd-snap-sched-head { display: flex; align-items: center; gap: var(--sp-2); }
.cmd-snap-sched-title { font-weight: 600; font-size: var(--fs-100); }
.cmd-snap-sched-sum { color: var(--text-muted); font-size: var(--fs-75); }
.cmd-snap-sched-next { color: var(--text-muted); font-size: var(--fs-50); }
/* "auto" tag on a scheduler-owned snapshot row — neutral, not a status colour. */
.cmd-snap-auto { background: var(--bg-hover); color: var(--text-muted); }
/* The "take automatically" checkbox row in the schedule dialog. */
.cmd-snap-check { display: flex; align-items: center; gap: var(--sp-2); margin-bottom: var(--sp-3); cursor: pointer; }
.cmd-snap-check input { width: auto; }
@media (max-width: 560px) {
  .cmd-snap-sched { flex-direction: column; align-items: stretch; }
}

/* ---- Host address change + HA cluster membership (views/hostaddressview.js) ----
   A host that moved is not a host that died: the banner lists, per plane, what is
   already true (✓) and what is still being followed (…), in the banner's own
   tone. The address rows put an address and its agreement chips on one line. */
.cmd-addr-banners { display: flex; flex-direction: column; gap: 0; }
/* An address (an IPv6 literal, a long hostname) must wrap, not widen the banner. */
.cmd-addr-banner .cmd-banner-title,
.cmd-addr-banner .cmd-banner-text { overflow-wrap: anywhere; }
.cmd-addr-checks { list-style: none; margin: var(--sp-1) 0 var(--sp-2); padding: 0; display: flex; flex-direction: column; gap: 3px; }
.cmd-addr-check { display: grid; grid-template-columns: 1.2em 13.5em 1fr; gap: var(--sp-2); align-items: baseline; font-size: var(--fs-75); }
.cmd-addr-check-mark { font-weight: 700; text-align: center; }
.cmd-addr-check-label { font-weight: 600; }
.cmd-addr-check-detail { color: var(--text-dim); overflow-wrap: anywhere; }
.cmd-addr-check.is-ok .cmd-addr-check-mark { color: var(--ok); }
.cmd-addr-check.is-pending .cmd-addr-check-mark { opacity: 0.8; }
.cmd-addr-value { display: inline-flex; flex-wrap: wrap; justify-content: flex-end; align-items: center; gap: var(--sp-1) var(--sp-2); }
.cmd-addr-history { display: flex; flex-direction: column; gap: var(--sp-2); }
.cmd-addr-foot { display: flex; flex-wrap: wrap; gap: var(--sp-2); }
/* An HA-cluster member's name in the key/value list: its status bead, then the link. */
.cmd-addr-member { display: inline-flex; align-items: center; gap: var(--sp-2); white-space: nowrap; }
.cmd-addr-warn { color: var(--warn); }
@media (max-width: 560px) {
  .cmd-addr-check { grid-template-columns: 1.2em 1fr; }
  .cmd-addr-check-detail { grid-column: 2; }
}

/* ============================================================================
   Log Explorer (#/logs) — the fleet log search / debugging console.
   Self-contained: all classes are prefixed .cmd-logs / .cmd-log so nothing here
   reaches the rest of the app. SimpleVirt Graphite tokens throughout.
   ========================================================================== */
.cmd-logs {
  display: flex;
  flex-direction: column;
  height: 100%;
  min-height: 0;
  gap: 12px;
  padding: 20px 24px 0;
}
.cmd-logs-head {
  display: flex;
  align-items: flex-start;
  justify-content: space-between;
  gap: 16px;
  flex-wrap: wrap;
}
.cmd-logs-title { font-size: 22px; font-weight: 640; letter-spacing: -0.01em; margin: 0; color: var(--text); }
.cmd-logs-sub { margin: 4px 0 0; color: var(--text-dim); font-size: var(--fs-75); max-width: 62ch; }
.cmd-logs-actions { display: flex; gap: 8px; flex-shrink: 0; }
.cmd-logs .cmd-btn {
  appearance: none;
  border: 1px solid var(--border);
  background: var(--bg-panel);
  color: var(--text-dim);
  font: inherit;
  font-size: var(--fs-75);
  padding: 7px 14px;
  border-radius: var(--r-sm);
  cursor: pointer;
  transition: background var(--dur-2), color var(--dur-2), border-color var(--dur-2);
}
.cmd-logs .cmd-btn:hover { background: var(--bg-hover); color: var(--text); border-color: var(--border-strong); }
.cmd-logs .cmd-logs-follow.active {
  background: var(--accent-tint);
  border-color: var(--accent);
  color: var(--accent-hover);
}

/* --- search + filters --- */
.cmd-logs-filters { display: flex; align-items: center; gap: 14px; flex-wrap: wrap; }
.cmd-logs-searchwrap {
  position: relative;
  flex: 1 1 340px;
  min-width: 240px;
  display: flex;
  align-items: center;
}
.cmd-logs-search-ic {
  position: absolute; left: 11px; color: var(--text-muted); font-size: var(--fs-300); pointer-events: none;
}
.cmd-logs-search {
  width: 100%;
  background: var(--bg-inset);
  border: 1px solid var(--border);
  border-radius: var(--r-sm);
  color: var(--text);
  font: inherit;
  font-size: 13.5px;
  padding: 9px 12px 9px 32px;
  outline: none;
  transition: border-color var(--dur-2), box-shadow var(--dur-2);
}
.cmd-logs-search:focus { border-color: var(--accent); box-shadow: 0 0 0 3px var(--accent-tint); }
.cmd-logs-search::placeholder { color: var(--text-muted); }

.cmd-logs-seg-group { display: flex; align-items: center; gap: 8px; }
.cmd-logs-seg-label, .cmd-logs-chip-label { font-size: var(--fs-25); letter-spacing: 0.06em; color: var(--text-muted); }
.cmd-logs-seg { display: inline-flex; background: var(--bg-inset); border: 1px solid var(--border); border-radius: var(--r-sm); overflow: hidden; }
.cmd-logs-seg-btn {
  appearance: none; border: 0; background: transparent; color: var(--text-dim);
  font: inherit; font-size: 12.5px; padding: 6px 12px; cursor: pointer;
  border-right: 1px solid var(--border);
}
.cmd-logs-seg-btn:last-child { border-right: 0; }
.cmd-logs-seg-btn:hover { background: var(--bg-hover); color: var(--text); }
.cmd-logs-seg-btn.active { background: var(--accent-tint); color: var(--accent-hover); font-weight: 560; }

/* --- source / host chips --- */
.cmd-logs-chips { display: flex; flex-direction: column; gap: 6px; }
.cmd-logs-chipline { display: flex; align-items: center; gap: 8px; flex-wrap: wrap; }
.cmd-logs-chiprow { display: flex; gap: 6px; flex-wrap: wrap; }
.cmd-logs-chip {
  appearance: none;
  border: 1px solid var(--border);
  background: var(--bg-panel);
  color: var(--text-muted);
  font: inherit;
  font-size: var(--fs-50);
  padding: 4px 11px;
  border-radius: 999px;
  cursor: pointer;
  transition: background var(--dur-2), color var(--dur-2), border-color var(--dur-2);
}
.cmd-logs-chip:hover { background: var(--bg-hover); color: var(--text-dim); }
.cmd-logs-chip.active { background: var(--accent-tint); border-color: var(--accent); color: var(--accent-hover); font-weight: 540; }
.cmd-logs-chip-journal.active { background: var(--warn-tint); border-color: var(--warn); color: var(--warn); }
.cmd-logs-chip-down { opacity: 0.55; text-decoration: line-through dotted; }

/* --- status strip --- */
.cmd-logs-status { display: flex; flex-direction: column; gap: 6px; }
.cmd-logs-status-left { display: flex; align-items: center; gap: 14px; flex-wrap: wrap; font-size: 12.5px; }
.cmd-logs-count { color: var(--text); font-weight: 600; }
.cmd-logs-trunc { color: var(--warn); font-size: var(--fs-50); }
.cmd-logs-sources-note { color: var(--text-muted); }
.cmd-logs-status-warn {
  background: var(--warn-tint);
  border: 1px solid color-mix(in srgb, var(--warn) 40%, transparent);
  border-radius: var(--r-sm);
  padding: 8px 12px;
  font-size: var(--fs-50);
}
.cmd-logs-warn-head { display: block; color: var(--warn); font-weight: 560; margin-bottom: 4px; }
.cmd-logs-unread { color: var(--text-dim); font-family: var(--mono); font-size: 11.5px; }

/* --- the log list --- */
.cmd-logs-listwrap {
  flex: 1 1 auto;
  min-height: 0;
  overflow-y: auto;
  border: 1px solid var(--border);
  border-radius: var(--r-md);
  background: var(--bg-inset);
  margin-bottom: 20px;
}
.cmd-logs-list { display: flex; flex-direction: column; }
.cmd-log-row { border-bottom: 1px solid var(--border-subtle); }
.cmd-log-row:hover { background: var(--row-hover); }
.cmd-log-head {
  display: grid;
  grid-template-columns: 96px 52px minmax(120px, auto) 1fr;
  align-items: baseline;
  gap: 12px;
  padding: 5px 14px;
  cursor: pointer;
  font-family: var(--mono);
  font-size: 12.5px;
  line-height: 1.5;
}
.cmd-log-time { color: var(--text-muted); white-space: nowrap; font-variant-numeric: tabular-nums; }
.cmd-log-level {
  font-size: 10.5px; font-weight: 700; letter-spacing: 0.03em;
  white-space: nowrap; text-align: right;
}
.cmd-log-level.lvl-error { color: var(--err); }
.cmd-log-level.lvl-warn { color: var(--warn); }
.cmd-log-level.lvl-info { color: var(--text-dim); }
.cmd-log-level.lvl-debug { color: var(--text-muted); }
.cmd-log-level.lvl-none { color: var(--text-disabled); }
.cmd-log-origin { display: inline-flex; gap: 6px; align-items: baseline; white-space: nowrap; overflow: hidden; }
.cmd-log-src {
  font-size: var(--fs-10); letter-spacing: 0.04em; font-weight: 640;
  padding: 1px 6px; border-radius: 3px; background: var(--bg-elevated); color: var(--text-dim);
}
.cmd-log-src.src-clusterd { color: #c9a0ff; background: rgba(150, 110, 230, 0.14); }
.cmd-log-src.src-libvirt { color: var(--cyan); background: var(--cyan-tint); }
.cmd-log-src.src-command { color: var(--accent-hover); background: var(--accent-tint); }
.cmd-log-src.src-qemu { color: #6fce9a; background: rgba(70, 162, 91, 0.14); }
.cmd-log-src.src-kernel { color: var(--warn); background: var(--warn-tint); }
.cmd-log-host { color: var(--text-muted); font-size: 11.5px; }
.cmd-log-msg { color: var(--text); overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.cmd-log-row.open .cmd-log-msg { white-space: normal; overflow-wrap: anywhere; }
.cmd-log-row.lvl-error .cmd-log-msg { color: #f2b3b0; }
.cmd-log-row.lvl-warn .cmd-log-msg { color: #ecca8a; }
.cmd-log-hit { background: color-mix(in srgb, var(--accent) 42%, transparent); color: var(--text); border-radius: 2px; padding: 0 1px; }

/* Group-repeats toggle active state (mirrors Follow). */
.cmd-logs .cmd-logs-group.active {
  background: var(--accent-tint);
  border-color: var(--accent);
  color: var(--accent-hover);
}
/* Inline structured-field preview after a message — muted, so similar lines are
   distinguishable at a glance without expanding each one. */
.cmd-log-fieldpreview {
  color: var(--text-muted);
  margin-left: 10px;
  font-size: 11.5px;
}
.cmd-log-row.open .cmd-log-fieldpreview { color: var(--text-dim); }
/* Collapsed ×N run: the message cell becomes a flex row with a right-aligned tail. */
.cmd-log-msg-group { display: flex; align-items: baseline; gap: 10px; }
.cmd-log-msgtext { flex: 1; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; min-width: 0; }
.cmd-log-grouptail { display: inline-flex; align-items: baseline; gap: 8px; margin-left: auto; white-space: nowrap; flex-shrink: 0; }
.cmd-log-span { color: var(--text-muted); font-size: var(--fs-25); }
.cmd-log-repeat {
  color: var(--accent-hover); background: var(--accent-tint);
  font-weight: 700; font-size: var(--fs-25); letter-spacing: 0.02em;
  padding: 0 7px; border-radius: 9px; font-variant-numeric: tabular-nums;
}
.cmd-log-grouprow.open > .cmd-log-head { background: var(--row-hover); }
/* Expanded member lines sit indented under their group. */
.cmd-log-groupmembers { border-left: 2px solid var(--border-strong); margin-left: 14px; background: var(--bg-elevated); }
.cmd-log-nested .cmd-log-head { padding-left: 14px; }

/* --- expanded detail --- */
.cmd-log-detail {
  padding: 8px 14px 12px 108px;
  background: var(--bg);
  border-top: 1px dashed var(--border);
  font-family: var(--mono);
  font-size: var(--fs-50);
}
.cmd-log-fields { display: grid; grid-template-columns: max-content 1fr; gap: 3px 12px; align-items: baseline; }
.cmd-log-fkey {
  appearance: none; border: 0; background: transparent; cursor: pointer;
  color: var(--text-muted); font: inherit; font-size: 11.5px; text-align: right;
  padding: 0; border-bottom: 1px dotted transparent;
}
.cmd-log-fkey:hover { color: var(--accent-hover); border-bottom-color: var(--accent); }
.cmd-log-fval { color: var(--text); overflow-wrap: anywhere; }
.cmd-log-raw {
  margin: 8px 0 0; padding: 8px 10px; background: var(--bg-inset); border: 1px solid var(--border-subtle);
  border-radius: var(--r-xs); color: var(--text-dim); white-space: pre-wrap; overflow-wrap: anywhere; font-size: 11.5px;
}
.cmd-log-nofields { color: var(--text-muted); font-style: italic; }

@media (max-width: 780px) {
  .cmd-logs { padding: 16px 14px 0; }
  .cmd-log-head { grid-template-columns: 74px 40px 1fr; }
  .cmd-log-origin { display: none; }
  .cmd-log-detail { padding-left: 14px; }
}

/* Log Explorer — severity summary chips in the status strip */
.cmd-logs-sev {
  font-size: var(--fs-50);
  font-weight: 560;
  padding: 1px 8px;
  border-radius: 999px;
  border: 1px solid transparent;
  background: transparent;
  font-family: inherit;
}
button.cmd-logs-sev { cursor: pointer; }
.cmd-logs-sev-err { color: var(--err); background: var(--err-tint); border-color: color-mix(in srgb, var(--err) 34%, transparent); }
button.cmd-logs-sev-err:hover { background: color-mix(in srgb, var(--err) 24%, transparent); }
.cmd-logs-sev-warn { color: var(--warn); background: var(--warn-tint); }

/* Access control — read-only role privilege viewer */
.cmd-priv-view { display: grid; grid-template-columns: 1fr 1fr; gap: 10px 24px; margin: 6px 0 2px; }
.cmd-priv-group { min-width: 0; }
.cmd-priv-item { display: flex; align-items: baseline; gap: 8px; font-size: var(--fs-75); padding: 1px 0; }
.cmd-priv-item.on { color: var(--text); }
.cmd-priv-item.off { color: var(--text-disabled); }
.cmd-priv-mark { width: 12px; text-align: center; font-weight: 700; flex-shrink: 0; }
.cmd-priv-item.on .cmd-priv-mark { color: var(--ok); }
.cmd-priv-item.off .cmd-priv-mark { color: var(--text-disabled); }

/* Certificates — fingerprint copy + fleet trust table */
.cmd-fp-wrap { display: inline-flex; align-items: center; gap: 8px; flex-wrap: wrap; }
.cmd-copy-btn { padding: 2px 8px; }
.cmd-fp-short {
  display: inline-block;
  max-width: 30ch;
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
  vertical-align: bottom;
}
.cmd-fleet-cert { margin-top: 16px; }
@media (max-width: 780px) {
  .cmd-priv-view { grid-template-columns: 1fr; }
}

/* Health Center — fleet GPU inventory + health panel */
.cmd-hc-gpus:empty { display: none; }
.cmd-hc-gpus { margin: 4px 0 18px; }
.cmd-hc-gpu-head { display: flex; align-items: baseline; gap: 12px; flex-wrap: wrap; margin-bottom: 10px; }
.cmd-hc-gpu-title { font-size: var(--fs-200); font-weight: 600; margin: 0; color: var(--text); }
.cmd-hc-gpu-tiles { display: flex; gap: 10px; flex-wrap: wrap; margin-bottom: 12px; }
.cmd-hc-gpu-tile {
  background: var(--bg-panel); border: 1px solid var(--border); border-radius: var(--r-md);
  padding: 8px 16px; min-width: 78px;
}
.cmd-hc-gpu-tile-val { font-size: var(--fs-500); font-weight: 640; color: var(--text); line-height: 1.1; }
.cmd-hc-gpu-tile-lbl { font-size: 11.5px; color: var(--text-muted); letter-spacing: 0.04em; }
.cmd-hc-gpu-tile.tone-err { border-color: color-mix(in srgb, var(--err) 45%, transparent); }
.cmd-hc-gpu-tile.tone-err .cmd-hc-gpu-tile-val { color: var(--err); }
.cmd-hc-gpu-tile.tone-warn { border-color: color-mix(in srgb, var(--warn) 45%, transparent); }
.cmd-hc-gpu-tile.tone-warn .cmd-hc-gpu-tile-val { color: var(--warn); }
.cmd-hc-gpu-tablewrap { border: 1px solid var(--border); border-radius: var(--r-md); overflow: hidden; }
.cmd-hc-gpu-table { width: 100%; border-collapse: collapse; font-size: var(--fs-75); }
.cmd-hc-gpu-table th {
  text-align: left; font-size: var(--fs-25); letter-spacing: 0.05em;
  color: var(--text-muted); font-weight: 600; padding: 8px 14px; background: var(--bg-inset);
  border-bottom: 1px solid var(--border);
}
.cmd-hc-gpu-table td { padding: 8px 14px; border-bottom: 1px solid var(--border-subtle); vertical-align: top; }
.cmd-hc-gpu-table tr:last-child td { border-bottom: 0; }
.cmd-hc-gpu-row.tone-err { background: color-mix(in srgb, var(--err) 6%, transparent); }
.cmd-hc-gpu-addr { display: block; font-family: var(--mono); font-size: var(--fs-25); }
.cmd-hc-gpu-badge {
  display: inline-block; font-size: var(--fs-25); font-weight: 600; letter-spacing: 0.03em;
  padding: 1px 8px; border-radius: 999px;
}
.cmd-hc-gpu-badge.tone-ok { color: var(--ok); background: var(--ok-tint); }
.cmd-hc-gpu-badge.tone-warn { color: var(--warn); background: var(--warn-tint); }
.cmd-hc-gpu-badge.tone-err { color: var(--err); background: var(--err-tint); }
.cmd-hc-gpu-issue { font-size: 11.5px; color: var(--text-dim); margin-top: 3px; max-width: 52ch; }
.cmd-hc-gpu-unread { color: var(--warn); }

/* Create VM — the plain "add a GPU" picker */
.cmd-gpu-grid { display: grid; grid-template-columns: repeat(auto-fill, minmax(220px, 1fr)); gap: 10px; margin: 8px 0 4px; }
.cmd-gpu-pick {
  position: relative; text-align: left; appearance: none; cursor: pointer;
  background: var(--bg-panel); border: 1px solid var(--border); border-radius: var(--r-md);
  padding: 12px 14px; font: inherit; color: var(--text);
  transition: border-color var(--dur-2), background var(--dur-2);
}
.cmd-gpu-pick:hover:not(.disabled) { border-color: var(--border-strong); background: var(--bg-hover); }
.cmd-gpu-pick.selected { border-color: var(--accent); background: var(--accent-tint); }
.cmd-gpu-pick.disabled { cursor: not-allowed; opacity: 0.6; }
.cmd-gpu-pick-name { font-size: var(--fs-100); font-weight: 560; color: var(--text); margin-bottom: 3px; padding-right: 20px; }
.cmd-gpu-pick-status { font-size: var(--fs-50); }
.cmd-gpu-pick-status.tone-ok { color: var(--ok); }
.cmd-gpu-pick-status.tone-info { color: var(--text-dim); }
.cmd-gpu-pick-status.tone-muted { color: var(--text-muted); }
.cmd-gpu-pick-check { position: absolute; top: 10px; right: 12px; color: var(--accent); font-weight: 700; }
.cmd-pci-advanced { margin-top: 12px; }
.cmd-pci-advanced > summary { cursor: pointer; color: var(--text-dim); font-size: var(--fs-75); padding: 4px 0; }
.cmd-pci-advanced > summary:hover { color: var(--text); }

/* ---- SimpleVirt HCI: one datastore, Virtual Objects, Health tiles, what-if strip ---- */
.cmd-hci-dsroles { display: flex; flex-direction: column; gap: 10px; }
.cmd-hci-dsrole { display: flex; align-items: baseline; gap: 12px; font-size: var(--fs-75); }
.cmd-hci-dsrole .cmd-hci-dslabel { flex: 0 0 110px; }
.cmd-hci-dstext { color: var(--text-dim); min-width: 0; overflow-wrap: anywhere; }

.cmd-hci-hz-head { flex-wrap: wrap; }
.cmd-hci-hz-actions { margin-left: auto; display: inline-flex; align-items: center; gap: 12px; }
.cmd-hci-hz-tile.is-ok .cmd-hci-stat-v { color: var(--ok); }
.cmd-hci-hz-tile.is-warn .cmd-hci-stat-v { color: var(--warn); }
.cmd-hci-hz-tile.is-err .cmd-hci-stat-v { color: var(--err); }
.cmd-hci-hz-tile.is-info .cmd-hci-stat-v { color: var(--info); }
.cmd-hci-hz-tile.is-unknown .cmd-hci-stat-v { color: var(--text-dim); }
.cmd-hci-hz-main { display: flex; align-items: baseline; gap: 8px; font-size: var(--fs-75); font-weight: 600; }
.cmd-hci-hz-main.is-err { color: var(--err); }
.cmd-hci-hz-main.is-warn { color: var(--warn); }
.cmd-hci-hz-main.is-ok { color: var(--ok); }
.cmd-hci-hz-main.is-checking, .cmd-hci-hz-main.is-expected { color: var(--text-dim); }
.cmd-hci-hz-mark { flex: 0 0 auto; min-width: 16px; }
.cmd-hci-hz-chip { margin-left: 4px; }
.cmd-hci-hz-group td { padding-top: 10px; padding-bottom: 10px; }
.cmd-hci-td-phase.is-expected { color: var(--text-dim); font-weight: 600; }

/* Health-score headline (hci_healthscore.go). Additive card above the findings; it hides itself
   (.hidden) when the controller sends no score, so the page degrades to its prior layout. */
.cmd-hci-score { display: flex; align-items: center; gap: 20px; flex-wrap: wrap; border: 1px solid var(--border); border-radius: var(--r-md); background: var(--bg-inset); padding: 12px 16px; color: var(--text); }
.cmd-hci-score.hidden { display: none; }
.cmd-hci-score.is-healthy { color: var(--ok); }
.cmd-hci-score.is-attention { color: var(--warn); }
.cmd-hci-score.is-critical { color: var(--err); }
.cmd-hci-score-main { display: flex; flex-direction: column; gap: 2px; }
.cmd-hci-score-figure { display: flex; align-items: baseline; }
.cmd-hci-score-num { font-size: var(--fs-800); font-weight: 700; line-height: 1.05; color: currentColor; }
.cmd-hci-score-max { font-size: var(--fs-75); font-weight: 600; color: var(--text-muted); }
.cmd-hci-score-grade { font-size: var(--fs-50); font-weight: 700; letter-spacing: .05em; color: currentColor; }
.cmd-hci-score-sparkwrap { flex: 1 1 160px; min-width: 120px; max-width: 320px; margin-left: auto; opacity: .85; }
.cmd-hci-score-sparkwrap.hidden { display: none; }
.cmd-hci-score-spark { display: block; width: 100%; height: 40px; }

.cmd-hci-whatif { border: 1px solid var(--border); border-radius: var(--r-md); background: var(--bg-inset); padding: 12px 15px; display: flex; flex-direction: column; gap: 8px; }
.cmd-hci-whatif-head { display: flex; align-items: baseline; justify-content: space-between; gap: 12px; }
.cmd-hci-whatif-head h4 { margin: 0; font-size: var(--fs-100); }
.cmd-hci-whatif-body { display: flex; flex-direction: column; gap: 8px; }
.cmd-hci-whatif-line { display: flex; align-items: flex-start; gap: 10px; font-size: var(--fs-75); }
.cmd-hci-whatif-mark { flex: 0 0 18px; text-align: center; font-weight: 700; }
.cmd-hci-whatif-text { min-width: 0; overflow-wrap: anywhere; }
.cmd-hci-whatif-line.is-safe .cmd-hci-whatif-mark { color: var(--ok); }
.cmd-hci-whatif-line.is-unsafe .cmd-hci-whatif-mark, .cmd-hci-whatif-line.is-unsafe .cmd-hci-whatif-text > div:first-child { color: var(--err); }
.cmd-hci-whatif-line.is-maintenance .cmd-hci-whatif-mark { color: var(--info); }
.cmd-hci-whatif-line.is-unknown { color: var(--text-dim); }
.cmd-hci-whatif-margin { border-top: 1px solid var(--border); padding-top: 8px; font-size: var(--fs-75); font-weight: 600; color: var(--text-dim); }
.cmd-hci-whatif-margin.is-one_more_failure { color: var(--ok); }
.cmd-hci-whatif-margin.is-none { color: var(--warn); }

.cmd-hci-sr { position: absolute; width: 1px; height: 1px; overflow: hidden; clip: rect(0 0 0 0); white-space: nowrap; }
.cmd-hci-obj-notes { display: flex; flex-direction: column; gap: 8px; }
.cmd-hci-obj-notes:empty, .cmd-hci-obj-strip:empty { display: none; }
.cmd-hci-obj-updated { margin-left: auto; }
.cmd-hci-obj-strip { display: grid; grid-template-columns: repeat(auto-fit, minmax(118px, 1fr)); gap: 12px; }
.cmd-hci-obj-tile.is-ok .cmd-hci-stat-v { color: var(--ok); }
.cmd-hci-obj-tile.is-warn .cmd-hci-stat-v { color: var(--warn); }
.cmd-hci-obj-tile.is-err .cmd-hci-stat-v { color: var(--err); }
.cmd-hci-obj-tile.is-info .cmd-hci-stat-v { color: var(--info); }
.cmd-hci-obj-tile.is-unknown .cmd-hci-stat-v { color: var(--text-dim); }
.cmd-hci-obj-vms { grid-column: 1 / -1; font-size: var(--fs-75); color: var(--text-dim); overflow-wrap: anywhere; }
.cmd-hci-obj-tools { display: flex; align-items: center; gap: 10px; flex-wrap: wrap; }
.cmd-hci-obj-tools .filter-input, .cmd-hci-obj-filter { min-width: 0; height: 32px; box-sizing: border-box; background: var(--bg-inset); border: 1px solid var(--border-strong); border-radius: var(--r-control); color: var(--text); font: inherit; font-size: var(--fs-100); padding: 0 var(--sp-3); }
.cmd-hci-obj-tools input.filter-input { width: 240px; }
.cmd-hci-obj-tools .filter-input::placeholder { color: var(--text-muted); }
.cmd-hci-obj-tools .filter-input:hover:not(:focus) { border-color: var(--border-focus); }
.cmd-hci-obj-tools .filter-input:focus { outline: none; border-color: var(--accent); box-shadow: 0 0 0 2px var(--focus-ring); }
.cmd-hci-obj-table > thead > tr > th, .cmd-hci-obj-table > .cmd-hci-obj-group > tr > td { padding: 0 8px; white-space: nowrap; }
.cmd-hci-obj-table > .cmd-hci-obj-group > tr > td.cmd-hci-obj-policy { white-space: normal; min-width: 128px; }
.cmd-hci-obj-nb { white-space: nowrap; }
.cmd-hci-obj-table > .cmd-hci-obj-group > tr > td.cmd-hci-obj-detailcell { white-space: normal; padding: 0; }
.cmd-hci-resil.is-info { color: var(--info); font-weight: 600; }
.cmd-hci-obj-count { margin-left: auto; }
/* A last-known (stale) group is marked without fading its text: opacity took the owner link and
   the Unknown pill to 2.4:1. Italic names and the banner above say "not live"; contrast stays. */
.cmd-hci-obj-table tbody.is-dim .cmd-hci-obj-row > td:first-child { font-style: italic; }
.cmd-hci-obj-group:not(:last-child) > tr:last-child > td { border-bottom: 1px solid var(--border-subtle); }
.cmd-hci-obj-row.is-inaccessible > td:first-child { box-shadow: inset 2px 0 0 var(--err); }
.cmd-hci-obj-row.is-reduced > td:first-child { box-shadow: inset 2px 0 0 var(--warn); }
.cmd-hci-obj-chip { margin-left: 8px; }
.cmd-hci-obj-caretcell { width: 34px; text-align: right; }
.cmd-hci-obj-caret { background: none; border: none; color: var(--text-dim); cursor: pointer; padding: 2px 8px; font-size: var(--fs-75); }
.cmd-hci-obj-caret:hover { color: var(--text); }
.cmd-hci-obj-caret:focus-visible { outline: 2px solid var(--focus-ring); outline-offset: 1px; }
.cmd-hci-obj-detailcell { background: var(--bg-inset); padding: 0; height: auto; }
/* width:0 + min-width:100%: the expanded detail fills the row but never widens the table's columns */
.cmd-hci-obj-detail { display: flex; flex-direction: column; gap: 12px; width: 0; min-width: 100%; box-sizing: border-box; padding: 14px 18px; }
.cmd-hci-obj-sentence { margin: 0; font-size: var(--fs-75); color: var(--text); }
.cmd-hci-obj-sep { margin: 0; font-size: var(--fs-75); }
.cmd-hci-obj-sep.is-ok { color: var(--ok); }
.cmd-hci-obj-sep.is-bad { color: var(--err); font-weight: 600; }
.cmd-hci-obj-why { display: flex; flex-direction: column; gap: 8px; align-items: flex-start; }
.cmd-hci-obj-why p { margin: 0; font-size: var(--fs-75); }
.cmd-hci-obj-sub { font-size: var(--fs-75); }
.cmd-hci-obj-loading { display: flex; align-items: center; gap: 8px; font-size: var(--fs-75); color: var(--text-dim); }
.cmd-hci-share-wrap { display: inline-flex; align-items: center; gap: 8px; }
.cmd-hci-share { -webkit-appearance: none; appearance: none; width: 110px; height: 8px; border: 0; border-radius: var(--r-sm); background: var(--bg-raised); overflow: hidden; }
.cmd-hci-share::-webkit-progress-bar { background: var(--border); border-radius: var(--r-sm); }
.cmd-hci-share::-webkit-progress-value { background: var(--ok); border-radius: var(--r-sm); }
.cmd-hci-share::-moz-progress-bar { background: var(--ok); border-radius: var(--r-sm); }
.cmd-hci-share-n { font-size: var(--fs-50); color: var(--text-dim); }
.cmd-hci-osdchip { display: inline-block; margin: 2px 6px 2px 0; padding: 1px 7px; border-radius: var(--r-sm); border: 1px solid var(--border); font-size: var(--fs-50); font-family: ui-monospace, monospace; color: var(--text-dim); }
.cmd-hci-osdchip.is-up { color: var(--ok); border-color: color-mix(in srgb, var(--ok) 35%, transparent); }
.cmd-hci-osdchip.is-down { color: var(--err); border-color: color-mix(in srgb, var(--err) 35%, transparent); }

@media (max-width: 720px) {
  .cmd-hci-dsrole { flex-direction: column; gap: 4px; }
  .cmd-hci-dsrole .cmd-hci-dslabel { flex: 0 0 auto; } /* a 110px basis is a HEIGHT once the row is a column */
  .cmd-hci-hz-actions { margin-left: 0; }
}

/* ---------- HCI review fixes: expected-during-maintenance, contrast, narrow dock ---------- */

/* The Datastores card's "expected during maintenance" chip and one-line note. The reduced
   badges stay; these only say why they are not a fault. */
.cmd-hci-dschip { margin-left: 2px; white-space: normal; height: auto; min-height: 22px; line-height: 1.3; padding-top: 2px; padding-bottom: 2px; }
.cmd-hci-dsmaint { margin: 0 0 var(--sp-2); }

/* Light theme: text on the tinted status surfaces (badges, pills, banners) at AA. The tone
   TOKENS stay as they are (dots, meters and keylines use them); only the TEXT that sits on a
   tint of the same hue is deepened: warn 3.5:1 -> 5.0:1, err 3.5:1 -> 5.1:1, neutral 3.9:1 -> 4.9:1,
   measured on the darkest surface these appear on (the canvas). */
:root[data-theme="light"] .badge-warn,
:root[data-theme="light"] .cmd-pill-warn,
:root[data-theme="light"] .cmd-banner-warn { color: #7a4f08; }
:root[data-theme="light"] .badge-err,
:root[data-theme="light"] .cmd-pill-err,
:root[data-theme="light"] .cmd-banner-err { color: #a3261d; }
:root[data-theme="light"] .cmd-subrail-btn.active { color: var(--info); }
:root[data-theme="light"] .cmd-hci-danger-toggle,
:root[data-theme="light"] .cmd-hci-hz-main.is-err { color: #a3261d; }
:root[data-theme="light"] .cmd-hci-hz-main.is-warn { color: #7a4f08; }
:root[data-theme="light"] .cmd-hci-whatif-line.is-unsafe .cmd-hci-whatif-mark,
:root[data-theme="light"] .cmd-hci-whatif-line.is-unsafe .cmd-hci-whatif-text > div:first-child { color: #a3261d; }
:root[data-theme="light"] .cmd-hci-whatif-margin.is-none { color: #7a4f08; }

/* Recent Tasks at phone width: the toolbar sits on its own row under the title instead of
   squeezing the title to nothing. */
@media (max-width: 520px) {
  .cmd-tasks:not(.collapsed) .cmd-tasks-bar { flex-wrap: wrap; height: auto; }
  .cmd-tasks:not(.collapsed) .cmd-tasks-head { flex: 1 1 8em; min-width: 8em; height: 34px; }
  .cmd-tasks:not(.collapsed) .cmd-tasks-caret { height: 34px; }
  .cmd-tasks:not(.collapsed) .cmd-tasks-tools { order: 3; flex: 1 0 100%; padding: 0 var(--sp-3) var(--sp-2); }
}

/* The sub-navigation rail at tablet and phone width. The stacked layout was declared BEFORE the
   base .cmd-subrail rule, so the base (a sticky column) won and the stuck rail covered the page
   under it. Declared here, after it, the rail is a wrapping row that scrolls with the page. */
@media (max-width: 900px) {
  .cmd-subrail { flex-direction: row; flex-wrap: wrap; position: static; padding-right: 0; border-right: none; }
}

/* ---------- live phase panel for an in-place host update (confirmApplyHost) ---------- */
/* The apply modal stops being a confirm form once the update is accepted and becomes this
   phase view: a thin progress bar plus a checklist that follows the task's named phases
   (evacuating → draining → applying → rebooting), so "what phase are we in" is always on
   screen and the operator never faces a blind, timing-out "Applying…". */
.cmd-apply-phases { margin-top: var(--sp-3); }
.cmd-apply-bar {
  position: relative;
  height: 6px;
  border-radius: 999px;
  background: var(--border);
  overflow: hidden;
}
.cmd-apply-bar-fill {
  position: absolute;
  inset: 0 auto 0 0;
  width: 0;
  border-radius: 999px;
  background: var(--accent);
  transition: width 0.4s ease;
}
.cmd-apply-bar.is-error .cmd-apply-bar-fill { background: var(--err); }
/* An indeterminate phase (no percent) sweeps rather than sitting frozen at a number. */
.cmd-apply-bar.is-indeterminate .cmd-apply-bar-fill {
  width: 35% !important;
  animation: cmd-apply-sweep 1.3s ease-in-out infinite;
}
@keyframes cmd-apply-sweep {
  0%   { left: -35%; }
  100% { left: 100%; }
}
.cmd-apply-steps {
  list-style: none;
  margin: var(--sp-3) 0 0;
  padding: 0;
  display: flex;
  flex-direction: column;
  gap: var(--sp-1);
}
.cmd-apply-step {
  display: flex;
  align-items: center;
  gap: var(--sp-2);
  font-size: var(--fs-75);
  color: var(--text-muted);
}
.cmd-apply-step.is-active { color: var(--text); }
.cmd-apply-step.is-error { color: var(--err); }
.cmd-apply-glyph {
  flex: none;
  width: 15px;
  height: 15px;
  display: inline-flex;
  align-items: center;
  justify-content: center;
  font-size: var(--fs-50);
  line-height: 1;
}
.cmd-apply-step.is-done .cmd-apply-glyph { color: var(--ok); }
.cmd-apply-step.is-error .cmd-apply-glyph { color: var(--err); }
/* The running step shows a live spinner in place of its glyph. */
.cmd-apply-step.is-active .cmd-apply-glyph {
  border: 2px solid var(--border);
  border-top-color: var(--accent);
  border-radius: 50%;
  animation: spin 0.7s linear infinite;
}
.cmd-apply-steptext { line-height: 1.35; }

/* ── Images Library: backing-store row + upload progress ─────────────────────── */
/* The backing store reads as one tidy line: a quiet label, the datastore as a
   status pill (health at a glance), an optional remediation hint, then the Change
   action pushed to the right — instead of a sentence the eye has to parse. */
.cmd-backing-row {
  display: flex;
  align-items: center;
  gap: var(--sp-3);
  flex-wrap: wrap;
}
.cmd-backing-label {
  font-size: var(--fs-75);
  font-weight: 600;
  color: var(--text-dim);
}
.cmd-backing-spacer { flex: 1 1 auto; }

/* Upload progress: a labelled bar with a live percent so the operator can SEE the
   transfer advancing and trust it is working, not a mystery spinner. */
.cmd-upload-progress { margin: var(--sp-2) 0 var(--sp-1); }
.cmd-upload-progress-head {
  display: flex;
  justify-content: space-between;
  align-items: baseline;
  font-size: var(--fs-75);
  color: var(--text-dim);
  margin-bottom: 6px;
}
.cmd-upload-pct { font-weight: 600; color: var(--text); font-variant-numeric: tabular-nums; }
.cmd-upload-bar {
  height: 6px;
  background: var(--bg-inset);
  border-radius: var(--r-status);
  overflow: hidden;
}
.cmd-upload-fill {
  height: 100%;
  width: 0;
  background: var(--accent);
  border-radius: var(--r-status);
  transition: width var(--dur-2) linear;
}
