/* Cloudify admin — shared design system.
   Airgap-safe: no external fonts/CDNs; icons are inline-SVG data-URIs used as
   CSS masks so they inherit currentColor. Keeps the established palette + Inter. */

:root {
  --bg: #0c0d0f; --bg-2: #101012;
  --surface: #17181b; --surface-2: #1d1f23; --surface-3: #232529;
  /* recessed/raised surfaces that were hardcoded ~40x across templates+JS */
  --surface-inset: #101113;   /* text inputs, wells */
  --surface-0: #0d0e11;       /* popovers, toasts, console bars */
  --on-green: #062b21;        /* text on a green (primary) fill */
  --border: #2c2f34; --border-soft: #24262b;
  --line: #24262b;            /* was referenced via var(--line,#23262b) but never defined */
  --text: #f4f5f6; --text-2: #c9cdd2; --muted: #8a9099;
  --green: #13d39e; --green-2: #0fa97e; --green-soft: rgba(19,211,158,.12);
  --amber: #f5a623; --red: #e5484d; --blue: #4aa3ff; --violet: #a78bfa;
  --font: "Inter", "Inter 28pt", -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, sans-serif;
  --mono: ui-monospace, SFMono-Regular, Menlo, monospace;
  --radius: 14px;
  --shadow: 0 1px 0 rgba(255,255,255,.03) inset, 0 12px 30px -12px rgba(0,0,0,.6);
  --shadow-lg: 0 24px 60px -20px rgba(0,0,0,.7);
  --appbar-h: 57px;
  --side-w: 240px;
  --rail-w: 62px;
}
* { box-sizing: border-box; }
html, body { min-height: 100%; }
/* app-like feel: no text I-beam over content; pointer only on interactive bits */
body { cursor: default; }
a, button, .btn, select, [role="button"], summary { cursor: pointer; }
input, textarea { cursor: text; }
body {
  margin: 0; font: 14px/1.55 var(--font); color: var(--text);
  background:
    radial-gradient(900px 520px at 12% -8%, rgba(19,211,158,.10), transparent 60%),
    radial-gradient(820px 560px at 108% 118%, rgba(74,163,255,.06), transparent 55%),
    var(--bg);
  background-attachment: fixed;
}
/* the constellation artwork, very low opacity, fixed behind everything */
body::before {
  content: ""; position: fixed; inset: 0; z-index: -1; pointer-events: none;
  background: url("bg-mesh.svg") center/cover no-repeat;
  opacity: .32;
  -webkit-mask-image: radial-gradient(120% 100% at 50% 0%, #000 40%, transparent 92%);
          mask-image: radial-gradient(120% 100% at 50% 0%, #000 40%, transparent 92%);
}
a { color: var(--green); text-decoration: none; }
a:hover { color: var(--green-2); }

/* ---- app header (glass) ---- */
.appbar {
  display: flex; align-items: center; gap: .9rem; padding: .8rem 1.5rem;
  border-bottom: 1px solid var(--border-soft); position: sticky; top: 0; z-index: 20;
  background: linear-gradient(180deg, rgba(20,21,24,.86), rgba(16,16,18,.72));
  backdrop-filter: blur(14px) saturate(1.2); -webkit-backdrop-filter: blur(14px) saturate(1.2);
}
.appbar::after {
  content: ""; position: absolute; left: 0; right: 0; bottom: -1px; height: 1px;
  background: linear-gradient(90deg, transparent, rgba(19,211,158,.5), transparent);
}
/* Brand lockup: the geometric mark (a text-free SVG) + "Atun" as live text in
   the app font. The retired logo drew the old product wordmark as vector paths,
   so renaming the file could never have fixed what it visually said. */
.appbar img.logo { height: 24px; display: block; }
.brandmark { display: inline-flex; align-items: center; gap: .5rem; }
.brandmark .wordmark {
  font-size: 1.02rem; font-weight: 700; letter-spacing: -.01em; color: var(--text);
  line-height: 1;
}
.appbar .spacer { flex: 1; }
.appbar .who { color: var(--muted); font-size: .82rem; }
.appbar .who b { color: var(--text-2); font-weight: 600; }
.tag {
  font-size: .62rem; font-weight: 700; letter-spacing: .09em; text-transform: uppercase;
  color: var(--green); border: 1px solid rgba(19,211,158,.35); background: var(--green-soft);
  padding: .18rem .5rem; border-radius: 999px;
}
.back { color: var(--muted); font-size: .85rem; display: inline-flex; align-items: center; gap: .35rem; }
.back:hover { color: var(--text); }
.btn {
  padding: .4rem .8rem; font: inherit; font-size: .8rem; cursor: pointer; color: var(--text-2);
  border: 1px solid var(--border); border-radius: 9px; background: rgba(255,255,255,.02);
  /* ⚠ justify-content IS NOT REDUNDANT on an inline-flex button. It only shows when the button is
     WIDER than its label, which happens wherever one is a direct child of a grid: a grid item
     stretches to its column, and the label then sat hard against the left padding with a gap of
     empty button after it. Reported on the tenant portal (Eslam 2026-10-11: "alot of them does not
     have the text centered, its shifted left") — e.g. "Download private key" in the `.kv` grid of
     My account, and every `.btn` the two planes put in a `.kv` or `.tiles` cell. Centring here
     fixes all of them at once instead of per-row, and changes nothing for a button that is only as
     wide as its content. */
  display: inline-flex; align-items: center; justify-content: center; gap: .4rem; transition: .15s;
}
.btn:hover { border-color: var(--muted); color: var(--text); background: rgba(255,255,255,.05); }
/* ★★ THE LABEL IS CENTRED, NOT THE ICON+LABEL PAIR (Eslam, 2026-10-11: "almost all buttons in portal do
   not have the text centered, the refresh, change password, filter, load older events" — and, shown the
   two readings, he picked "the label sits right of centre").
   ⛔ WHY `justify-content: center` WAS NOT ENOUGH, and why the earlier pass did not finish this: it
   centres the button's CONTENT as a unit. With a leading icon the unit is [icon][gap][label], so the
   WORD sits right of the button's middle by (icon+gap)/2 — exactly the four buttons he named, all four
   of which have an icon. Nothing is mis-aligned by the usual definition, which is why reading the rule
   found nothing; the complaint is about the LABEL's own centre, and it is a fair one.
   ⇒ A trailing spacer the same width as the icon balances it, so the label lands on the button's centre
     with the icon still at its left. ⚠ He chose to KEEP the button icons, so this is the way to give him
     both. It costs each icon button one icon-width of right-hand space, which is the space the
     centring needs to exist at all.
   ⚠ PROGRESSIVE: a browser without :has() simply gets today's layout, not a broken one.
   ⛔ AND NOT ON AN ICON-ONLY BUTTON, which has no label to centre — a spacer there would shove the lone
     icon off to the left. CSS cannot tell "icon + text node" from "icon alone" (the text is not an
     element), so those carry `ico-only` and a test refuses a new one that does not. */
.btn:has(> .ico:first-child):not(.ico-only)::after { content: ""; flex: none; width: 1.05em; }
/* Canonical primary button. Lived only in clusters.css/registry.css before, so
   `class="btn primary"` silently rendered as a plain grey .btn on every page
   that loaded neither (node, index, backups, settings, tenant-detail). */
.btn.primary {
  /* ⚠ TRANSPARENT 1px BORDER, NOT `border: 0`. Every other .btn carries a 1px
     border, so a borderless primary is 2px SHORTER than anything standing next to
     it — visible as mismatched heights wherever a primary and a plain/danger button
     share a row (the Backups page's "New instance…" beside "Delete…"). A
     transparent border keeps the gradient look identical and restores box parity
     everywhere at once, instead of patching each row that happens to pair them. */
  color: var(--on-green); border: 1px solid transparent; font-weight: 650;
  background: linear-gradient(180deg, var(--green), var(--green-2));
}
.btn.primary:hover { filter: brightness(1.06); color: var(--on-green); }

/* ---- text inputs / selects — THE HOUSE FIELD --------------------------------
   `.ti` existed only in clusters.css and registry.css, so a page that did not load one of those
   rendered stock browser fields: wrong font, wrong background, a native border on a dark panel
   (Eslam 2026-10-05 on the Licence page: "some fields are looking like stock html"). It is global
   now — the same trap the file-input rule above was moved here to fix. */
input.ti, select.ti, textarea.ti {
  width: 100%; padding: .56rem .7rem; font: inherit; font-size: .86rem; color: var(--text);
  background: var(--surface-inset); border: 1px solid var(--border); border-radius: 10px;
}
input.ti:focus, select.ti:focus, textarea.ti:focus {
  outline: none; border-color: var(--green); box-shadow: 0 0 0 3px var(--green-soft);
}
input.ti::placeholder { color: var(--muted); }

/* ---- file inputs -------------------------------------------------------------
   The browser default ("Choose File | no file selected") ignores every token on
   the page — wrong font, wrong colours, a grey OS button on a dark panel. Two
   file inputs exist (registry upload, settings bundle restore) and BOTH looked
   unstyled. Global, not page-local, so a third one is styled the day it is added
   — the `.detail-top`/`.scrim` page-local trap has cost this project twice.
   ::file-selector-button is the standard selector; ::-webkit-file-upload-button
   is kept for older WebKit. */
input[type=file] {
  width: 100%; font: inherit; font-size: .8rem; color: var(--text-2);
  border: 1px dashed var(--border); border-radius: 9px;
  padding: .45rem .55rem; background: rgba(255,255,255,.02);
  cursor: pointer; transition: .15s;
}
input[type=file]:hover { border-color: var(--muted); color: var(--text); }
input[type=file]::file-selector-button,
input[type=file]::-webkit-file-upload-button {
  font: inherit; font-size: .78rem; font-weight: 600; cursor: pointer;
  margin-right: .6rem; padding: .3rem .7rem; border-radius: 7px;
  border: 1px solid var(--border); color: var(--text-2);
  background: rgba(255,255,255,.04); transition: .15s;
}
input[type=file]:hover::file-selector-button,
input[type=file]:hover::-webkit-file-upload-button {
  border-color: var(--muted); color: var(--text);
}
/* Section/panel/card headers force their icons green. A primary button sitting
   inside one of those headers would render a green icon on a green fill, so the
   icon must inherit the button's own text colour instead. */
.btn.primary .ico,
.section-h .btn.primary .ico, .panel-h .btn.primary .ico, .card-h .btn.primary .ico { color: inherit; }
.btn.primary:disabled, .btn:disabled { opacity: .45; cursor: default; filter: none; }
.btn.danger { color: #ff9ea0; border-color: rgba(229,72,77,.4); }
.btn.danger:hover { background: rgba(229,72,77,.14); color: #ffb3b5; }

/* One focus ring for the whole app. Keyboard users get a visible ring even
   where a component clears the native outline. */
:where(a, button, .btn, select, input, textarea, [tabindex]):focus-visible {
  outline: 2px solid var(--green); outline-offset: 2px; border-radius: inherit;
}

/* ---- LICENSED SURFACES (plan item 5) ----------------------------------------
   lib.js puts `no-<feature>` on <body> when the licence does not include that addon, and these
   three rules are what make a `data-feature` element ABSENT. They are the load-bearing half, not
   a decoration of the JS sweep: every create wizard in this admin is built into the page when it
   is opened, so markup that appears AFTER the one-shot sweep (the VM wizard's GPU controls, the
   pool wizard's accelerator box) is gated only by a stylesheet rule that matches whenever it
   appears. `!important` on purpose — a page that sets `style.display = ''` on its own GPU box
   when the workload switch moves must not be able to reveal an unlicensed surface.
   ⚠ An UNKNOWN licence state sets none of these classes, so nothing is hidden and the operator
   keeps every tool while the server is unreachable (visibility fails open; the server's gate is
   the enforcement). */
body.no-accel [data-feature="accel"],
body.no-k8s [data-feature="k8s"],
body.no-services [data-feature="services"],
/* ★ 'portal' joined 2026-10-09 — the tenant portal is a licensed addon and the admin carries surfaces for
   it now (the OS-image publish strip). A feature whose rule is missing here is a surface that the one-shot
   JS sweep hides once and that comes back with the next page build. */
body.no-portal [data-feature="portal"] { display: none !important; }

/* Screen-reader-only text — gives icon-only controls and status dots a name. */
.vh {
  position: absolute; width: 1px; height: 1px; margin: -1px; padding: 0;
  overflow: hidden; clip: rect(0 0 0 0); clip-path: inset(50%); white-space: nowrap; border: 0;
}

/* One spinner. Was reimplemented 5 ways with @keyframes spin declared 4 times. */
.spinner {
  display: inline-block; width: var(--spinner-size, 12px); height: var(--spinner-size, 12px);
  border: 2px solid currentColor; border-right-color: transparent; border-radius: 50%;
  animation: spin .7s linear infinite; vertical-align: -1px;
}
.spinner.busy { border-color: rgba(245,166,35,.3); border-top-color: var(--amber); }
@keyframes spin { to { transform: rotate(360deg); } }

/* ---- shell: sidebar + content ---- */
.navtoggle { display: inline-flex; align-items: center; justify-content: center; width: 34px; height: 34px; border: 1px solid var(--border); border-radius: 9px; background: rgba(255,255,255,.02); color: var(--text-2); cursor: pointer; }
.navtoggle:hover { color: var(--text); border-color: var(--muted); }
.shell { display: flex; align-items: flex-start; }
.side {
  width: var(--side-w); flex-shrink: 0; position: sticky; top: var(--appbar-h);
  height: calc(100vh - var(--appbar-h)); overflow-y: auto; overflow-x: hidden;
  border-right: 1px solid var(--border-soft); background: linear-gradient(180deg, rgba(20,21,24,.55), rgba(14,14,16,.35));
  padding: .9rem .6rem 1.4rem; transition: width .16s ease;
}
.side::-webkit-scrollbar { width: 8px; } .side::-webkit-scrollbar-thumb { background: #2a2c31; border-radius: 8px; }
.shell > main, .shell > .content { flex: 1; min-width: 0; }
.grp { font-size: .64rem; letter-spacing: .1em; text-transform: uppercase; color: var(--muted); font-weight: 700; padding: .9rem .7rem .35rem; }
.grp:first-child { padding-top: .2rem; }
/* collapsible groups (accordion) — the .grp label is a real <button> inside a
   .nav-group wrapper, so it needs the element resets a button doesn't inherit,
   and it is STYLED as a button: its own surface, border and hover/open states. */
.nav-group { margin-bottom: .55rem; }          /* air between the top-level buttons */
.nav-group:last-child { margin-bottom: 0; }
.side .grp {
  display: flex; align-items: center; gap: .4rem; width: 100%; cursor: pointer;
  font-family: inherit; font-size: .76rem; letter-spacing: .06em; font-weight: 700;
  padding: .58rem .7rem; border-radius: 9px; text-align: left;
  background: rgba(255,255,255,.03); border: 1px solid var(--border-soft); color: var(--text-2);
  transition: background .14s ease, border-color .14s ease, color .14s ease;
}
.side .grp:hover { background: rgba(255,255,255,.06); border-color: var(--border); color: var(--text); }
.nav-group.open > .grp { background: rgba(255,255,255,.055); border-color: var(--border); color: var(--text); }
/* ⛔⛔ THE RAIL'S OWN LIST RESET BELONGS HERE, NOT ON EVERY <ul>. Both planes were emitting
   `<ul style="list-style:none;margin:0;padding:0">` — nav.js writes it, the portal's template copies
   it — so the rail depended on an inline STYLE ATTRIBUTE to not look like a bulleted list. The admin
   gets away with it because its CSP is report-only; the portal ENFORCES style-src, so the tenant menu
   rendered with bullet points and default indentation (Eslam: "it looks like shit"). A stylesheet rule
   needs no inline allowance and cannot be blocked. */
.side .nav-group > ul, .side .nav-sub > ul { list-style: none; margin: 0; padding: 0; }
/* ★ the one utility both planes were writing inline 25 times over */
.push-r { margin-left: auto; }
.nav-group > ul { display: none; }
/* sub-items sit indented under their button, with a guide line to read as children */
.nav-group.open > ul {
  display: block; margin: .3rem 0 0 .85rem; padding-left: .5rem;
  border-left: 1px solid var(--border-soft);
}
.grp .chev { margin-left: auto; width: .85rem; height: .85rem; opacity: .55; transition: transform .16s ease; }
.nav-group:not(.open) > .grp .chev { transform: rotate(-90deg); }
/* a collapsed group still shows where you are */
.nav-group.has-active:not(.open) > .grp { color: var(--green); border-color: var(--green-soft); }
/* ★ THIRD NAV LEVEL — Managed Services (2026-07-28). A .nav-sub is a level-2 row that
   TOGGLES instead of navigating; its <ul> holds the level-3 service links. Deliberately
   reuses .nav-item for the leaves (see leaf() in nav.js) so active/hover/soon styling can
   never drift between levels — the only difference is the indent below. */
.side .grp .grp-i { width: 1rem; height: 1rem; opacity: .8; flex-shrink: 0; }
.nav-sub > .sub-h {
  display: flex; align-items: center; gap: .65rem; width: 100%; cursor: pointer;
  font-family: inherit; font-size: .82rem; font-weight: 600; text-align: left;
  padding: .46rem .7rem; margin: .1rem 0; border-radius: 9px;
  background: none; border: 1px solid transparent; color: var(--text-2);
  transition: background .14s ease, color .14s ease;
}
.nav-sub > .sub-h:hover { background: rgba(255,255,255,.04); color: var(--text); }
.nav-sub > .sub-h .ico { flex-shrink: 0; width: 1.15rem; height: 1.15rem; opacity: .85; }
.nav-sub > .sub-h .txt { white-space: nowrap; }
/* the count sits where a leaf's SOON chip does, so the two columns line up */
.nav-sub > .sub-h .sub-n {
  margin-left: auto; font-size: .6rem; font-weight: 700; color: var(--muted);
  background: rgba(255,255,255,.05); border-radius: 999px; padding: .05rem .38rem; min-width: 1.1rem; text-align: center;
}
.nav-sub > .sub-h .chev { width: .8rem; height: .8rem; opacity: .5; margin-left: .3rem; transition: transform .16s ease; }
.nav-sub:not(.open) > .sub-h .chev { transform: rotate(-90deg); }
.nav-sub.has-active:not(.open) > .sub-h { color: var(--green); }
.nav-sub > ul { display: none; }
.nav-sub.open > ul { display: block; margin: .1rem 0 .25rem .95rem; padding-left: .55rem; border-left: 1px solid var(--border-soft); }
/* level-3 leaf: one indent step in, slightly smaller, and its active marker moves onto
   the inner guide line the same way .nav-group > ul does for level 2. */
.nav-item.sub-item { font-size: .82rem; padding: .4rem .6rem; }
.nav-item.sub-item .ico { width: 1rem; height: 1rem; opacity: .75; }
.nav-sub > ul .nav-item.active::before { left: -9px; }
.nav-item {
  display: flex; align-items: center; gap: .65rem; padding: .5rem .7rem; margin: .1rem 0; border-radius: 9px;
  color: var(--text-2); font-size: .86rem; font-weight: 500; white-space: nowrap; position: relative; cursor: pointer;
}
.nav-item:hover { background: rgba(255,255,255,.04); color: var(--text); }
.nav-item .ico { flex-shrink: 0; width: 1.15rem; height: 1.15rem; opacity: .85; }
.nav-item.active { color: var(--green); background: var(--green-soft); }
.nav-item.active::before { content: ""; position: absolute; left: -6px; top: 8px; bottom: 8px; width: 3px; border-radius: 3px; background: var(--green); }
/* inside a group the marker lands ON the guide line, so the active item lights up its segment */
.nav-group > ul .nav-item.active::before { left: -9px; }
.nav-item.soon { color: var(--muted); }
.nav-item.soon:hover { color: var(--text-2); }
.nav-item .soon-chip { margin-left: auto; font-size: .56rem; font-weight: 700; letter-spacing: .05em; color: var(--muted); border: 1px solid var(--border); border-radius: 5px; padding: .05rem .3rem; }
/* rail (whole-sidebar collapse) — ★ INERT since 2026-07-27: the toggle control was
   removed and nothing sets `body.rail` any more (the sidebar is fixed open, sections
   collapse individually instead). Kept so the mode can be revived without rewriting it. */
body.rail .side { width: var(--rail-w); padding: .9rem .5rem 1.4rem; }
/* in the rail there is no label to put on a button, and no room to indent under
   one — so the group button drops its chrome back to a plain "•••" separator. */
body.rail .side .grp { text-align: center; font-size: 0; padding: .55rem 0 .2rem; justify-content: center; background: none; border-color: transparent; }
body.rail .side .grp:hover { background: none; border-color: transparent; }
body.rail .side .grp::after { content: "•••"; font-size: .6rem; letter-spacing: .1em; color: var(--border); }
/* the rail has no labels to collapse under, so every group stays open there */
body.rail .nav-group { margin-bottom: .2rem; }
body.rail .nav-group > ul, body.rail .nav-group.open > ul { display: block; margin: 0; padding-left: 0; border-left: 0; }
body.rail .grp .chev { display: none; }
body.rail .nav-item { justify-content: center; gap: 0; padding: .55rem 0; }
body.rail .nav-item .txt, body.rail .nav-item .soon-chip { display: none; }
body.rail .nav-item.active::before { left: -5px; }

main { padding: 1.6rem; max-width: 1360px; margin: 0 auto; }

/* ---- section heading ---- */
.section-h { display: flex; align-items: center; gap: .55rem; font-weight: 650; font-size: .95rem; margin: 0 0 .85rem; }
.section-h .muted { color: var(--muted); font-weight: 400; font-size: .8rem; }
/* a SECOND heading inside one page (a dashboard's "Quota" band, say): same type, less weight, with the
   air a new band needs above it. Added 2026-10-09 for the tenant portal's dashboard. */
.section-h.sub { margin-top: 1.6rem; font-size: .88rem; color: var(--text-2); }
.section-h .ico { color: var(--green); }

/* ---- stat tiles ---- */
.tiles { display: grid; grid-template-columns: repeat(auto-fit, minmax(158px, 1fr)); gap: 1rem; margin-bottom: 1.6rem; }
.tile {
  position: relative; padding: 1rem 1.15rem; border-radius: var(--radius); overflow: hidden;
  border: 1px solid var(--border); background: linear-gradient(180deg, var(--surface-2), var(--surface));
  box-shadow: var(--shadow);
}
.tile::before { content: ""; position: absolute; inset: 0 auto 0 0; width: 3px; background: var(--muted); opacity: .5; }
.tile.up::before { background: var(--green); opacity: 1; }
.tile.down::before { background: var(--red); opacity: 1; }
.tile .thead { display: flex; align-items: center; gap: .45rem; color: var(--muted); font-size: .72rem; text-transform: uppercase; letter-spacing: .06em; }
.tile .n { font-size: 2rem; font-weight: 750; line-height: 1.15; margin-top: .35rem; letter-spacing: -.02em; }
.tile.up .n { color: var(--green); } .tile.down .n { color: var(--red); }

/* ---- panels / cards ---- */
.panel { border: 1px solid var(--border); border-radius: var(--radius); overflow: hidden; background: var(--surface); box-shadow: var(--shadow); }
.panel + .panel, .panel.my { margin-top: 1.4rem; }
.panel-h { padding: .85rem 1.15rem; border-bottom: 1px solid var(--border-soft); font-weight: 640; font-size: .9rem; display: flex; align-items: center; gap: .55rem; }
.panel-h .ico { color: var(--green); }
.panel-h .muted { color: var(--muted); font-weight: 400; font-size: .8rem; }
.card { border: 1px solid var(--border); border-radius: var(--radius); background: var(--surface); overflow: hidden; box-shadow: var(--shadow); }
.card-h { padding: .75rem 1rem; border-bottom: 1px solid var(--border-soft); font-weight: 620; font-size: .86rem; display: flex; align-items: center; gap: .5rem; }
.card-h .ico { color: var(--green); }
.card-h .muted { color: var(--muted); font-weight: 400; font-size: .78rem; }
.card-b { padding: .95rem 1rem; }

/* ---- tables ---- */
.scroll { overflow-x: auto; }
table { border-collapse: collapse; width: 100%; font-size: .85rem; white-space: nowrap; }
thead th { position: sticky; top: 0; }
th, td { padding: .58rem 1rem; border-bottom: 1px solid var(--border-soft); vertical-align: middle; }
/* Headers align LEFT to match the browser's native left-aligned <td>. A centered
   default made every header label float right of its own column's values, which
   read as "the values belong to the wrong header" — the bug had to be re-fixed
   per page and was still missed on tenant-detail / tenants-overview / manage.
   Opt a column out with .tl/.tc/.tr on BOTH its <th> and its <td>. */
th { font-size: .67rem; text-transform: uppercase; letter-spacing: .06em; color: var(--muted); font-weight: 650; text-align: left; background: var(--surface-2); }
th.tl, td.tl { text-align: left; }
th.tc, td.tc { text-align: center; }
th.tr, td.tr { text-align: right; }
tbody tr:last-child td { border-bottom: 0; }
tbody tr { transition: background .1s; }
tbody tr:hover { background: rgba(255,255,255,.025); }

/* fixed-layout + truncation helper — add `tbl-fixed` to a <table> and give each
   column a width; long cells then ellipsis instead of forcing a horizontal
   scrollbar. Cells that must stay full (an ops menu, a pill group) get `.no-ellipsis`.
   ⚠ THE NAME IN THIS COMMENT WAS WRONG UNTIL 2026-10-09 — it said `.nowrap-full`, a class no
   stylesheet has ever defined. The tenant portal's tables then used that name, taken from here, and
   two cells silently had no rule at all. A comment that names a class is part of the API. */
table.tbl-fixed { table-layout: fixed; }
table.tbl-fixed th, table.tbl-fixed td { overflow: hidden; text-overflow: ellipsis; }
table.tbl-fixed td.no-ellipsis { overflow: visible; text-overflow: clip; }
/* ★ PROMOTED OUT OF clusters.css (2026-10-09). A right-aligned, tabular-numeral monospace cell is a
   TABLE helper, not a clusters-page one — it was stranded in a page stylesheet, so any page that loads
   only this file (the tenant portal does) used the class and got nothing. Numbers that do not line up
   column to column are the most visible kind of "not symmetrical". */
.r-mono { font-family: var(--mono); font-variant-numeric: tabular-nums; }

/* ---------------------------------------------------------------------------
   MODALS + WIZARD — the dialog vocabulary every plane shares.
   ★ MOVED HERE FROM clusters.css ON 2026-10-09, carried verbatim (only the indentation changed).
   It was a page stylesheet's content and it is a design-system component: the tenant portal carries
   THIS file and nothing else, so a dialog built there would have rendered unstyled while still
   "working" — the exact failure the block's own comment records for the vm-detail migrate dialog.
   ⚠ Verbatim on purpose: nothing about the existing admin pages changes.
   --------------------------------------------------------------------------- */
/* modals + wizard */
/* ⚠ `safe center`, not plain `center`. A dialog TALLER than the viewport that is
 centred overflows in BOTH directions, and the overflowing top becomes
 unreachable — you cannot scroll up to it. The `safe` keyword falls back to
 start-alignment exactly in that case, so a short form is centred on screen
 (Eslam 2026-08-01) and a long one still scrolls from its top. */
/* ⚠ justify-content/align-content are NOT redundant with place-items here. They are the
 belt-and-braces for a scrim some JS shows with an inline `display:flex` (svc-create.js
 did exactly that, and flexbox IGNORES justify-items — the dialog pinned itself to the
 left edge). These two centre the content under EITHER layout mode, so a future inline
 display cannot silently un-centre a dialog again. */
.scrim{position:fixed;inset:0;z-index:100;background:rgba(0,0,0,.64);backdrop-filter:blur(3px);display:grid;place-items:safe center;justify-content:safe center;align-content:safe center;padding:2rem 1.5rem;overflow:auto}
.scrim[hidden]{display:none}
.wiz{width:min(620px,96vw);border:1px solid var(--border);border-radius:16px;background:var(--surface);box-shadow:var(--shadow-lg);overflow:hidden}
.wiz.sm{width:min(480px,96vw)}
.wiz.lg{width:min(1000px,97vw)}
.wiz-h .lg-auto{margin-left:auto;display:flex;align-items:center;gap:.35rem;font-size:.75rem;color:var(--muted);cursor:pointer}
.wiz-h .lg-auto+.x{margin-left:.4rem}
.logpre{margin:0;max-height:70vh;overflow:auto;background:#0b0c0f;color:#c7d0d9;font-family:var(--mono);font-size:.72rem;line-height:1.5;padding:.9rem 1rem;white-space:pre-wrap;word-break:break-word}
.wiz-h{display:flex;align-items:center;gap:.6rem;padding:1rem 1.15rem;border-bottom:1px solid var(--border-soft)}
.wiz-h h3{margin:0;font-size:1rem;font-weight:680}
.wiz-h.danger h3{color:#ffb4b6}
.wiz-h .x{margin-left:auto;width:28px;height:28px;border-radius:8px;border:1px solid var(--border);background:none;color:var(--muted);cursor:pointer}
.wiz-b{padding:1.15rem;max-height:66vh;overflow:auto}
.fld{font-size:.7rem;text-transform:uppercase;letter-spacing:.06em;color:var(--muted);margin:1rem 0 .4rem}
.fld:first-child{margin-top:0}
/* ---- an INLINE "label: control" filter pair -----------------------------------------------------
   ★ Eslam 2026-10-11, on the tenant portal's Activity Log: "the shape of the action and outcome
   should be inline, IE: Action: <drop down menu> same for outcome, if you can have both on the same
   line with propper separation and spacing go for it too."
   A bare label and control side by side in a .formrow are two independent flex items: they wrap
   apart, and the gap between a label and ITS OWN control is the same as the gap to the NEXT pair, so
   three controls on a line read as six unrelated things. .filt makes each pair one item — tight
   inside, generous between — and the row wraps by pair.
   ⚠ The label's .fld margins are vertical-stack margins; inside a pair they have to go, or the pair
   sits a centimetre below the control it names. */
.filt { display: inline-flex; align-items: center; gap: .45rem; }
.filt > .fld { margin: 0; white-space: nowrap; }
.filtrow { gap: 1.25rem; row-gap: .6rem; }
.filtrow > .btn, .filtrow > .sp { margin: 0; }
/* ⛔ Checkboxes are not text fields. The .ti rule below is class-scoped so it does not hit them
 today, but the VM wizard had an UNSCOPED `.modal input{width:100%}` that turned the driver
 checkbox into a full-width block — huge gap, one word per line, checkbox on its own line.
 Stating the intended shape here keeps that from being reintroduced by a wider selector. */
input[type=checkbox],input[type=radio]{width:auto;padding:0;margin:0 .45rem 0 0;vertical-align:-1px;accent-color:var(--green);cursor:pointer}
input.ti,select.ti{width:100%;padding:.56rem .7rem;font:inherit;color:var(--text);background:var(--surface-inset);border:1px solid var(--border);border-radius:10px}
input.ti:focus,select.ti:focus{border-color:var(--green)} input.ti:focus:not(:focus-visible),select.ti:focus:not(:focus-visible){outline:none}
input.ti.bad{border-color:var(--red)}
.pfx{display:flex;align-items:center;background:var(--surface-inset);border:1px solid var(--border);border-radius:10px;overflow:hidden}
.pfx span{padding:.56rem .1rem .56rem .7rem;color:var(--muted);font-family:var(--mono)}
.pfx input{border:0;background:none;flex:1}
.seg{display:flex;gap:.45rem;flex-wrap:wrap}
.seg button{flex:1;min-width:80px;padding:.5rem .4rem;font:inherit;font-size:.82rem;border:1px solid var(--border);border-radius:10px;background:var(--surface-inset);color:var(--text-2);cursor:pointer}
.seg button.on{color:#04241b;background:linear-gradient(180deg,var(--green),var(--green-2));border-color:transparent;font-weight:660}
.seg.tone button.on{background:var(--surface-3);color:var(--text);border:1px solid var(--green)}
.chips{display:flex;gap:.4rem;flex-wrap:wrap}
.chips button{padding:.4rem .7rem;font:inherit;font-size:.8rem;border:1px solid var(--border);border-radius:9px;background:var(--surface-inset);color:var(--text-2);cursor:pointer}
.chips button.on{color:#04241b;background:linear-gradient(180deg,var(--green),var(--green-2));border-color:transparent;font-weight:660}
.row2{display:grid;grid-template-columns:1fr 1fr;gap:.8rem}
.note{font-size:.75rem;color:var(--muted);line-height:1.5;margin-top:.45rem}
.summ{margin-top:1rem;border:1px solid rgba(19,211,158,.3);background:rgba(19,211,158,.12);border-radius:12px;padding:.8rem .9rem;font-size:.82rem;color:var(--text-2);line-height:1.5}
.summ b{color:var(--text)}
.verify{margin-top:.9rem;padding:.8rem .9rem;border-radius:12px;border:1px solid var(--border);background:var(--surface-2)}
.verify.ok{border-color:rgba(19,211,158,.45);background:rgba(19,211,158,.1)}
.verify.bad{border-color:var(--red);background:rgba(229,72,77,.08)}
.verify .vh{font-weight:660;font-size:.85rem}.verify.ok .vh{color:var(--green)}.verify.bad .vh{color:#ffb4b6}
.vcheck{display:flex;gap:.5rem;font-size:.79rem;color:var(--text-2);margin-top:.45rem}
.vcheck .m{font-weight:800}.vcheck .m.ok{color:var(--green)}.vcheck .m.bad{color:var(--red)}
.vfix{margin-top:.55rem;font-size:.76rem;color:#ffcfd0}
.warn-box{border:1px solid var(--red);background:rgba(229,72,77,.09);border-radius:11px;padding:.75rem .9rem;margin-bottom:.9rem;color:#ffcccd;font-size:.82rem}
.cfm code{font-family:var(--mono);color:var(--amber)}
.wiz-f{display:flex;align-items:center;gap:.7rem;padding:.9rem 1.15rem;border-top:1px solid var(--border-soft)}
.wiz-f .sp{flex:1}
.stepper{display:flex;align-items:center;justify-content:center;gap:.6rem;margin:.4rem 0}
.stepper button{width:36px;height:34px;border:1px solid var(--border);border-radius:9px;background:var(--surface-2);color:var(--text);font-size:1.2rem;cursor:pointer}
.stepper .n{min-width:46px;text-align:center;font-size:1.4rem;font-weight:700}
.steps{display:flex;flex-direction:column;gap:.5rem;margin-top:.6rem}
.step{display:flex;align-items:center;gap:.6rem;font-size:.8rem;color:var(--text-2)}
.step .n{width:22px;height:22px;border-radius:50%;border:1px solid var(--border);display:grid;place-items:center;font-size:.7rem;color:var(--muted)}

/* ---- shared "Ops" row-action popup menu (was inline-duplicated in manage /
   tenant-vms; promoted here so every page uses the same component). The menu is
   position:fixed + JS-positioned so it never spawns a scrollbar inside a
   .scroll wrapper. Keep the .opsmenu class name — nav.js uiBusy() keys on it. */
.ops { position: relative; display: inline-flex; }
.opsbtn { display: inline-flex; align-items: center; justify-content: center; width: 28px; height: 26px;
  border: 1px solid var(--border); border-radius: 8px; background: rgba(255,255,255,.03); color: var(--text-2); cursor: pointer; }
.opsbtn:hover { color: var(--text); border-color: var(--muted); }
.opsmenu { position: fixed; z-index: 40; min-width: 176px; padding: .3rem; background: #0d0e11;
  border: 1px solid var(--border); border-radius: 10px; box-shadow: var(--shadow-lg); }
.opsmenu[hidden] { display: none; }
.opsmenu button { display: flex; align-items: center; gap: .55rem; width: 100%; text-align: left; font: inherit;
  font-size: .82rem; color: var(--text-2); background: none; border: 0; padding: .45rem .55rem; border-radius: 7px; cursor: pointer; }
.opsmenu button:hover { background: rgba(255,255,255,.05); color: var(--text); }
.opsmenu button.danger { color: #ff9ea0; }
.opsmenu button.danger:hover { background: rgba(229,72,77,.14); }
.opsmenu button:disabled { opacity: .4; cursor: default; }
.opsmenu .sep { height: 1px; background: var(--border-soft); margin: .25rem .2rem; }
.opsmenu .ico { width: .95rem; height: .95rem; }

/* ---- status dot / chips / badges / pills ---- */
.dot { display: inline-block; width: .62rem; height: .62rem; border-radius: 50%; }
.dot.up { background: var(--green); box-shadow: 0 0 9px rgba(19,211,158,.7); }
.dot.down { background: #4a2b2c; border: 1px solid var(--red); }
.chip { display: inline-flex; align-items: center; gap: .3rem; font-size: .72rem; padding: .13rem .5rem; border-radius: 7px; color: var(--text-2); background: var(--surface-3); border: 1px solid var(--border); }
.chip .ico { opacity: .8; }
.egip { font-family: ui-monospace, monospace; font-size: .78rem; color: var(--blue); }
.chip.compute { color: #9fe6d4; } .chip.gpu { color: var(--violet); } .chip.cphost { color: var(--blue); } .chip.network { color: #7dd3fc; } .chip.mixed { color: var(--amber); } .chip.controlplane { color: var(--green); }
/* agent debug-on flag (fleet views) */
.dbgb { font-size: .58rem; font-weight: 700; letter-spacing: .04em; color: var(--amber); border: 1px solid rgba(245,166,35,.45); background: rgba(245,166,35,.12); border-radius: 5px; padding: .05rem .3rem; vertical-align: 1px; }
.state { font-size: .78rem; color: var(--text-2); }
.state.enrolled, .state.prepped { color: var(--amber); }
.state.decommissioned, .state.removed, .state.cordoned { color: var(--muted); }
.state.decommissioning { color: var(--amber); }  /* in progress: agent cleanup + reboot */
.state.ok { color: var(--green); } .state.bad { color: var(--red); }
.badge { font-size: .63rem; font-weight: 700; letter-spacing: .04em; padding: .12rem .45rem; border-radius: 6px; margin-left: .4rem; vertical-align: middle; display: inline-flex; align-items: center; gap: .25rem; }
.badge.cordoned { color: var(--amber); border: 1px solid rgba(245,166,35,.4); background: rgba(245,166,35,.12); }
.badge.notready { color: var(--red); border: 1px solid rgba(229,72,77,.4); background: rgba(229,72,77,.12); }
.badge.drift { color: var(--red); border: 1px solid rgba(229,72,77,.5); background: rgba(229,72,77,.14); }
/* was #fff on --red = 3.91:1, under the 4.5:1 floor for this small bold text */
.badge.conflict { color: #2a0507; border: 1px solid #ff8b8e; background: #ff8b8e; }
.pill { font-size: .7rem; font-weight: 600; padding: .12rem .5rem; border-radius: 999px; border: 1px solid var(--border); color: var(--text-2); }
.pill.ok { color: var(--green); border-color: rgba(19,211,158,.4); background: var(--green-soft); }
.pill.warn { color: var(--amber); border-color: rgba(245,166,35,.4); background: rgba(245,166,35,.12); }
.pill.bad { color: var(--red); border-color: rgba(229,72,77,.4); background: rgba(229,72,77,.12); }
.cert.soon { color: var(--amber); } .cert.crit { color: var(--red); }
.seen { color: var(--muted); font-size: .8rem; }
.mono { font-family: var(--mono); font-size: .82rem; }
a.nodelink { color: var(--text); border-bottom: 1px dotted color-mix(in srgb, var(--green) 55%, transparent); }
a.nodelink:hover { color: var(--green); }

/* ---- resource meter cell ---- */
.meter { display: flex; flex-direction: column; gap: .24rem; min-width: 104px; }
.meter .mlbl { font-size: .78rem; color: var(--text-2); }
.meter .bar { height: 5px; border-radius: 4px; background: #2a2c30; overflow: hidden; }
.meter .bar > i { display: block; height: 100%; background: linear-gradient(90deg, var(--green-2), var(--green)); border-radius: 4px; }
.meter .bar > i.warn { background: linear-gradient(90deg, #c8871a, var(--amber)); }
.meter .bar > i.crit { background: linear-gradient(90deg, #b83a3e, var(--red)); }

/* ---- icons (CSS mask, inherit currentColor) ---- */
.ico { display: inline-block; width: 1.05em; height: 1.05em; background: currentColor; vertical-align: -.16em;
  -webkit-mask: var(--i) center/contain no-repeat; mask: var(--i) center/contain no-repeat; }
.ico-server{--i:url('data:image/svg+xml;utf8,<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="none" stroke="black" stroke-width="1.8" stroke-linecap="round"><rect x="3" y="4" width="18" height="7" rx="1.5"/><rect x="3" y="13" width="18" height="7" rx="1.5"/><circle cx="7" cy="7.5" r=".6" fill="black"/><circle cx="7" cy="16.5" r=".6" fill="black"/></svg>');}
.ico-up{--i:url('data:image/svg+xml;utf8,<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="none" stroke="black" stroke-width="1.8" stroke-linecap="round" stroke-linejoin="round"><path d="M20 6 9 17l-5-5"/></svg>');}
.ico-down{--i:url('data:image/svg+xml;utf8,<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="none" stroke="black" stroke-width="1.8" stroke-linecap="round"><path d="M18 6 6 18M6 6l12 12"/></svg>');}
.ico-cpu{--i:url('data:image/svg+xml;utf8,<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="none" stroke="black" stroke-width="1.8" stroke-linejoin="round"><rect x="7" y="7" width="10" height="10" rx="1.5"/><path d="M9 2v3M15 2v3M9 19v3M15 19v3M2 9h3M2 15h3M19 9h3M19 15h3" stroke-linecap="round"/></svg>');}
.ico-mem{--i:url('data:image/svg+xml;utf8,<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="none" stroke="black" stroke-width="1.8" stroke-linejoin="round"><rect x="3" y="7" width="18" height="10" rx="1.5"/><path d="M7 17v2M12 17v2M17 17v2M7 11v2M12 11v2M17 11v2" stroke-linecap="round"/></svg>');}
.ico-disk{--i:url('data:image/svg+xml;utf8,<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="none" stroke="black" stroke-width="1.8"><circle cx="12" cy="12" r="9"/><circle cx="12" cy="12" r="3"/><path d="M12 3v6" stroke-linecap="round"/></svg>');}
.ico-shield{--i:url('data:image/svg+xml;utf8,<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="none" stroke="black" stroke-width="1.8" stroke-linejoin="round"><path d="M12 3l7 3v5c0 4.5-3 7.5-7 9-4-1.5-7-4.5-7-9V6z"/></svg>');}
.ico-clock{--i:url('data:image/svg+xml;utf8,<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="none" stroke="black" stroke-width="1.8" stroke-linecap="round"><circle cx="12" cy="12" r="9"/><path d="M12 7v5l3 2"/></svg>');}
.ico-cert{--i:url('data:image/svg+xml;utf8,<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="none" stroke="black" stroke-width="1.8" stroke-linejoin="round"><circle cx="12" cy="9" r="5"/><path d="M9 13l-2 8 5-3 5 3-2-8" stroke-linecap="round"/></svg>');}
.ico-network{--i:url('data:image/svg+xml;utf8,<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="none" stroke="black" stroke-width="1.8"><circle cx="5" cy="6" r="2"/><circle cx="19" cy="6" r="2"/><circle cx="12" cy="18" r="2"/><path d="M6.5 7.5 11 16M17.5 7.5 13 16M7 6h10" stroke-linecap="round"/></svg>');}
/* ★ Managed Services (2026-07-28). Four icons the set was missing — same stroke-only 24x24
   shape language as the rest, so they mask identically through .ico's currentColor mask. */
/* ★ 2026-07-28 (Eslam): EVERY nav entry above the service leaves must carry its OWN icon —
   28 slots, and the set only had 15 distinct ones that suit a nav. These eight close the gap.
   Same stroke-only 24x24 language so they mask identically; no # or % (the data URI is
   unescaped utf8) and no apostrophes (the url() is single-quoted). */
/* ★ 2026-07-28 (Eslam): the 17 SERVICE LEAVES get their own icons too, so all 45 nav entries
   are unique. Deliberately NOT vendor logos (trademark, and they do not survive a 16px mask):
   each glyph states the engine's DATA MODEL or role, and the six relational-ish engines get
   distinct SHAPES rather than cylinder-variants — the plain cylinder belongs to the parent
   "Database Services", so a leaf reusing it would read as the section, not the product. */
.ico-dbrel{--i:url('data:image/svg+xml;utf8,<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="none" stroke="black" stroke-width="1.8" stroke-linejoin="round"><rect x="3" y="4" width="18" height="16" rx="1.8"/><path d="M3 9h18M3 14.5h18M9.5 9v11"/></svg>');}
.ico-dbwave{--i:url('data:image/svg+xml;utf8,<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="none" stroke="black" stroke-width="1.8" stroke-linejoin="round"><rect x="3" y="4" width="18" height="16" rx="1.8"/><path d="M6 10c2-2 4 2 6 0s4-2 6 0M6 15c2-2 4 2 6 0s4-2 6 0" stroke-linecap="round"/></svg>');}
.ico-dbfork{--i:url('data:image/svg+xml;utf8,<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="none" stroke="black" stroke-width="1.8" stroke-linecap="round"><path d="M12 21V12M12 12L6.5 6.5M12 12l5.5-5.5"/><circle cx="5" cy="5" r="1.8"/><circle cx="19" cy="5" r="1.8"/></svg>');}
.ico-dbglobe{--i:url('data:image/svg+xml;utf8,<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="none" stroke="black" stroke-width="1.8"><circle cx="12" cy="12" r="9"/><path d="M3 12h18"/><ellipse cx="12" cy="12" rx="4" ry="9"/></svg>');}
.ico-leaf{--i:url('data:image/svg+xml;utf8,<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="none" stroke="black" stroke-width="1.8" stroke-linejoin="round"><path d="M20 4c0 9-6 15-15 15 0-9 6-15 15-15z"/><path d="M5.5 18.5c4-4 7.5-6.5 11.5-8.5" stroke-linecap="round"/></svg>');}
.ico-ring{--i:url('data:image/svg+xml;utf8,<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="none" stroke="black" stroke-width="1.8"><circle cx="12" cy="12" r="7.5"/><circle cx="12" cy="4.5" r="1.7"/><circle cx="18.5" cy="15.8" r="1.7"/><circle cx="5.5" cy="15.8" r="1.7"/></svg>');}
.ico-vector{--i:url('data:image/svg+xml;utf8,<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="none" stroke="black" stroke-width="1.8" stroke-linecap="round" stroke-linejoin="round"><path d="M4.5 19.5L18 6"/><path d="M18 6h-5M18 6v5"/><circle cx="8.5" cy="9" r="1.4"/><circle cx="15.5" cy="17" r="1.4"/></svg>');}
.ico-bolt{--i:url('data:image/svg+xml;utf8,<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="none" stroke="black" stroke-width="1.8" stroke-linejoin="round"><path d="M13 2.5L5 13.5h6l-2 8 8-11h-6z"/></svg>');}
.ico-slabs{--i:url('data:image/svg+xml;utf8,<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="none" stroke="black" stroke-width="1.8" stroke-linejoin="round"><rect x="3" y="4" width="18" height="4" rx="1.2"/><rect x="3" y="10" width="18" height="4" rx="1.2"/><rect x="3" y="16" width="18" height="4" rx="1.2"/></svg>');}
.ico-stream{--i:url('data:image/svg+xml;utf8,<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="none" stroke="black" stroke-width="1.8" stroke-linecap="round" stroke-linejoin="round"><path d="M3 7h11l-2.5-2.5M3 12h15l-2.5-2.5M3 17h8l-2.5-2.5"/></svg>');}
.ico-envelope{--i:url('data:image/svg+xml;utf8,<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="none" stroke="black" stroke-width="1.8" stroke-linejoin="round"><rect x="3" y="5" width="18" height="14" rx="2"/><path d="M3.5 7.5l8.5 6 8.5-6" stroke-linecap="round"/></svg>');}
.ico-inbox{--i:url('data:image/svg+xml;utf8,<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="none" stroke="black" stroke-width="1.8" stroke-linejoin="round"><path d="M3 13l3-8h12l3 8v5a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2z"/><path d="M3 13h5l1.5 3h5L16 13h5"/></svg>');}
.ico-lens{--i:url('data:image/svg+xml;utf8,<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="none" stroke="black" stroke-width="1.8" stroke-linecap="round"><path d="M4 6h9M4 10h6M4 14h4.5"/><circle cx="15.5" cy="14" r="4.8"/><path d="M19.2 17.6L21.5 20"/></svg>');}
.ico-compass{--i:url('data:image/svg+xml;utf8,<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="none" stroke="black" stroke-width="1.8" stroke-linejoin="round"><circle cx="12" cy="12" r="9"/><path d="M15.8 8.2l-2.2 5.4-5.4 2.2 2.2-5.4z"/></svg>');}
.ico-bucket{--i:url('data:image/svg+xml;utf8,<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="none" stroke="black" stroke-width="1.8" stroke-linejoin="round"><path d="M4 7.5h16l-1.6 12a2 2 0 0 1-2 1.7H7.6a2 2 0 0 1-2-1.7z"/><ellipse cx="12" cy="7" rx="8" ry="2.6"/></svg>');}
.ico-crate{--i:url('data:image/svg+xml;utf8,<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="none" stroke="black" stroke-width="1.8" stroke-linejoin="round"><rect x="3" y="6" width="18" height="14" rx="1.6"/><path d="M3 11h18M9 6v14M15 6v14"/></svg>');}
.ico-pulse{--i:url('data:image/svg+xml;utf8,<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="none" stroke="black" stroke-width="1.8" stroke-linecap="round" stroke-linejoin="round"><path d="M3 12h4l3-7 4 14 3-7h4"/></svg>');}
.ico-org{--i:url('data:image/svg+xml;utf8,<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="none" stroke="black" stroke-width="1.8" stroke-linejoin="round"><rect x="4" y="3" width="16" height="18" rx="1.6"/><path d="M9 7h2M13 7h2M9 11h2M13 11h2M9 15h2M13 15h2" stroke-linecap="round"/></svg>');}
.ico-gauge{--i:url('data:image/svg+xml;utf8,<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="none" stroke="black" stroke-width="1.8" stroke-linecap="round"><path d="M4 18a8 8 0 1 1 16 0"/><path d="M12 18l4.5-5.5"/></svg>');}
.ico-vm{--i:url('data:image/svg+xml;utf8,<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="none" stroke="black" stroke-width="1.8" stroke-linejoin="round"><rect x="3" y="4" width="18" height="12" rx="1.8"/><path d="M8.5 20h7M12 16v4" stroke-linecap="round"/></svg>');}
.ico-link{--i:url('data:image/svg+xml;utf8,<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="none" stroke="black" stroke-width="1.8" stroke-linecap="round"><path d="M10.5 13.5a4 4 0 0 0 5.7 0l2-2a4 4 0 0 0-5.7-5.7l-1 1"/><path d="M13.5 10.5a4 4 0 0 0-5.7 0l-2 2a4 4 0 0 0 5.7 5.7l1-1"/></svg>');}
.ico-archive{--i:url('data:image/svg+xml;utf8,<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="none" stroke="black" stroke-width="1.8" stroke-linejoin="round"><rect x="3" y="4" width="18" height="4" rx="1.2"/><path d="M5 8v11a1.5 1.5 0 0 0 1.5 1.5h11A1.5 1.5 0 0 0 19 19V8"/><path d="M10 12h4" stroke-linecap="round"/></svg>');}
.ico-ruler{--i:url('data:image/svg+xml;utf8,<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="none" stroke="black" stroke-width="1.8" stroke-linejoin="round"><rect x="2.5" y="8" width="19" height="8" rx="1.5"/><path d="M7 8v3M11 8v4M15 8v3M19 8v4" stroke-linecap="round"/></svg>');}
.ico-sliders{--i:url('data:image/svg+xml;utf8,<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="none" stroke="black" stroke-width="1.8" stroke-linecap="round"><path d="M4 7h9M19 7h1M4 17h5M15 17h5"/><circle cx="16" cy="7" r="2.2"/><circle cx="12" cy="17" r="2.2"/></svg>');}
.ico-services{--i:url('data:image/svg+xml;utf8,<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="none" stroke="black" stroke-width="1.8" stroke-linejoin="round"><rect x="3" y="3" width="8" height="8" rx="1.6"/><rect x="13" y="3" width="8" height="8" rx="1.6"/><rect x="3" y="13" width="8" height="8" rx="1.6"/><circle cx="17" cy="17" r="4"/></svg>');}
.ico-database{--i:url('data:image/svg+xml;utf8,<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="none" stroke="black" stroke-width="1.8"><ellipse cx="12" cy="6" rx="8" ry="3"/><path d="M4 6v12c0 1.7 3.6 3 8 3s8-1.3 8-3V6"/><path d="M4 12c0 1.7 3.6 3 8 3s8-1.3 8-3"/></svg>');}
.ico-queue{--i:url('data:image/svg+xml;utf8,<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="none" stroke="black" stroke-width="1.8" stroke-linejoin="round"><rect x="2.5" y="5" width="5" height="14" rx="1.4"/><rect x="9.5" y="5" width="5" height="14" rx="1.4"/><rect x="16.5" y="5" width="5" height="14" rx="1.4"/></svg>');}
.ico-search{--i:url('data:image/svg+xml;utf8,<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="none" stroke="black" stroke-width="1.8" stroke-linecap="round"><circle cx="11" cy="11" r="7"/><path d="M20 20l-4.4-4.4"/></svg>');}
.ico-layers{--i:url('data:image/svg+xml;utf8,<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="none" stroke="black" stroke-width="1.8" stroke-linejoin="round"><path d="M12 3l9 5-9 5-9-5z"/><path d="M3 13l9 5 9-5"/></svg>');}
.ico-gpu{--i:url('data:image/svg+xml;utf8,<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="none" stroke="black" stroke-width="1.8" stroke-linejoin="round"><rect x="2" y="6" width="20" height="12" rx="2"/><circle cx="8" cy="12" r="2.4"/><circle cx="15" cy="12" r="2.4"/></svg>');}
.ico-history{--i:url('data:image/svg+xml;utf8,<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="none" stroke="black" stroke-width="1.8" stroke-linecap="round"><path d="M3 12a9 9 0 1 0 3-6.7L3 8"/><path d="M3 3v5h5"/><path d="M12 8v4l3 2"/></svg>');}
.ico-logs{--i:url('data:image/svg+xml;utf8,<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="none" stroke="black" stroke-width="1.8" stroke-linecap="round" stroke-linejoin="round"><path d="M14 3H7a2 2 0 0 0-2 2v14a2 2 0 0 0 2 2h10a2 2 0 0 0 2-2V8z"/><path d="M14 3v5h5"/><path d="M8.5 13h7M8.5 16.5h7"/></svg>');}
.ico-chev{--i:url('data:image/svg+xml;utf8,<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="none" stroke="black" stroke-width="2.2" stroke-linecap="round" stroke-linejoin="round"><path d="M6 9l6 6 6-6"/></svg>');}
.ico-dots{--i:url('data:image/svg+xml;utf8,<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="black"><circle cx="12" cy="5" r="1.7"/><circle cx="12" cy="12" r="1.7"/><circle cx="12" cy="19" r="1.7"/></svg>');}
.ico-wrench{--i:url('data:image/svg+xml;utf8,<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="none" stroke="black" stroke-width="1.8" stroke-linecap="round" stroke-linejoin="round"><path d="M14.7 6.3a4 4 0 0 0-5.4 5.2L3 17.8 6.2 21l6.3-6.3a4 4 0 0 0 5.2-5.4l-2.6 2.6-2.3-.6-.6-2.3z"/></svg>');}
.ico-check{--i:url('data:image/svg+xml;utf8,<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="none" stroke="black" stroke-width="2.4" stroke-linecap="round" stroke-linejoin="round"><path d="M20 6 9 17l-5-5"/></svg>');}
.ico-power{--i:url('data:image/svg+xml;utf8,<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="none" stroke="black" stroke-width="1.9" stroke-linecap="round" stroke-linejoin="round"><path d="M12 3v9"/><path d="M6.4 6.4a8 8 0 1 0 11.2 0"/></svg>');}
.ico-trash{--i:url('data:image/svg+xml;utf8,<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="none" stroke="black" stroke-width="1.8" stroke-linecap="round" stroke-linejoin="round"><path d="M4 7h16M9 7V4h6v3M6 7l1 13h10l1-13"/></svg>');}
.ico-trend{--i:url('data:image/svg+xml;utf8,<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="none" stroke="black" stroke-width="1.8" stroke-linecap="round" stroke-linejoin="round"><path d="M3 15l5-5 4 3 6-7"/><path d="M21 6v4h-4"/></svg>');}
.ico-box{--i:url('data:image/svg+xml;utf8,<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="none" stroke="black" stroke-width="1.8" stroke-linejoin="round"><path d="M21 8l-9-5-9 5 9 5 9-5z"/><path d="M3 8v8l9 5 9-5V8"/></svg>');}
.ico-alert{--i:url('data:image/svg+xml;utf8,<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="none" stroke="black" stroke-width="1.8" stroke-linecap="round" stroke-linejoin="round"><path d="M12 3 2 20h20z"/><path d="M12 9v5M12 17.5v.5"/></svg>');}
.ico-info{--i:url('data:image/svg+xml;utf8,<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="none" stroke="black" stroke-width="1.8" stroke-linecap="round"><circle cx="12" cy="12" r="9"/><path d="M12 11v5M12 8v.5"/></svg>');}
.ico-fleet{--i:url('data:image/svg+xml;utf8,<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="none" stroke="black" stroke-width="1.8" stroke-linejoin="round"><rect x="3" y="4" width="7" height="7" rx="1.2"/><rect x="14" y="4" width="7" height="7" rx="1.2"/><rect x="3" y="14" width="7" height="6" rx="1.2"/><rect x="14" y="14" width="7" height="6" rx="1.2"/></svg>');}
.ico-users{--i:url('data:image/svg+xml;utf8,<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="none" stroke="black" stroke-width="1.8" stroke-linecap="round" stroke-linejoin="round"><circle cx="9" cy="8" r="3.2"/><path d="M3.5 20a5.5 5.5 0 0 1 11 0"/><path d="M16 5.2a3.2 3.2 0 0 1 0 6.1M17.5 14.4A5.5 5.5 0 0 1 20.5 20"/></svg>');}
.ico-gear{--i:url('data:image/svg+xml;utf8,<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="none" stroke="black" stroke-width="1.8" stroke-linejoin="round"><circle cx="12" cy="12" r="3"/><path d="M12 2v3M12 19v3M2 12h3M19 12h3M4.9 4.9l2.1 2.1M17 17l2.1 2.1M19.1 4.9L17 7M7 17l-2.1 2.1"/></svg>');}
.ico-menu{--i:url('data:image/svg+xml;utf8,<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="none" stroke="black" stroke-width="1.9" stroke-linecap="round"><path d="M4 7h16M4 12h16M4 17h16"/></svg>');}
.ico-plus{--i:url('data:image/svg+xml;utf8,<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="none" stroke="black" stroke-width="1.9" stroke-linecap="round"><path d="M12 5v14M5 12h14"/></svg>');}
.ico-grid{--i:url('data:image/svg+xml;utf8,<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="none" stroke="black" stroke-width="1.8" stroke-linejoin="round"><rect x="3" y="3" width="7" height="7" rx="1.2"/><rect x="14" y="3" width="7" height="7" rx="1.2"/><rect x="3" y="14" width="7" height="7" rx="1.2"/><rect x="14" y="14" width="7" height="7" rx="1.2"/></svg>');}

/* ---- filter bar ---- */
.filterbar { display: flex; gap: .6rem; align-items: center; padding: .7rem 1.1rem; border-bottom: 1px solid var(--border-soft); flex-wrap: wrap; }
.filterbar .field { position: relative; display: inline-flex; align-items: center; }
.filterbar .field .ico { position: absolute; left: .55rem; color: var(--muted); pointer-events: none; }
.filterbar input, .filterbar select {
  font: inherit; font-size: .82rem; color: var(--text); background: #101113;
  border: 1px solid var(--border); border-radius: 9px; padding: .44rem .6rem;
}
.filterbar input { min-width: 220px; padding-left: 2rem; }
.filterbar input:focus, .filterbar select:focus { outline: none; border-color: var(--green); box-shadow: 0 0 0 3px var(--green-soft); }
.filterbar .grow { flex: 1; }
.filterbar .cnt { color: var(--muted); font-size: .78rem; white-space: nowrap; }
.filterbar .clear { color: var(--muted); font-size: .78rem; background: none; border: 0; padding: .2rem .4rem; }
.filterbar .clear:hover { color: var(--text); }
.pager { display: flex; align-items: center; justify-content: flex-end; gap: .9rem; padding: .6rem 1.1rem; border-top: 1px solid var(--border-soft); }
.pager:empty { display: none; }
.pg-info { color: var(--muted); font-size: .78rem; white-space: nowrap; font-variant-numeric: tabular-nums; }
.pg-btn { font-size: .76rem; padding: .28rem .7rem; }

/* ---- tooltip (single fixed panel, centered in the header band) ---- */
th[data-tip] { text-decoration: underline dotted color-mix(in srgb, var(--text) 30%, transparent); text-underline-offset: 3px; }
#tipbox {
  position: fixed; top: 11px; left: 50%; z-index: 250; max-width: 360px; padding: .5rem .85rem;
  font: 400 .74rem/1.5 var(--font); color: var(--text-2); white-space: pre-line; text-align: center;
  background: rgba(8,8,10,.96); border: 1px solid var(--border); border-radius: 11px; box-shadow: var(--shadow-lg);
  opacity: 0; transform: translate(-50%, -5px); transition: opacity .13s, transform .13s; pointer-events: none;
}
#tipbox.show { opacity: 1; transform: translate(-50%, 0); }

.note { margin-top: 1.3rem; font-size: .76rem; color: #63666c; }
.empty { color: var(--muted); font-size: .82rem; padding: .3rem 0; }
/* ⛔⛔ AN EMPTY STATE IN A TABLE CELL HAD NO SIDE PADDING, AND THE CLASS IS WHY (Eslam, 2026-10-11:
   "the texts inside the elemnts in general does not have padding from its perant, for example (This
   tenant holds no load balancers.)"). `th, td` sets `padding: .58rem 1rem`, but that is a TYPE
   selector — specificity 0,0,1 — and `.empty` is a CLASS at 0,1,0, so `padding: .3rem 0` won and the
   sentence sat hard against the cell's left edge. The rule above is right for the `<div class="empty">`
   form (it has no cell padding to keep); it was never meant to reach a `<td>`.
   ⇒ The cell form gets the cell's own horizontal padding back, and a little MORE vertical room than a
   data row, because an empty state is a whole-table message rather than one value among many.
   ⚠ 32 cells in the admin and every "this tenant holds no …" line in the portal, fixed in one place
   instead of per table. */
th.empty, td.empty { padding: .9rem 1rem; }

/* The appbar logo doubles as "home" — it navigates to Fleet Status (the root).
   display:flex keeps the img metrics identical to the un-wrapped version. */
.appbar a.logo-home { display: flex; align-items: center; text-decoration: none; cursor: pointer; }
.appbar a.logo-home:hover { opacity: .85; }

/* control-plane / fleet nice-name button + k8s-ready inline marker */
/* opacity:.5 on --muted rendered ~2.33:1 — an enabled control below AA. Dim via
   colour instead so the contrast is knowable. */
.nick-btn { border: none; background: none; cursor: pointer; color: #7c828b; font-size: .78rem; padding: 0 .25rem; margin-left: .15rem; transition: .12s; }
.nick-btn:hover { color: var(--green); opacity: 1; }
.kok { color: var(--green); font-size: .78rem; }

/* nice-name modal (mid-page, HTML) */
.nmodal { position: fixed; inset: 0; z-index: 300; background: rgba(0,0,0,.55); backdrop-filter: blur(2px); display: grid; place-items: center; padding: 2rem; }
.nmodal[hidden] { display: none; }
.nmodal-box { width: min(440px, 94vw); border: 1px solid var(--border); border-radius: 14px; background: var(--surface); box-shadow: var(--shadow-lg); overflow: hidden; }
.nmodal-h { display: flex; align-items: center; justify-content: space-between; gap: 1rem; padding: .8rem 1rem; border-bottom: 1px solid var(--border-soft); font-weight: 600; font-size: .88rem; }
.nmodal-h .ico { color: var(--green); }
.nmodal-x { border: none; background: none; color: var(--muted); cursor: pointer; font-size: 1rem; line-height: 1; }
.nmodal-x:hover { color: var(--text); }
.nmodal-body { padding: 1rem; }
.nmodal-body input { width: 100%; padding: .55rem .7rem; border: 1px solid var(--border); border-radius: 8px; background: var(--surface-2); color: var(--text); font: inherit; box-sizing: border-box; }
.nmodal-body input:focus { outline: none; border-color: var(--green); }
.nmodal-body .hint { font-size: .74rem; color: var(--muted); margin: .5rem 0 0; }
.nmodal-actions { display: flex; justify-content: flex-end; gap: .6rem; margin-top: 1rem; }

/* platform control-plane services — a FIXED 3×3 grid of identical blocks (Eslam 2026-10-05):
   row 1 server · admin · auth, row 2 the three licensed addons, row 3 the data services + debug.
   `grid-auto-rows: 1fr` makes every ROW the height of the tallest tile and the min-height keeps a
   short tile ("Not licensed") exactly as tall as a full one, so no block changes size with its
   content. The tile is a column with the sub-line pushed to the bottom, which is what keeps the
   three lines on the same baselines across all nine. */
.cpv-tiles { display: grid; grid-template-columns: repeat(3, minmax(0, 1fr)); grid-auto-rows: 1fr; gap: .8rem; margin-bottom: 1.4rem; }
.cpv-tiles .tile { min-width: 0; min-height: 6.2rem; padding: .8rem .85rem; display: flex; flex-direction: column; }
.cpv-tiles .tile .cpv-sub { margin-top: auto; }
.cpv-tiles .n.cpv-n { font-size: 1.2rem; margin-top: .3rem; font-family: var(--mono); }
.cpv-tiles .n.cpv-dbgn { font-family: inherit; font-size: 1rem; }
.cpv-sub { font-size: .72rem; color: var(--muted); margin-top: .4rem; white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }
/* ⚠ A TINY BUTTON STILL NEEDS VERTICAL PADDING (Eslam, 2026-10-11: "some even needs a bit more padding
   on top bottom or/and sides to seperate them from other elements"). This was `height: 24px` with
   `padding: 0 .6rem` — zero top and bottom — so the label had no room of its own and the button read as
   cramped against whatever it sat beside: Refresh, Load older events, and the 26 others in the portal.
   ⇒ Real padding, and `min-height` rather than `height` so the 24px floor that existing rows are built
   around is kept while a button whose content needs more can take it instead of clipping. */
.btn.tiny { min-height: 24px; padding: .24rem .65rem; font-size: .72rem; }
/* narrow screens keep equal blocks, just fewer per row */
@media (max-width: 900px){ .cpv-tiles { grid-template-columns: repeat(2, minmax(0, 1fr)); } }
@media (max-width: 560px){ .cpv-tiles { grid-template-columns: minmax(0, 1fr); } }

/* needs-attention band */
.attn { display: flex; flex-wrap: wrap; align-items: center; gap: .6rem; padding: .6rem .9rem; margin-bottom: 1.4rem; border: 1px solid var(--border); border-radius: 10px; background: var(--surface-2); }
.attn-h { display: inline-flex; align-items: center; gap: .45rem; font-weight: 650; font-size: .8rem; color: var(--text-2); }
.attn-h .ico { color: var(--amber); }
.attn-item { font-size: .76rem; padding: .18rem .6rem; border-radius: 999px; border: 1px solid var(--border); }
.attn-item.bad { color: var(--red); border-color: var(--red); background: rgba(220,60,60,.08); }
.attn-item.warn { color: var(--amber); border-color: var(--amber); background: rgba(217,154,43,.08); }
.attn-item.drift { color: var(--violet); border-color: var(--violet); background: rgba(192,96,208,.08); }
.attn-node { color: inherit; text-decoration: underline; text-underline-offset: 2px; }
.attn-node:hover { opacity: .75; }
.vwarn { color: var(--amber); font-weight: 600; }

/* (recent-activity grid removed 2026-07-28 — .recent-list/.recent-row/.rc-name/.rc-node/.rc-by/
   .rc-when styled a widget no template or script builds. These are LITERAL compound class names,
   never assembled from data, so "no reference" really does mean unused here — unlike .cni.flannel
   or .chip.compute below, which ARE built as "cni "+cluster.cni / "chip "+node_type and would look
   equally unreferenced to a grep. Do not sweep those. */

/* resource scheduler/live toggle */
.res-toggle { display: inline-flex; align-items: center; gap: .45rem; font-size: .72rem; color: var(--muted); }
.rsegs { display: inline-flex; }
.rseg { border: 1px solid var(--border); background: none; color: var(--muted); font: inherit; font-size: .72rem; padding: .12rem .5rem; cursor: pointer; }
.rseg:first-child { border-radius: 6px 0 0 6px; border-right: none; }
.rseg:last-child { border-radius: 0 6px 6px 0; }
.rseg.on + .rseg { border-left-color: var(--green); }
.rseg.on { color: var(--green); border-color: var(--green); background: var(--green-soft, rgba(60,200,120,.1)); }

/* clickable table-row name → enriched detail view */
.rowlink{color:var(--green);text-decoration:none;font-weight:640}
.rowlink:hover{text-decoration:underline}
/* ⚠ THE SAME CLASS ON A <button>, for a plane that has no URL to link to. The tenant portal serves
   one URL per pane (its pane list is derived from the verb allow-list), so a cluster's own page is a
   VIEW inside the pane and its opener cannot be an <a href>. A button keeps the keyboard and the
   semantics; these four lines take the browser's chrome off it so it reads as the same link. */
button.rowlink{padding:0;border:0;background:none;font:inherit;cursor:pointer;text-align:left}
button.rowlink:focus-visible{outline:2px solid var(--green);outline-offset:2px;border-radius:4px}

/* ★ NATIVE <select> POPUPS DO NOT INHERIT THE SELECT'S BACKGROUND (2026-07-28).
   The OPTION list is rendered by the browser on its OWN default surface — white — while
   inheriting the page's --text, which on this dark theme is near-white. Result: white text on a
   white list, i.e. an invisible dropdown. MEASURED on the VM live-migrate picker.
   ⚠ NOT specific to that dialog: NO select in this admin styled its options, so every dropdown
   carried the same latent bug (the backup restore-point picker included). One rule, global —
   options are otherwise unstyled everywhere, so this cannot collide with anything. */
select option,
select optgroup {
  background-color: var(--surface-2);
  color: var(--text);
}

/* ---- shared modal (.scrim2 / .modal2) -------------------------------------
   Moved here from registry.css 2026-08-08. It was page-local, so Settings had no
   modal at all and the mirror roll fell back to a native confirm() — a browser
   dialog in the middle of an otherwise styled UI. Any page may use it now. */
/* modal (k8s add + confirmations) */
.scrim2 { position: fixed; inset: 0; background: rgba(0,0,0,.55); display: flex;
  align-items: center; justify-content: center; z-index: 50; }
.scrim2[hidden] { display: none; }
.modal2 { width: min(560px, 92vw); max-height: 80vh; display: flex; flex-direction: column;
  background: var(--surface); border: 1px solid var(--border); border-radius: var(--radius);
  box-shadow: var(--shadow-lg); overflow: hidden; }
.modal2-h { padding: .9rem 1.1rem; border-bottom: 1px solid var(--border-soft); font-weight: 640; }
.modal2-b { padding: 1rem 1.1rem; overflow: auto; }
.modal2-f { padding: .8rem 1.1rem; border-top: 1px solid var(--border-soft);
  display: flex; gap: .5rem; align-items: center; }
.modal2-f .sp { flex: 1; }
