/*
 * VITALS stylesheet.
 *
 * Light theme, violet accent, white sidebar with collapsible sections and a user
 * block pinned at the foot. Top-level sections (Operations, Monitoring, Admin)
 * live in the sidebar with their features nested beneath, so everything stays
 * visible as the feature set grows.
 *
 * Dense but airy: the reader is an SRE or FDE examining a cluster they may not
 * know, so scannable data beats decoration.
 *
 * No framework, no build step: one file served from the JAR.
 */

:root {
    /* Surfaces */
    --bg:            #ffffff;
    --bg-page:       #f7f9f9;
    --bg-sidebar:    #fcfdfd;
    --bg-subtle:     #f1f5f4;
    --bg-hover:      #eaf2f1;
    --bg-inset:      #f9fbfb;

    /* Card surface: mostly opaque so it stays crisp under the new layered shadows,
       while still letting a hint of the ambient wash tint through. One token, so
       every card/tile/panel shares the exact same surface. */
    --surface:       color-mix(in srgb, var(--bg) 94%, transparent);

    /* Lines */
    --border:        #e4eaea;
    --border-strong: #d2dada;

    /* Text: warm-neutral ink rather than pure black.
       Secondary tones darkened for more contrast ("brighter"/crisper) — the dim
       and faint greys drove the washed-out look. Revert to #5a6465 / #8a9394 to
       restore the softer original. */
    --text:          #141a1a;
    --text-dim:      #454f50;
    --text-faint:    #6d7778;

    /*
      Ambient wash behind the whole app. Keeps large light surfaces from reading
      as flat paper, without tinting content.
    */
    --wash:
        radial-gradient(1200px 620px at 78% -12%, rgba(62, 156, 147, 0.10) 0%, transparent 62%),
        radial-gradient(880px 520px at 4% 108%, rgba(201, 162, 39, 0.07) 0%, transparent 60%);

    --shadow-color: rgba(20, 32, 32, 0.06);
    --scrim: rgba(255, 255, 255, 0.72);

    /*
      Brand gradient: teal to sage to gold. Emphasis in the wordmark comes from
      weight contrast rather than colour, so the gradient is reserved for the
      mark, the active nav tile, and accent details.
    */
    /* Brand gradient: sunset — coral → orange → gold. Used on the Sign in button, the
       wordmark, badges, the loader and the selected nav icon. The dark theme inherits these,
       so both light and dark share this sunset sweep. */
    --grad-start:    #e05656;
    --grad-mid:      #e8873a;
    --grad-end:      #e0b429;
    --brand-gradient: linear-gradient(120deg,
                        var(--grad-start) 0%,
                        var(--grad-mid) 50%,
                        var(--grad-end) 100%);

    /* Accent: the teal end of the gradient carries interactive state */
    --accent:        #2f8b83;
    --accent-hover:  #257771;
    --accent-soft:   #eaf4f2;
    --accent-line:   #c9e2de;
    --gold:          #C9A227;

    /* Themed table/border tint. Derived from the active theme's brand mid-tone
       (--grad-mid: sunset orange on light/dark, teal on graphite, azure on navy) mixed
       toward the surface so it reads as a tasteful coloured line, not a loud border.
       Because it references --grad-mid, which every theme overrides, it follows the theme
       automatically. Used for the boxed-cell table's outer border, its filter controls,
       and each cell chip. */
    --table-accent-border: color-mix(in srgb, var(--grad-mid) 42%, var(--border));

    /* Logo mark gradient stops — the sunset palette, matching --brand-gradient. Light (and
       dark, inherited) share these; other themes override the three below. */
    --logo-a: #e05656;
    --logo-b: #e8873a;
    --logo-c: #e0b429;

    /* Status */
    --ok:            #2f8b6b;
    --ok-soft:       #eaf5f0;
    --warn:          #a2731a;
    --warn-soft:     #fbf4e4;
    --error:         #c4362b;
    --error-soft:    #fbedeb;
    --info:          #2f7392;
    --info-soft:     #eaf2f6;

    --mono: ui-monospace, SFMono-Regular, "SF Mono", Menlo, Consolas, monospace;
    --sans: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, "Helvetica Neue", Arial, sans-serif;

    --radius:     10px;
    --radius-sm:  7px;
    --sidebar-w:  236px;
    --topbar-h:   52px;

    /*
      Layered, soft shadows. A near-transparent ambient layer plus a tighter
      contact layer reads as depth on paper rather than a hard 1px line — the
      difference between "boxed" enterprise UI and a modern floating surface.
      Every card, table and panel inherits these, so lifting them here lifts the
      whole app at once.
    */
    --shadow-sm: 0 1px 2px rgba(20, 32, 32, 0.04), 0 1px 3px rgba(20, 32, 32, 0.05);
    --shadow:    0 2px 4px rgba(20, 32, 32, 0.05), 0 4px 12px rgba(20, 32, 32, 0.06);
    --shadow-md: 0 4px 10px rgba(20, 32, 32, 0.07), 0 10px 28px rgba(20, 32, 32, 0.08);

    /* 3D lift tokens: a white lit edge and a faint accent-tinted surface gradient. Light
       theme keeps it subtle. Never grey — the tint follows the brand teal. */
    --lift-top:       color-mix(in srgb, #ffffff 85%, transparent);
    --lift-surface-a: color-mix(in srgb, var(--accent) 5%, var(--surface));
    --lift-surface-b: var(--surface);
    --lift-hover-a:   color-mix(in srgb, var(--accent) 9%, var(--surface));
    --lift-hover-b:   color-mix(in srgb, var(--accent) 3%, var(--surface));
}

/*
  Graphite theme — a true medium-grey world, not a tint of the white theme.

  Everything moves into confident cool greys: a graphite page, lighter grey cards that sit
  on it as raised panels, and a slightly darker sidebar. The interactive accent shifts from
  the light theme's teal to a steel-blue, and the brand gradient becomes steel → slate-blue,
  so even the coloured elements say "graphite" rather than borrowing the light palette. Ink
  stays near-black for contrast. This is a distinct fourth world, clearly not Light.
  (data-theme value kept as "slate" so no wiring changes.)
*/
[data-theme="slate"] {
    /* Inverted elevation: the page is the LIGHTEST grey, and cards/menus sit DARKER, so
       surfaces read as recessed panels rather than glaring white ones. */
    --bg:            #d3d8de;
    --bg-page:       #dfe3e8;   /* page — the lightest grey */
    --bg-sidebar:    #cdd3da;
    --bg-subtle:     #c4ccd4;
    --bg-hover:      #b9c2cc;
    --bg-inset:      #c9cfd6;

    --surface:       #c9d0d8;   /* card — a clear step DARKER than the page */

    --border:        #a9b3bf;
    --border-strong: #8c98a7;

    --text:          #1a2230;
    --text-dim:      #46525f;
    --text-faint:    #66717f;

    /* Cool steel wash rather than the teal/gold of light. */
    --wash:
        radial-gradient(1200px 620px at 78% -12%, rgba(79, 109, 143, 0.14) 0%, transparent 62%),
        radial-gradient(880px 520px at 4% 108%, rgba(90, 104, 128, 0.10) 0%, transparent 60%);

    --shadow-color: rgba(40, 52, 70, 0.16);
    --scrim: rgba(207, 212, 218, 0.76);

    /* Grey cards on a graphite page carry a real, defined shadow — this is what gives the
       theme its solid, panelled feel. */
    --shadow-sm: 0 1px 2px rgba(40, 52, 70, 0.12), 0 1px 3px rgba(40, 52, 70, 0.12);
    --shadow:    0 2px 6px rgba(40, 52, 70, 0.15), 0 6px 16px rgba(40, 52, 70, 0.14);
    --shadow-md: 0 6px 16px rgba(40, 52, 70, 0.18), 0 16px 34px rgba(40, 52, 70, 0.20);

    /* Steel-blue accent — distinct from the light theme's teal. */
    --accent:        #4f6d8f;
    --accent-hover:  #3f5c7d;
    --accent-soft:   rgba(79, 109, 143, 0.14);
    --accent-line:   rgba(79, 109, 143, 0.34);
    --gold:          #6a7c93;

    /* Logo mark: teal → deep blue, matching this theme's brand gradient. */
    --logo-a: #2f8b83;
    --logo-b: #2c7188;
    --logo-c: #2a5d8f;

    /* Brand gradient: teal → deep blue (tryout option 2). Used on the Sign in button, the
       wordmark, badges, the loader and the selected nav icon in this grey theme. */
    --grad-start:    #2f8b83;
    --grad-mid:      #2c7188;
    --grad-end:      #2a5d8f;
    --brand-gradient: linear-gradient(120deg,
                        var(--grad-start) 0%,
                        var(--grad-mid) 50%,
                        var(--grad-end) 100%);

    /* Status colours deepened slightly so they hold up on grey rather than looking pastel. */
    --ok:            #2f8b6b;
    --ok-soft:       rgba(47, 139, 107, 0.14);
    --warn:          #9a6f18;
    --warn-soft:     rgba(154, 111, 24, 0.14);
    --error:         #bd382c;
    --error-soft:    rgba(189, 56, 44, 0.14);
    --info:          #3a6e8c;
    --info-soft:     rgba(58, 110, 140, 0.14);

    /* Cards are DARKER than the page, so the surface gradient stays in the darker grey
       (a gentle top-to-bottom deepen) rather than mixing toward white. A faint light top
       edge still catches the "lit rim", and hover deepens slightly for a pressed feel. */
    --lift-top:       color-mix(in srgb, #ffffff 40%, transparent);
    --lift-surface-a: var(--surface);
    --lift-surface-b: color-mix(in srgb, #000000 5%, var(--surface));
    --lift-hover-a:   color-mix(in srgb, #000000 3%, var(--surface));
    --lift-hover-b:   color-mix(in srgb, #000000 8%, var(--surface));
}

/*
  Dark theme.
  Deep blue-black rather than neutral grey, with a teal glow from the top edge.
  Surfaces sit only slightly above the background and rely on hairline borders,
  which keeps dense tables readable without heavy contrast steps.
*/
[data-theme="dark"] {
    --bg:            #101820;
    --bg-page:       #0a1016;
    --bg-sidebar:    #0c131a;
    --bg-subtle:     #16202a;
    --bg-hover:      #182430;
    --bg-inset:      #0e161d;

    /* Slightly lifted above the page on dark, for a subtle elevation cue. */
    --surface:       #131d27;

    --border:        #1e2a35;
    --border-strong: #2b3a47;

    /* Secondary tones lifted for more contrast; revert to #9aa8b0 / #6b7a84. */
    --text:          #f2f6f7;
    --text-dim:      #b3c0c7;
    --text-faint:    #85939c;

    --wash:
        radial-gradient(1300px 680px at 74% -16%, rgba(62, 156, 147, 0.20) 0%, transparent 60%),
        radial-gradient(900px 520px at 2% 106%, rgba(201, 162, 39, 0.08) 0%, transparent 58%);

    --shadow-color: rgba(0, 0, 0, 0.4);
    --scrim: rgba(10, 16, 22, 0.72);

    /* On dark, depth comes from deeper ambient shadow rather than contact lines. */
    --shadow-sm: 0 1px 2px rgba(0, 0, 0, 0.30), 0 1px 3px rgba(0, 0, 0, 0.22);
    --shadow:    0 2px 6px rgba(0, 0, 0, 0.34), 0 6px 16px rgba(0, 0, 0, 0.30);
    --shadow-md: 0 6px 16px rgba(0, 0, 0, 0.40), 0 14px 34px rgba(0, 0, 0, 0.38);

    /* Teal reads brighter on dark, so interactive accents shift up a step */
    --accent:        #4db6aa;
    --accent-hover:  #63c6ba;
    --accent-soft:   rgba(77, 182, 170, 0.14);
    --accent-line:   rgba(77, 182, 170, 0.34);

    --ok:            #4cb98b;
    --ok-soft:       rgba(76, 185, 139, 0.14);
    --warn:          #d3a24a;
    --warn-soft:     rgba(211, 162, 74, 0.14);
    --error:         #e0685c;
    --error-soft:    rgba(224, 104, 92, 0.14);
    --info:          #5aa7c7;
    --info-soft:     rgba(90, 167, 199, 0.14);

    /* 3D lift on dark: a teal-tinted lit edge and a teal lift on the card surface, so cards
       rise as "brighter/tealer", not grey. */
    --lift-top:       color-mix(in srgb, var(--accent) 45%, transparent);
    --lift-surface-a: color-mix(in srgb, var(--accent) 14%, var(--surface));
    --lift-surface-b: var(--surface);
    --lift-hover-a:   color-mix(in srgb, var(--accent) 22%, var(--surface));
    --lift-hover-b:   color-mix(in srgb, var(--accent) 8%, var(--surface));
}

/*
  Navy blue theme — a fresh, self-contained blue world.

  Rich royal-navy surfaces with clear elevation steps between the page and the cards, a
  bright sky-blue accent, and a blue-family brand gradient (indigo → azure → cyan) that
  replaces the teal→gold sweep on buttons and the wordmark. Depth (the 3D look) comes from
  blue-tinted shadows and a blue lift on card surfaces — never grey — via the shared
  --lift-* tokens defined here and consumed by the 3D rules at the foot of this file.
*/
[data-theme="navy"] {
    /* Surfaces: a deliberate ladder from the darkest page up to the lightest raised card,
       all in blue, so elevation reads as "closer/bluer-lit" rather than "greyer". */
    --bg:            #16294d;   /* base card/background blue */
    --bg-page:       #0b1730;   /* darkest — the page behind everything */
    --bg-sidebar:    #0f1f3f;
    --bg-subtle:     #1d3360;
    --bg-hover:      #244079;
    --bg-inset:      #0e1c3a;

    --surface:       #1a2f59;   /* raised card surface, clearly above the page */

    --border:        #2c477e;
    --border-strong: #3d5c9c;

    --text:          #eaf1ff;
    --text-dim:      #b3c4e6;
    --text-faint:    #8298c4;

    /* Cool blue ambient wash. */
    --wash:
        radial-gradient(1300px 680px at 76% -16%, rgba(74, 163, 255, 0.24) 0%, transparent 60%),
        radial-gradient(920px 540px at 0% 108%, rgba(120, 110, 255, 0.14) 0%, transparent 58%);

    --shadow-color: rgba(4, 12, 32, 0.55);
    --scrim: rgba(11, 23, 48, 0.76);

    /* Blue-tinted, not black: shadows carry a navy hue so depth stays in the blue family. */
    --shadow-sm: 0 1px 2px rgba(6, 16, 40, 0.45), 0 1px 3px rgba(6, 16, 40, 0.35);
    --shadow:    0 2px 6px rgba(6, 16, 40, 0.50), 0 8px 20px rgba(8, 20, 52, 0.42);
    --shadow-md: 0 6px 18px rgba(6, 16, 40, 0.55), 0 18px 40px rgba(10, 26, 66, 0.50);

    /* Bright sky-blue accent for interactive state. */
    --accent:        #4aa3ff;
    --accent-hover:  #6cb6ff;
    --accent-soft:   rgba(74, 163, 255, 0.18);
    --accent-line:   rgba(74, 163, 255, 0.40);
    --gold:          #7cc4ff;

    /* Logo mark: blue-family sweep (indigo → azure → cyan) to match the navy theme. */
    --logo-a: #5566ff;
    --logo-b: #3f8fff;
    --logo-c: #35d0e0;

    /* Brand gradient: indigo → azure → cyan. A pure blue-family sweep, no gold. Used on the
       Sign in button, the wordmark, the active nav icon, badges and the loader. */
    --grad-start:    #5566ff;
    --grad-mid:      #3f8fff;
    --grad-end:      #35d0e0;
    --brand-gradient: linear-gradient(120deg,
                        var(--grad-start) 0%,
                        var(--grad-mid) 50%,
                        var(--grad-end) 100%);

    /* Status colours stay meaningful, tuned to sit on navy. */
    --ok:            #46c78e;
    --ok-soft:       rgba(70, 199, 142, 0.16);
    --warn:          #f0b24d;
    --warn-soft:     rgba(240, 178, 77, 0.16);
    --error:         #ff7a6b;
    --error-soft:    rgba(255, 122, 107, 0.16);
    --info:          #58b6f0;
    --info-soft:     rgba(88, 182, 240, 0.16);

    /* 3D lift tokens (blue, never grey). The 3D rules at the foot of the file use these so
       every theme can define its own elevation colour without duplicating the rules. */
    --lift-top:      color-mix(in srgb, #6fb4ff 26%, transparent);   /* lit top edge */
    --lift-surface-a: color-mix(in srgb, #4aa3ff 16%, var(--surface));/* card top (bluer) */
    --lift-surface-b: var(--surface);                                 /* card bottom */
    --lift-hover-a:  color-mix(in srgb, #4aa3ff 24%, var(--surface));
    --lift-hover-b:  color-mix(in srgb, #4aa3ff 8%, var(--surface));
}

* { box-sizing: border-box; }

html, body { height: 100%; }

body {
    margin: 0;
    font-family: var(--sans);
    font-size: 14px;
    line-height: 1.5;
    background: var(--bg-page);
    color: var(--text);
    -webkit-font-smoothing: antialiased;
    -moz-osx-font-smoothing: grayscale;
    transition: background-color 0.2s ease, color 0.2s ease;
}

/*
  The ambient wash sits behind everything as a fixed layer, so it does not scroll
  with content and does not repaint on every htmx swap.
*/
body::before {
    content: '';
    position: fixed;
    inset: 0;
    background: var(--wash);
    pointer-events: none;
    /*
      Negative, so the wash sits behind page content without each container needing
      to be lifted above it. An earlier version used z-index:0 here and raised
      individual elements instead, which left anything unraised — and any child using
      a negative z-index — painting underneath the wash.
    */
    z-index: -1;
}

code {
    font-family: var(--mono);
    font-size: 0.86em;
    background: var(--bg-subtle);
    border: 1px solid var(--border);
    border-radius: 4px;
    padding: 1px 5px;
    color: var(--text-dim);
}

/* ================= App frame ================= */

.app {
    display: grid;
    grid-template-columns: var(--sidebar-w) 1fr;
    height: 100vh;
    overflow: hidden;
    transition: grid-template-columns 0.16s ease;
}

.app.sidebar-collapsed { grid-template-columns: 0 1fr; }

/* ================= Sidebar ================= */

.sidebar {
    /* Slightly translucent so the ambient wash reads through the chrome */
    background: color-mix(in srgb, var(--bg-sidebar) 88%, transparent);
    backdrop-filter: blur(10px);
    border-right: 1px solid var(--border);
    display: flex;
    flex-direction: column;
    overflow: hidden;
}

/*
  Collapsing narrows the grid column to zero and clips the contents. The sidebar
  must stay a grid item: removing it with display:none shifts main into the
  zero-width first column and blanks the whole page.
*/
.app.sidebar-collapsed .sidebar {
    border-right: none;
    visibility: hidden;
}

.sidebar-brand {
    display: flex;
    align-items: center;
    gap: 9px;
    height: var(--topbar-h);
    padding: 0 16px;
    border-bottom: 1px solid var(--border);
    flex-shrink: 0;
    text-decoration: none;
    color: var(--text);
    transition: background 0.12s;
}

.sidebar-brand:hover { background: var(--bg-subtle); }

.brand-logo { width: 24px; height: 24px; flex-shrink: 0; }

/* The mark's gradient follows the theme via --logo-a/b/c. */
.brand-logo .logo-stop-a { stop-color: var(--logo-a); }
.brand-logo .logo-stop-b { stop-color: var(--logo-b); }
.brand-logo .logo-stop-c { stop-color: var(--logo-c); }

/*
  Wordmark: heavy stem, light suffix, single ink colour. The contrast is carried
  by weight rather than colour, which reads as more considered than a two-tone
  treatment and survives being placed on any background.
*/
.brand-name {
    font-weight: 750;
    font-size: 14.5px;
    letter-spacing: 0.2px;
    white-space: nowrap;
    color: var(--text);
}

.brand-name .suffix {
    font-weight: 400;
    letter-spacing: 0.1px;
}

.nav {
    flex: 1;
    overflow-y: auto;
    /* Tight top padding so there is no large gap above the first section. */
    padding: 2px 0 12px;
}

.nav::-webkit-scrollbar { width: 10px; }
.nav::-webkit-scrollbar-track { background: transparent; }
.nav::-webkit-scrollbar-thumb {
    background: var(--border-strong);
    border-radius: 5px;
    border: 3px solid var(--bg-sidebar);
}
.nav::-webkit-scrollbar-thumb:hover { background: var(--text-faint); }

/* ---- Section: a top-level heading in the sidebar ---- */

.nav-section { margin-bottom: 1px; }

.section-head {
    display: flex;
    align-items: center;
    gap: 7px;
    width: 100%;
    padding: 6px 14px 4px;
    background: none;
    border: none;
    cursor: pointer;
    font-family: inherit;
    font-size: 11px;
    font-weight: 650;
    text-transform: uppercase;
    letter-spacing: 0.6px;
    color: var(--text-faint);
    text-align: left;
    border-radius: var(--radius-sm);
    transition: color 0.12s, background 0.12s;
}

.section-head:hover { color: var(--text-dim); background: var(--bg-subtle); }

/* Icon sprite host: present in the DOM but never rendered */
.sprite { position: absolute; width: 0; height: 0; overflow: hidden; }

/*
  Icons are single-weight thin strokes in muted grey, so the sidebar reads as a
  calm list. Colour is spent only on the selected item.
*/
.section-icon {
    width: 16px;
    height: 16px;
    flex-shrink: 0;
    fill: none;
    stroke: currentColor;
    stroke-width: 1.4;
    stroke-linecap: round;
    stroke-linejoin: round;
    color: var(--text-faint);
    transition: color 0.12s;
}

.section-head:hover .section-icon { color: var(--text-dim); }

.chevron {
    font-size: 8px;
    width: 8px;
    display: inline-block;
    transition: transform 0.16s ease;
    color: var(--text-faint);
    flex-shrink: 0;
}

.nav-section.collapsed .chevron { transform: rotate(-90deg); }

.section-label { flex: 1; }

.section-count {
    font-family: var(--mono);
    font-size: 9.5px;
    font-weight: 500;
    letter-spacing: 0;
    color: var(--text-faint);
    background: var(--bg-subtle);
    border: 1px solid var(--border);
    border-radius: 9px;
    padding: 0 5px;
    min-width: 17px;
    text-align: center;
}

.section-items {
    list-style: none;
    margin: 0;
    padding: 0 8px 2px;
}

.nav-section.collapsed .section-items,
.nav-section.collapsed .section-empty { display: none; }

/* ---- Feature: nested under its section ---- */

.nav-item {
    display: flex;
    align-items: center;
    gap: 10px;
    width: 100%;
    padding: 4px 10px 4px 12px;
    margin-bottom: 1px;
    /* Buttons do not inherit these, so they are set explicitly */
    background: none;
    border: none;
    font-family: inherit;
    text-align: left;
    color: var(--text-dim);
    text-decoration: none;
    font-size: 13.5px;
    border-radius: var(--radius-sm);
    cursor: pointer;
    transition: background 0.1s, color 0.1s;
}

.nav-item:focus-visible,
.user-menu-item:focus-visible {
    outline: 2px solid var(--accent);
    outline-offset: -2px;
}

.nav-item:hover { background: var(--bg-subtle); color: var(--text); }

/* Selected: dark ink and weight, matching the wordmark's logic */
.nav-item.selected {
    background: var(--bg-subtle);
    color: var(--text);
    font-weight: 600;
}

/* Item icon: a bordered tile that fills with the brand gradient when selected */
.item-icon {
    width: 26px;
    height: 26px;
    border-radius: 7px;
    flex-shrink: 0;
    display: flex;
    align-items: center;
    justify-content: center;
    border: 1px solid var(--border);
    background: var(--bg);
    color: var(--text-faint);
    transition: background 0.14s, color 0.14s, border-color 0.14s;
}

.item-icon svg {
    width: 14px;
    height: 14px;
    fill: none;
    stroke: currentColor;
    stroke-width: 1.4;
    stroke-linecap: round;
    stroke-linejoin: round;
}

.nav-item:hover .item-icon { border-color: var(--border-strong); color: var(--text-dim); }

.nav-item.selected .item-icon {
    background: var(--brand-gradient);
    border-color: transparent;
    color: #fff;
}

.nav-item .label { flex: 1; min-width: 0; }

/* Capability-gated: shown so its absence is explained, but inert */
.nav-item.disabled {
    color: var(--text-faint);
    cursor: not-allowed;
    align-items: flex-start;
}

.nav-item.disabled:hover { background: none; }
.nav-item.disabled .item-icon { opacity: 0.55; }

.nav-item .reason {
    display: block;
    font-family: var(--mono);
    font-size: 9.5px;
    font-style: normal;
    color: var(--text-faint);
    margin-top: 1px;
}

.section-empty {
    margin: 0;
    padding: 2px 14px 8px 20px;
    font-size: 12px;
    color: var(--text-faint);
    font-style: italic;
}

/* ---- User block, pinned at the foot ---- */

.sidebar-foot {
    position: relative;
    flex-shrink: 0;
    border-top: 1px solid var(--border);
    padding: 8px 10px;
}

/* The whole block is the menu trigger */
.user-trigger {
    display: flex;
    align-items: center;
    gap: 9px;
    width: 100%;
    padding: 6px 8px;
    background: none;
    border: none;
    border-radius: var(--radius-sm);
    cursor: pointer;
    font-family: inherit;
    text-align: left;
    color: var(--text);
    transition: background 0.12s;
}

.user-trigger:hover { background: var(--bg-subtle); }

.trigger-caret {
    color: var(--text-faint);
    flex-shrink: 0;
    transition: transform 0.16s ease;
}

.user-trigger[aria-expanded="true"] .trigger-caret { transform: rotate(180deg); }

.avatar {
    width: 28px;
    height: 28px;
    border-radius: 50%;
    background: var(--brand-gradient);
    color: #fff;
    font-size: 11.5px;
    font-weight: 650;
    display: flex;
    align-items: center;
    justify-content: center;
    flex-shrink: 0;
    text-transform: uppercase;
}

.avatar-lg { width: 36px; height: 36px; font-size: 13px; }

.user-meta { flex: 1; min-width: 0; }

.user-name {
    display: block;
    font-size: 12.5px;
    font-weight: 600;
    line-height: 1.3;
    white-space: nowrap;
    overflow: hidden;
    text-overflow: ellipsis;
}

.user-role {
    display: block;
    font-size: 10.5px;
    color: var(--text-faint);
    line-height: 1.3;
    white-space: nowrap;
    overflow: hidden;
    text-overflow: ellipsis;
}

/* ---- User menu ---- */

.user-menu {
    position: absolute;
    left: 10px;
    right: 10px;
    bottom: calc(100% - 2px);
    background: var(--bg);
    border: 1px solid var(--border);
    border-radius: 12px;
    box-shadow: 0 8px 28px var(--shadow-color);
    padding: 6px;
    z-index: 40;
}

.user-menu[hidden] { display: none; }

.user-menu-head {
    display: flex;
    align-items: center;
    gap: 10px;
    padding: 10px 10px 12px;
    border-bottom: 1px solid var(--border);
    margin-bottom: 6px;
}

.user-menu-head .user-name { font-size: 13.5px; }
.user-menu-head .user-role { font-size: 11.5px; }

.user-menu-items { display: flex; flex-direction: column; }

.user-menu-form { margin: 0; display: flex; }

.user-menu-item {
    display: flex;
    align-items: center;
    gap: 10px;
    width: 100%;
    padding: 8px 10px;
    background: none;
    border: none;
    border-radius: var(--radius-sm);
    color: var(--text-dim);
    font-size: 13px;
    font-family: inherit;
    text-align: left;
    text-decoration: none;
    cursor: pointer;
    transition: background 0.1s, color 0.1s;
}

.user-menu-item:hover { background: var(--bg-subtle); color: var(--text); }
.user-menu-item .chrome-icon { color: var(--text-faint); flex-shrink: 0; }
.user-menu-item:hover .chrome-icon { color: var(--accent); }

/* ================= Main ================= */

.main {
    display: flex;
    flex-direction: column;
    overflow: hidden;
    min-width: 0;
}

.topbar {
    display: flex;
    align-items: center;
    gap: 14px;
    height: var(--topbar-h);
    padding: 0 20px;
    background: var(--scrim);
    backdrop-filter: blur(10px);
    border-bottom: 1px solid var(--border);
    flex-shrink: 0;
}

.crumb {
    display: flex;
    align-items: baseline;
    gap: 7px;
    font-size: 13px;
    min-width: 0;
    flex: 1;
}

.crumb-section { color: var(--text-faint); }

.crumb-section:not(:empty)::after {
    content: '›';
    margin-left: 7px;
    color: var(--text-faint);
    font-size: 14px;
}

.crumb-item { color: var(--text); font-weight: 600; }

.topbar-right { display: flex; align-items: center; gap: 9px; }

.chip {
    font-size: 10.5px;
    font-weight: 650;
    letter-spacing: 0.3px;
    padding: 3px 9px;
    border-radius: 11px;
    display: inline-flex;
    align-items: center;
    gap: 5px;
}

/* Demo data must never be mistaken for a real cluster reading */
.chip-demo {
    background: var(--warn-soft);
    color: var(--warn);
    border: 1px solid color-mix(in srgb, var(--warn) 30%, transparent);
}

.chip-dot {
    width: 6px;
    height: 6px;
    border-radius: 50%;
    background: currentColor;
}

.chip-cluster {
    background: var(--bg-subtle);
    color: var(--text-dim);
    border: 1px solid var(--border);
    font-family: var(--mono);
    font-weight: 500;
}

.btn-icon {
    background: var(--bg);
    border: 1px solid var(--border-strong);
    border-radius: var(--radius-sm);
    color: var(--text-dim);
    cursor: pointer;
    font-size: 14px;
    width: 30px;
    height: 28px;
    line-height: 1;
    padding: 0;
    transition: background 0.12s, color 0.12s, border-color 0.12s;
}

.btn-icon:hover {
    color: var(--accent);
    border-color: var(--accent-line);
    background: var(--accent-soft);
}

.btn-icon.spinning { animation: spin 0.6s linear; }

/* Brief confirmation flash after Copy/Export. */
.btn-icon.flash-ok {
    color: var(--ok);
    border-color: var(--ok);
    background: var(--ok-soft);
}
.btn-icon.flash-error {
    color: var(--error);
    border-color: var(--error);
    background: var(--error-soft);
}

.toggle-glyph { font-size: 13px; line-height: 1; }

.btn-icon { display: inline-flex; align-items: center; justify-content: center; }

.chrome-icon {
    width: 15px;
    height: 15px;
    fill: none;
    stroke: currentColor;
    stroke-width: 1.4;
    stroke-linecap: round;
    stroke-linejoin: round;
}

/* Only the glyph for the CURRENT theme shows; default (no attribute) is light. */
.theme-icon-dark,
.theme-icon-navy,
.theme-icon-slate { display: none; }
.theme-icon-light { display: inline; }

[data-theme="slate"] .theme-icon-light { display: none; }
[data-theme="slate"] .theme-icon-slate { display: inline; }

[data-theme="dark"] .theme-icon-light  { display: none; }
[data-theme="dark"] .theme-icon-dark   { display: inline; }

[data-theme="navy"] .theme-icon-light  { display: none; }
[data-theme="navy"] .theme-icon-navy   { display: inline; }

@keyframes spin { to { transform: rotate(360deg); } }

.content {
    flex: 1;
    overflow-y: auto;
    padding: 22px 24px 44px;
    min-width: 0;
    /* Positioning context for the panel loading overlay. */
    position: relative;
}

/* ================= Banners ================= */

.banner {
    padding: 9px 20px;
    font-size: 12.5px;
    border-bottom: 1px solid var(--border);
    flex-shrink: 0;
}

.banner-warn {
    background: var(--warn-soft);
    border-bottom-color: color-mix(in srgb, var(--warn) 30%, transparent);
    color: var(--warn);
}

.banner code { background: color-mix(in srgb, var(--bg) 60%, transparent); border-color: transparent; color: inherit; }

/* ================= Loading ================= */

/*
 * Full-panel loading overlay. Covers the emptied panel while the next view
 * loads, so the previous view is never left visible mid-fetch. vitals.js toggles
 * the [hidden] attribute; the bar animates on its own while shown.
 */
.panel-loader {
    position: absolute;
    inset: 0;
    display: flex;
    flex-direction: column;
    align-items: center;
    justify-content: center;
    gap: 14px;
    padding: 40px;
    background: var(--bg);
    z-index: 5;
}

.panel-loader[hidden] { display: none; }

/*
 * Inline variant, injected into a sub-panel such as the Cluster Insight output.
 * Flows in place rather than covering the whole content area, and needs no
 * positioning context on its parent.
 */
.panel-loader-inline {
    position: static;
    inset: auto;
    min-height: 180px;
    background: transparent;
}

.panel-loader-bar {
    position: relative;
    width: min(280px, 60%);
    height: 4px;
    border-radius: 999px;
    background: var(--bg-subtle);
    overflow: hidden;
}

.panel-loader-fill {
    position: absolute;
    top: 0;
    left: 0;
    height: 100%;
    width: 40%;
    border-radius: 999px;
    background: var(--brand-gradient);
    /* Indeterminate: signals work without implying a known duration. */
    animation: panel-loader-slide 1.1s ease-in-out infinite;
}

@keyframes panel-loader-slide {
    0%   { left: -40%; }
    100% { left: 100%; }
}

.panel-loader-text {
    color: var(--text-faint);
    font-size: 12.5px;
}

/*
 * Respect reduced-motion: replace the sliding bar with a static full bar that
 * gently pulses, so there is still a signal without the horizontal motion.
 */
@media (prefers-reduced-motion: reduce) {
    .panel-loader-fill {
        left: 0;
        width: 100%;
        animation: panel-loader-pulse 1.4s ease-in-out infinite;
    }
    @keyframes panel-loader-pulse {
        0%, 100% { opacity: 0.35; }
        50%      { opacity: 0.8; }
    }
}

.placeholder { color: var(--text-faint); font-size: 13px; }

/* ================= Donuts (Cluster Overview) ================= */

.donut-grid {
    display: grid;
    grid-template-columns: repeat(auto-fit, minmax(180px, 1fr));
    gap: 14px;
    margin-bottom: 18px;
}

.donut-card {
    background: var(--surface);
    border: 1px solid var(--border);
    border-radius: var(--radius);
    box-shadow: var(--shadow-sm);
    padding: 14px 16px 16px;
    display: flex;
    flex-direction: column;
    align-items: center;
    gap: 8px;
}

.donut-head {
    align-self: flex-start;
    font-size: 10.5px;
    font-weight: 650;
    text-transform: uppercase;
    letter-spacing: 0.5px;
    color: var(--text-faint);
}

.donut { display: flex; flex-direction: column; align-items: center; gap: 6px; }

.donut-svg { width: 104px; height: 104px; }

.donut-track {
    fill: none;
    stroke: var(--bg-inset);
    stroke-width: 10;
}

.donut-arc {
    fill: none;
    stroke-width: 10;
    stroke-linecap: round;
    transition: stroke-dasharray 0.4s ease;
}
.donut-arc.ok    { stroke: var(--ok); }
.donut-arc.warn  { stroke: var(--warn); }
.donut-arc.error { stroke: var(--error); }
.donut-arc.na    { stroke: var(--border); }

.donut-pct {
    fill: var(--text);
    font-size: 20px;
    font-weight: 650;
    text-anchor: middle;
    dominant-baseline: middle;
    font-family: var(--font);
}

.donut-sub { font-size: 11.5px; color: var(--text-faint); text-align: center; }

/* ================= Overview tile grid ================= */

.tile-grid {
    display: grid;
    /* 10 columns to match the insight strip below, so the top ring tiles and the chips share
       the same column width and align. There are fewer than 10 ring tiles, so this row is not
       full — the tiles simply take the first columns at the same width as the chips. */
    grid-template-columns: repeat(10, 1fr);
    gap: 12px;
}

.tile-card {
    background: var(--surface);
    border: 1px solid var(--border);
    border-radius: var(--radius);
    box-shadow: var(--shadow-sm);
    padding: 9px 12px 10px;
    display: flex;
    flex-direction: column;
    align-items: center;
    gap: 3px;
    font: inherit;
    color: inherit;
    cursor: pointer;
    transition: border-color 0.15s ease, box-shadow 0.15s ease, transform 0.15s ease;
}
.tile-card:hover {
    border-color: var(--accent);
    box-shadow: var(--shadow-md, var(--shadow-sm));
    transform: translateY(-2px);
}
.tile-card:focus-visible {
    outline: 2px solid var(--accent);
    outline-offset: 2px;
}

.tile-head {
    align-self: flex-start;
    font-size: 10px;
    font-weight: 650;
    text-transform: uppercase;
    letter-spacing: 0.5px;
    color: var(--text-faint);
}

.tile-ring { display: flex; flex-direction: column; align-items: center; gap: 2px; }
/* Compact ring — still large enough for a long ratio (e.g. 1980/2040) via the
   figure-sm/xs shrink classes — so the tile row stays short and more of the page shows. */
.tile-svg { width: 78px; height: 78px; }
.tile-ring-figure {
    fill: var(--text);
    font-size: 19px;
    font-weight: 650;
    text-anchor: middle;
    dominant-baseline: middle;
    font-family: var(--font);
}

/* Long ring figures (e.g. big ratios like 1980/2040) shrink to stay inside the
   ring, since SVG text does not reflow. Keeps every tile the same size. The viewBox
   is fixed at 100 units, so these sizes are in viewBox units and scale with the ring. */
.tile-ring-figure.figure-sm  { font-size: 15px; }
.tile-ring-figure.figure-xs  { font-size: 12px; }
.tile-ring-figure.figure-xxs { font-size: 9.5px; }

.tile-stat {
    display: flex;
    flex-direction: column;
    align-items: center;
    gap: 1px;
    padding: 4px 0 2px;
}
.tile-figure {
    font-size: 30px;
    font-weight: 680;
    letter-spacing: -0.5px;
    color: var(--text);
    line-height: 1.05;
}
.tile-figure.ok    { color: var(--ok); }
.tile-figure.warn  { color: var(--warn); }
.tile-figure.error { color: var(--error); }
.tile-figure.na    { color: var(--text-faint); }
.tile-unit {
    font-size: 11px;
    font-weight: 600;
    color: var(--text-dim);
}
.tile-sub {
    font-size: 10.5px;
    color: var(--text-faint);
    text-align: center;
}

/* Count tiles on the Network hub: a large number rather than a health ring,
   since networking objects have no meaningful "healthy percentage". */
.count-grid {
    display: grid;
    /* Fixed-width tracks packed from the left, so a hub with two tiles shows two
       normal-sized cards rather than stretching each to fill half the row. auto-fill
       (not auto-fit) keeps empty tracks from collapsing the sizing. */
    grid-template-columns: repeat(auto-fill, minmax(220px, 240px));
    gap: 14px;
    margin-bottom: 18px;
}
.count-card {
    background: var(--surface);
    border: 1px solid var(--border);
    border-radius: var(--radius);
    box-shadow: var(--shadow-sm);
    padding: 16px 18px 18px;
    display: flex;
    flex-direction: column;
    gap: 6px;
    font: inherit;
    color: inherit;
    text-align: left;
    cursor: pointer;
    transition: border-color 0.15s ease, box-shadow 0.15s ease, transform 0.15s ease;
}
.count-card:hover {
    border-color: var(--accent);
    box-shadow: var(--shadow-md, var(--shadow-sm));
    transform: translateY(-2px);
}
.count-card:focus-visible {
    outline: 2px solid var(--accent);
    outline-offset: 2px;
}
.count-head {
    font-size: 10.5px;
    font-weight: 650;
    text-transform: uppercase;
    letter-spacing: 0.5px;
    color: var(--text-faint);
}
.count-value {
    font-size: 30px;
    font-weight: 680;
    letter-spacing: -0.5px;
    color: var(--text);
    line-height: 1.1;
}
.count-value.ok   { color: var(--ok); }
.count-value.warn { color: var(--warn); }
.count-value.na   { color: var(--text-faint); }
.count-sub { font-size: 11.5px; color: var(--text-faint); }

/* A non-clickable count tile (e.g. Secrets, which is never read): no pointer, no
   lift, and dimmed so it reads as informational rather than actionable. */
.count-card-static {
    cursor: default;
    opacity: 0.72;
}
.count-card-static:hover {
    border-color: var(--border);
    box-shadow: var(--shadow-sm);
    transform: none;
}

/* Clickable tiles on the Workloads hub: reset the button chrome, then add a
   pointer affordance and a lift on hover so the whole card reads as a link. */
.donut-card-link {
    font: inherit;
    color: inherit;
    text-align: inherit;
    cursor: pointer;
    width: 100%;
    transition: border-color 0.15s ease, box-shadow 0.15s ease, transform 0.15s ease;
}
.donut-card-link:hover {
    border-color: var(--accent);
    box-shadow: var(--shadow-md, var(--shadow-sm));
    transform: translateY(-2px);
}
.donut-card-link:focus-visible {
    outline: 2px solid var(--accent);
    outline-offset: 2px;
}

/* ================= Module views ================= */

.module-head {
    display: flex;
    align-items: flex-start;
    justify-content: space-between;
    gap: 16px;
    margin-bottom: 18px;
}



/* Title + description share one line: the h2 stays the large label and the description
   sits inline to its right at its own small size, aligned on the baseline. The inner
   wrapper is the unclassed <div> that every module-head holds (h2 + p.module-sub). Wraps
   to a second line only when the viewport is too narrow to hold both. */
.module-head > div {
    display: flex;
    align-items: baseline;
    gap: 10px;
    flex-wrap: wrap;
    min-width: 0;
}

.module-head h2 {
    margin: 0;
    font-size: 20px;
    font-weight: 650;
    letter-spacing: -0.3px;
    flex: 0 0 auto;
}

.module-sub {
    margin: 0;
    font-size: 13px;
    color: var(--text-faint);
    min-width: 0;
}

.module-meta {
    font-size: 11.5px;
    color: var(--text-faint);
    font-family: var(--mono);
    text-align: right;
    white-space: nowrap;
    padding-top: 4px;
}

/* ---- Stat cards ---- */

.stat-grid {
    display: grid;
    grid-template-columns: repeat(auto-fill, minmax(178px, 1fr));
    gap: 12px;
    margin-bottom: 22px;
}

/* Wider tracks for stat cards whose value is a phrase (e.g. "Supported — 321 days
   left") rather than a short figure, so it stays on one line. */
.stat-grid-wide {
    grid-template-columns: repeat(auto-fill, minmax(240px, 1fr));
}

/* The EOL tiles carry the longest phrases in the app ("Past end of life 185 days ago").
   The status value may wrap to a second line, but the tile is wide enough that the
   "Ends <date>" sub-line stays on ONE line. Scoped to the version-eol grid. */
#version-eol.stat-grid-wide {
    grid-template-columns: repeat(auto-fill, minmax(300px, 1fr));
}
/* The support-status phrase wraps within its tile instead of clipping on one line. */
#version-eol .eol-status-value {
    font-size: 14px;
    line-height: 1.35;
    white-space: normal;
    overflow-wrap: anywhere;
}
/* Keep the "Ends <date>" sub-line on a single line — it is short and reads best unbroken. */
#version-eol .stat-sub { white-space: nowrap; }
.stat-value.nowrap { white-space: nowrap; }

.stat {
    background: var(--surface);
    border: 1px solid var(--border);
    border-radius: var(--radius);
    padding: 13px 15px;
    box-shadow: var(--shadow-sm);
}

/* Clickable stat tiles (rendered as <a> or <button>) get the 3D affordance: a resting
   shadow, and a lift + deeper shadow + accent border on hover/focus, so they read as
   controls everywhere the same way the Overview tiles do. Applied to any interactive
   .stat regardless of page, so the look is consistent across the app. */
a.stat, button.stat {
    display: block;
    width: 100%;
    text-align: left;
    color: inherit;
    text-decoration: none;
    font: inherit;
    cursor: pointer;
    transition: box-shadow 0.15s ease, border-color 0.15s ease, transform 0.15s ease;
}
a.stat.stat-ic, button.stat.stat-ic { display: flex; }
a.stat:hover, button.stat:hover,
a.stat:focus-visible, button.stat:focus-visible {
    box-shadow: var(--shadow);
    border-color: var(--border-strong);
    transform: translateY(-2px);
    outline: none;
}
a.stat:focus-visible, button.stat:focus-visible {
    border-color: var(--accent);
}
/* A faint corner chevron marks a stat tile as navigable, at rest and stronger on hover. */
a.stat, button.stat { position: relative; }
a.stat::after, button.stat::after {
    content: '›';
    position: absolute;
    top: 8px;
    right: 11px;
    color: var(--accent);
    font-weight: 700;
    opacity: 0.35;
    transition: opacity 0.15s ease;
}
a.stat:hover::after, button.stat:hover::after,
a.stat:focus-visible::after, button.stat:focus-visible::after { opacity: 0.95; }

.stat-label {
    font-size: 10.5px;
    text-transform: uppercase;
    letter-spacing: 0.6px;
    color: var(--text-faint);
    font-weight: 600;
    margin-bottom: 5px;
}

.stat-value {
    font-size: 22px;
    font-weight: 650;
    font-family: var(--mono);
    line-height: 1.15;
    letter-spacing: -0.5px;
}

.stat-sub { font-size: 11px; color: var(--text-faint); margin-top: 3px; }

.stat-value.ok    { color: var(--ok); }
.stat-value.warn  { color: var(--warn); }
.stat-value.error { color: var(--error); }

/* ---- Stat card with a leading icon chip (modern KPI-card pattern) ----
   A soft-tinted rounded icon square on the left, metric on the right. The chip
   tint follows the metric's status, so an all-clear card reads teal/green and a
   problem card reads amber/red — the colour carries meaning, not decoration. */
.stat-ic {
    display: flex;
    align-items: center;
    gap: 13px;
    transition: box-shadow 0.15s ease, border-color 0.15s ease, transform 0.15s ease;
}
.stat-ic:hover {
    box-shadow: var(--shadow);
    border-color: var(--border-strong);
    transform: translateY(-1px);
}
.stat-chip {
    flex: 0 0 auto;
    width: 40px;
    height: 40px;
    border-radius: 11px;
    display: flex;
    align-items: center;
    justify-content: center;
    background: var(--accent-soft);
    color: var(--accent);
}
.stat-chip svg {
    width: 20px;
    height: 20px;
    fill: none;
    stroke: currentColor;
    stroke-width: 1.6;
    stroke-linecap: round;
    stroke-linejoin: round;
}
.stat-chip.ok    { background: var(--ok-soft);    color: var(--ok); }
.stat-chip.warn  { background: var(--warn-soft);  color: var(--warn); }
.stat-chip.error { background: var(--error-soft); color: var(--error); }
.stat-chip.info  { background: var(--info-soft);  color: var(--info); }
.stat-ic .stat-body { min-width: 0; }
.stat-ic .stat-label { margin-bottom: 2px; }
.stat-ic .stat-value { font-size: 21px; }

/* A stat tile that links to a section lower on the page (multi-table pages). Keeps
   the card look, drops the anchor's underline/colour, and lifts slightly on hover so
   it reads as clickable. */
a.stat-link {
    display: block;
    text-decoration: none;
    color: inherit;
    cursor: pointer;
    transition: box-shadow 0.15s ease, border-color 0.15s ease, transform 0.15s ease;
}
a.stat-link.stat-ic { display: flex; }
a.stat-link:hover {
    box-shadow: var(--shadow);
    border-color: var(--border-strong);
    transform: translateY(-1px);
}
a.stat-link:focus-visible {
    outline: 2px solid var(--accent);
    outline-offset: 2px;
}

/* The shared overview fragment builds each tile from spans (so a tile can be an <a>
   without invalid block-in-inline nesting). Force the body to stack and the parts to
   read as blocks, matching the hand-written .stat tiles that use <div>s. */
.stat > .stat-body,
.stat-link > .stat-body { display: block; }
.stat-body > .stat-label,
.stat-body > .stat-value,
.stat-body > .stat-sub { display: block; }

/* When a section is jumped to from an overview tile, leave room above it so its title
   is not hidden under the sticky topbar and reads clearly as the scroll target. */
[data-overview-target] { scroll-margin-top: 76px; }
[data-overview-target].overview-flash { animation: overview-flash 1.2s ease; }
@keyframes overview-flash {
    0%   { box-shadow: 0 0 0 2px var(--accent); }
    100% { box-shadow: var(--shadow-sm); }
}

/* ---- Tables ---- */

.table-wrap {
    background: var(--bg);
    /* A stronger edge + a resting shadow so the table reads as a raised card against the
       page, which the hairline border alone did not — the white surface and the faint-grey
       page were too close to tell apart. */
    border: 1px solid var(--border-strong);
    border-radius: var(--radius);
    overflow: hidden;
    margin-bottom: 18px;
    box-shadow: var(--shadow);
    transition: box-shadow 0.16s ease;
}
.table-wrap:hover { box-shadow: var(--shadow-md); }

.table-title {
    display: flex;
    align-items: center;
    gap: 8px;
    padding: 10px 15px;
    font-size: 12px;
    font-weight: 650;
    color: var(--text);
    background: var(--bg-inset);
    border-bottom: 1px solid var(--border);
}

/*
  A plain single-table page repeats its own name in the table title ("Pods" → "Pods, worst
  first"), which the page heading already says — a wasted line. Hide a table title that is
  just text (no child pills, notes or legends). Titles that carry a badge/count/legend, or
  that distinguish one section from another on a multi-table page, add the `keep` class to
  stay visible. This is a density win applied app-wide without editing every template.
*/
.table-title:not(.keep):not(:has(*)) { display: none; }

/*
  Scroll in both directions and cap the height. Columns already scrolled
  horizontally; capping the height means a long table scrolls vertically inside
  its own box instead of pushing the header off the top of the page. A short
  table never reaches the cap, so it shows no vertical scrollbar — the behaviour
  only kicks in for the large tables that need it, with no per-table markup.

  The cap is viewport-relative so it adapts to screen size, less the room the
  page chrome and surrounding cards take.
*/
.table-scroll {
    overflow: auto;
    /*
      Cap the visible height to exactly the sticky header + ten data rows, so a longer table
      scrolls vertically inside this box rather than pushing the page. The precise height is
      set per-table by wireTableRowCap() in vitals.js (measuring the real header + 10 rows,
      since usage-bar rows are taller than plain ones). This is the no-JS fallback: a sane
      cap so the page still does not run away if the script has not run yet.
    */
    max-height: 70vh;
    /*
      Reserve the scrollbar gutter at all times. Without this, a table tall enough
      to scroll gains a vertical scrollbar that narrows the body's content box but
      not the sticky header row (which is painted across the full width), so the
      header cells drift a few pixels right of the columns beneath them. A stable
      gutter gives header and body the same available width, keeping them aligned
      whether or not the scrollbar is present.
    */
    scrollbar-gutter: stable;
}

table.data {
    width: 100%;
    /*
      border-collapse: separate, not collapse. A collapsed table computes column
      widths for sticky <th> independently of the body cells, so the pinned header
      row drifts out of line with the columns beneath it. With separate borders the
      header and body share one column layout and stay aligned. Spacing is zeroed so
      the visual is unchanged; row dividers are drawn per-cell below.
    */
    border-collapse: separate;
    border-spacing: 0;
    font-size: 14px;
}

/*
  Sticky header row: stays pinned to the top of .table-scroll while the body
  scrolls under it. A header cell inherits the alignment of its column via the
  align-* helpers below, so a right-aligned numeric column gets a right-aligned
  header and the two line up.
*/
table.data th {
    text-align: left;
    padding: 8px 16px;
    font-size: 10.5px;
    font-weight: 700;
    text-transform: uppercase;
    letter-spacing: 0.6px;
    /* Darker than the old faint grey so the header reads as its own band above row one,
       not as another dim row. */
    color: var(--text-dim);
    /* A subtle translucent header that stays legible when it sticks over content. */
    background: color-mix(in srgb, var(--bg-inset) 88%, var(--bg));
    backdrop-filter: saturate(1.1) blur(2px);
    /* A crisper 2px accent-tinted rule under the header separates it clearly from the body. */
    border-bottom: 2px solid color-mix(in srgb, var(--accent) 22%, var(--border-strong));
    white-space: nowrap;
    position: sticky;
    top: 0;
    z-index: 3;
}

/* Sortable headers (wired by vitals.js wireTableSort). The caret is a pseudo-element so
   it never shifts the header text or fights the sticky/right-align rules. Neutral by
   default, accent-coloured and pointed once a column is the active sort key. */
table.data th.th-sort { cursor: pointer; user-select: none; }
table.data th.th-sort:hover { color: var(--text); background: color-mix(in srgb, var(--accent) 14%, var(--bg-inset)); }
table.data th.th-sort:focus-visible {
    outline: 2px solid var(--accent);
    outline-offset: -2px;
}
/* A clear, boxed sort glyph so the control reads as an obvious button, not a faint hint.
   Bigger (13px), higher-contrast, and carried in a small chip after the label. */
table.data th.th-sort::after {
    content: '\21C5';   /* up-down paired arrows: sortable but unsorted */
    margin-left: 6px;
    /* Match the header label's own size and sit on the same baseline so the glyph rides
       the NAMESPACE text line rather than dropping below it. */
    font-size: 1em;
    line-height: inherit;
    color: var(--text-dim);
    opacity: 0.85;
    vertical-align: baseline;
}
table.data th.th-sort:hover::after { color: var(--accent); opacity: 1; }
table.data th.th-sort.sort-asc::after,
table.data th.th-sort.sort-desc::after {
    opacity: 1;
    color: #fff;
    background: var(--accent);
    border-radius: 4px;
    padding: 0 3px;
    font-size: 1em;
}
table.data th.th-sort.sort-asc::after  { content: '\2191'; }  /* up arrow */
table.data th.th-sort.sort-desc::after { content: '\2193'; }  /* down arrow */
/* Right-aligned numeric headers keep the glyph snug to the label on the right edge. */
table.data th.num.th-sort::after { margin-left: 5px; }

table.data td {
    padding: 5px 16px;
    border-bottom: 1px solid var(--border);
    vertical-align: middle;
}


/* A soft accent-tinted hover reads more alive than a flat grey; the transition
   keeps it from flickering as the pointer sweeps a dense table. */
table.data tbody tr {
    transition: background 0.12s ease;
}
table.data tbody tr:hover td {
    background: color-mix(in srgb, var(--accent) 6%, var(--bg));
}

/* Hover accent bar: a thin accent edge on the row's first cell that appears on hover, a
   small tactile cue that the row is a unit. Drawn with an inset shadow so it adds no width
   and never shifts the layout. */
table.data tbody tr td:first-child {
    box-shadow: inset 3px 0 0 transparent;
    transition: box-shadow 0.12s ease;
}
table.data tbody tr:hover td:first-child {
    box-shadow: inset 3px 0 0 var(--accent);
}

/* Round the body's outer corners so it sits flush inside the rounded card, and drop the
   last row's divider so it meets the card edge cleanly. */
table.data tbody tr:last-child td { border-bottom: none; }
table.data tbody tr:last-child td:first-child { border-bottom-left-radius: var(--radius); }
table.data tbody tr:last-child td:last-child  { border-bottom-right-radius: var(--radius); }

td.num, th.num { text-align: right; font-family: var(--mono); }

/* Left-aligned numeric column: mono figures that read better sitting under their
   header on a wide table (small integer counts, not magnitudes to compare). */
td.num-left, th.num-left { text-align: left; font-family: var(--mono); }
table.data th.num-left { text-align: left; }
/* Header cells for numeric columns must right-align to line up with their
   right-aligned values. Scoped with table.data to outrank `table.data th`
   (which sets left) regardless of source order. */
table.data th.num { text-align: right; }
td.mono   { font-family: var(--mono); font-size: 13px; }
td.dim    { color: var(--text-faint); }
td.nowrap { white-space: nowrap; }

/*
  Keep data-table rows on a single line by default. With many columns the browser used to
  squeeze and wrap short cells (a hyphenated namespace, a phase pill, the created age) across
  two or three lines, which read as clutter. Now every cell stays on one line and the whole
  table scrolls horizontally inside its .table-scroll wrapper when it genuinely cannot fit —
  the standard dense-table behaviour. Genuinely long free text (an event message, a pending
  reason, a dependents list) opts back into wrapping with the `wrap` class so it does not
  force the table absurdly wide.
*/
table.data th, table.data td { white-space: nowrap; }
table.data th.wrap, table.data td.wrap { white-space: normal; }

/* A cell that stays on one line and shows an ellipsis when it overflows; the full text
   is available on hover via the element's title attribute. Used for long, low-priority
   columns (e.g. ConfigMap key names) so they do not force a row to wrap. */
td.cell-truncate {
    max-width: 380px;
    white-space: nowrap;
    overflow: hidden;
    text-overflow: ellipsis;
}

/* Give identifier columns enough room to stay on one line rather than wrapping a
   hyphenated namespace like "aws-privateca-issuer" across three lines. */
table.data th.col-ns,   table.data td.col-ns   { min-width: 150px; }
table.data th.col-name, table.data td.col-name { min-width: 190px; }

/* Name/identifier cell that shows in full but is capped to ~40 characters wide by default,
   with an ellipsis when it overflows. The complete value is in the cell's title (hover) and
   its data-copy (export), and the column can be dragged wider via the header grip. Replaces
   the old server-side 40-char abbreviation: nothing is trimmed from the value now, only the
   rendered width. */
table.data td.col-cap {
    max-width: 40ch;
    overflow: hidden;
    text-overflow: ellipsis;
    white-space: nowrap;
}
/* A link/button inside a capped cell must itself clip, or it overflows the cell. */
table.data td.col-cap .xlink,
table.data td.col-cap > span,
table.data td.col-cap > button {
    max-width: 100%;
    overflow: hidden;
    text-overflow: ellipsis;
    white-space: nowrap;
    vertical-align: bottom;
}

/* Capped-width cell that WRAPS rather than ellipsing. Same 40ch ceiling as col-cap, but
   the content flows onto multiple lines instead of being clipped. For the few columns
   whose full text is the most critical data on the page and must stay readable — e.g. the
   Message column on Events and Problems — so the column never grows wider than 40ch yet
   nothing is hidden. */
table.data td.col-cap-wrap,
table.data th.col-cap-wrap {
    max-width: 40ch;
    white-space: normal;
    overflow-wrap: anywhere;
    word-break: break-word;
}

/* ---- Draggable column resize ---- */

/* The absolutely-placed grip anchors to its header cell. The header keeps
   position: sticky (from the base `table.data th` rule) so it stays pinned when the
   body scrolls — a sticky element is itself a positioning context, so the grip still
   anchors correctly. Do NOT set position: relative here: that would override sticky and
   the header would scroll away under the body (only the non-resizable # column stayed
   pinned otherwise). */

/* A thin grab strip on the right edge of a resizable header. Invisible until hovered so it
   does not clutter the header, but always grabbable. Sits above the sort click target and
   swallows its own events in JS, so dragging never sorts. */
table.data th.th-resizable .col-resize-grip {
    position: absolute;
    top: 0;
    right: 0;
    width: 7px;
    height: 100%;
    cursor: col-resize;
    user-select: none;
    z-index: 2;
}
table.data th.th-resizable .col-resize-grip::after {
    content: "";
    position: absolute;
    top: 25%;
    right: 3px;
    width: 2px;
    height: 50%;
    background: var(--border);
    /* Always visible, so the column boundaries read as an affordance the user can grab —
       not a hidden control that only appears on a pixel-perfect edge hover. Brightens to
       the accent when the header is hovered or the grip itself is grabbed. */
    opacity: 0.7;
    transition: opacity 0.12s ease, background 0.12s ease, width 0.12s ease;
}
table.data th.th-resizable:hover .col-resize-grip::after { opacity: 1; }
table.data th.th-resizable .col-resize-grip:hover::after {
    opacity: 1;
    background: var(--accent);
    /* A touch wider on direct hover so the grabbable strip is obvious. */
    width: 3px;
}
/* While a drag is in progress, show the resize cursor everywhere and suppress text
   selection so the drag reads cleanly. */
body.col-resizing { cursor: col-resize !important; user-select: none !important; }

/* Once any column has been resized, switch the table to fixed layout so the explicit
   <col> pixel widths are honoured exactly rather than redistributed by auto layout. The
   table keeps width:100%, so untouched columns still share the remaining space. */
table.data.cols-resized { table-layout: fixed; }

/* Under fixed layout a nowrap cell overflows its box when a column is dragged narrow,
   bleeding text into the next column. Clip every body/header cell to its column width
   with an ellipsis so narrowing hides the overflow instead of overwriting neighbours;
   the full value stays available via the cell's title/data-copy. Cells that opt into
   wrapping (.wrap) keep wrapping and just clip vertically as needed. */
table.data.cols-resized th,
table.data.cols-resized td {
    overflow: hidden;
    text-overflow: ellipsis;
}
table.data.cols-resized td > * {
    max-width: 100%;
    overflow: hidden;
    text-overflow: ellipsis;
    vertical-align: bottom;
}
/* Keep the header label clipping, but never clip the resize grip: it is absolutely
   positioned at the column's right edge (right:0, inside the box), so it survives the
   header's overflow:hidden and stays grabbable at any column width. */
table.data.cols-resized th.th-resizable .col-resize-grip { overflow: visible; }

/* ---- Boxed cell values (the launchpad "chip in every cell" look) ----
   Opt-in per table via `table.data.cell-boxed`. Any value wrapped in a `.cell-val`
   span inside that table renders as a bordered, rounded, subtly-tinted chip, so each
   data point reads as its own token rather than loose text — the distinctive launchpad
   table look, rebuilt with vitals' own tokens (no inline styles, CSP-safe). State tints
   reuse the pill palette: .cell-val-ok / -warn / -error / -info for green/amber/red/blue.
   Left as a modifier so it can be rolled out table by table. */
/* The chip fills its cell so every box in a column is the same width regardless of the
   value's length (the "uniform column" look). Border follows the active theme via
   --table-accent-border. */
table.data.cell-boxed .cell-val {
    display: flex;
    align-items: center;
    gap: 5px;
    width: 100%;
    box-sizing: border-box;
    padding: 1px 8px;
    border: 1px solid var(--table-accent-border);
    border-radius: var(--radius-sm);
    background: var(--bg-inset);
    color: var(--text-dim);
    font-family: var(--mono);
    font-size: 11.5px;
    line-height: 1.7;
    white-space: nowrap;
    overflow: hidden;
    text-overflow: ellipsis;
}

/* Numeric columns (#, Pods, CPU/Mem used) right-align their value inside the box. */
table.data.cell-boxed td.num .cell-val { justify-content: flex-end; }
/* num-left columns (Right-Sizing metrics, Updated/desired) keep their value left-aligned. */
table.data.cell-boxed td.num-left .cell-val { justify-content: flex-start; }

/* Meter variant: the value text sits ON TOP of a proportional background fill inside a
   single chip (no separate bar row below), so the committed CPU/Memory reads at a glance
   without widening the column — the launchpad "text merged with progress" treatment. The
   fill is an absolutely-positioned layer sized to the percent; the text is above it. */
table.data.cell-boxed .cell-val.cell-meter {
    position: relative;
    justify-content: flex-start;
    padding: 1px 8px;
    isolation: isolate;
}
.cell-meter .cell-meter-fill {
    position: absolute;
    inset: 0 auto 0 0;   /* left-anchored, full height, width set inline per row */
    z-index: -1;
    border-radius: inherit;
    background: color-mix(in srgb, var(--ok) 22%, transparent);
}
.cell-meter .cell-meter-fill.warn  { background: color-mix(in srgb, var(--warn) 26%, transparent); }
.cell-meter .cell-meter-fill.error { background: color-mix(in srgb, var(--error) 30%, transparent); }
.cell-meter .cell-meter-text { position: relative; z-index: 1; }

/* Non-mono variant for plain text values (zone, instance type) that should not be mono. */
table.data.cell-boxed .cell-val.sans { font-family: var(--sans); }

/* Wrap variant: for long free-text columns (event message, selectors) that should wrap
   inside the box rather than truncate to one line. */
table.data.cell-boxed td.col-cap-wrap .cell-val,
table.data.cell-boxed .cell-val.wrap {
    white-space: normal;
    overflow: visible;
    text-overflow: clip;
}

/* When a chip holds a value plus a trailing marker pill (e.g. the System "this instance"
   tag, or RBAC capability tags), the value text truncates on the left and the pill keeps its
   own size on the right — they sit side by side (the flex gap spaces them), never overlap
   and never wrap below the box. This rule wins over the generic `.cell-val > span` below via
   the more specific `.cell-val-text` class. */
table.data.cell-boxed .cell-val > .cell-val-text {
    flex: 1 1 auto;
    min-width: 0;
    overflow: hidden;
    text-overflow: ellipsis;
    white-space: nowrap;
    max-width: none;
}
table.data.cell-boxed .cell-val > .pill-sm {
    flex: 0 0 auto;
    max-width: none;
    overflow: visible;
    /* Drop the pill's own border when it sits inside a chip — the chip already owns the
       border, so a bordered pill inside read as a double border (RBAC capability tags). */
    border-color: transparent;
}

/* A cross-link button (.xlink / .link-btn) sitting inside a chip stays a borderless link —
   it must not clip its own text away or add a nested box; the chip owns the border and the
   ellipsis. Truncation happens on the .cell-val, not the link. */
table.data.cell-boxed .cell-val > .xlink,
table.data.cell-boxed .cell-val > .link-btn,
table.data.cell-boxed .cell-val > span:not(.cell-val-text):not(.pill):not(.pill-sm):not(.sev-dot) {
    min-width: 0;
    max-width: 100%;
    overflow: hidden;
    text-overflow: ellipsis;
    white-space: nowrap;
    padding: 0;
}

/* Tighter horizontal cell padding so the themed boxes sit close together and the gap
   between columns is small — the boxes themselves now carry the visual separation. */
table.data.cell-boxed td,
table.data.cell-boxed th { padding-left: 5px; padding-right: 5px; padding-top: 3px; padding-bottom: 3px; }
table.data.cell-boxed td:first-child,
table.data.cell-boxed th:first-child { padding-left: 12px; }
table.data.cell-boxed td:last-child,
table.data.cell-boxed th:last-child { padding-right: 12px; }

/* Themed outer border on the boxed table's wrapper and its filter controls, so the whole
   surface picks up the theme colour, not just the cells. */
.table-wrap.cell-boxed-wrap { border: 1px solid var(--table-accent-border); }
.table-wrap.cell-boxed-wrap .filter-bar .input {
    border-color: var(--table-accent-border);
}

/* A pill/meter often sits inside a trigger button (info-trigger opens a modal; cell-btn
   opens a drill-down). For the box to fill the whole column — so every row's box is the
   same width regardless of label length — the button must be full-width too, not shrink to
   its text. Without this the pill filled only the button and widths varied per row. */
table.data.cell-boxed td > .info-trigger,
table.data.cell-boxed td > .cell-btn,
table.data.cell-boxed td > button.info-trigger,
table.data.cell-boxed td .info-trigger.usage-cell {
    display: block;
    width: 100%;
    box-sizing: border-box;
    text-align: inherit;
    padding: 0;
    background: none;
    border: 0;
}

/* The Status pill adopts the same rectangular box shape AND the same height as the other
   cell chips inside a boxed table, so the whole row lines up as one consistent token style.
   Padding/line-height/font-size are matched to .cell-val so the box height is identical.
   width:100% + the full-width button rule above make every pill fill its column uniformly. */
table.data.cell-boxed td .pill {
    border-radius: var(--radius-sm);
    display: flex;
    width: 100%;
    box-sizing: border-box;
    justify-content: center;
    padding: 1px 8px;
    font-size: 11.5px;
    line-height: 1.7;
}
/* A pill whose state has no coloured border (e.g. an event "Normal" with a transparent
   border) still shows a box in a boxed table, so no status cell reads as borderless. */
table.data.cell-boxed td > .pill[class*="pill-"] { border-color: var(--table-accent-border); }
table.data.cell-boxed td > .pill.pill-ok      { border-color: color-mix(in srgb, var(--ok) 30%, transparent); }
table.data.cell-boxed td > .pill.pill-warn    { border-color: color-mix(in srgb, var(--warn) 30%, transparent); }
table.data.cell-boxed td > .pill.pill-error   { border-color: color-mix(in srgb, var(--error) 30%, transparent); }
table.data.cell-boxed td > .pill.pill-info    { border-color: color-mix(in srgb, var(--info) 30%, transparent); }

/* Small run/outcome pills that appear as a GROUP in one cell (CronJob recent runs) are a
   set of chips, not one status: keep them inline, auto-width, on a single clipped line
   inside the chip (the cell opens a modal for the full list). */
table.data.cell-boxed td .cron-runs {
    display: flex;
    flex-wrap: nowrap;
    gap: 4px;
    width: 100%;
    overflow: hidden;
}
table.data.cell-boxed td .cron-runs .pill { display: inline-flex; width: auto; flex-shrink: 0; }

/* A small leading severity dot inside a chip (Blast Radius namespace), coloured by state.
   Sits within the .cell-val so the dot + value read as one boxed token. */
.cell-val .sev-dot {
    flex-shrink: 0;
    width: 7px;
    height: 7px;
    border-radius: 50%;
    background: var(--text-faint);
}
.cell-val .sev-dot.sev-error   { background: var(--error); }
.cell-val .sev-dot.sev-warn    { background: var(--warn); }
.cell-val .sev-dot.sev-ok      { background: var(--ok); }
.cell-val .sev-dot.sev-neutral { background: var(--text-faint); }

/* A small amber dot before an image marks an image-hygiene risk (mutable tag / pull Always)
   without a wide badge — the full detail is in the Status modal. */
.cell-val .img-risk-dot {
    flex-shrink: 0;
    width: 7px;
    height: 7px;
    border-radius: 50%;
    background: var(--warn);
}

/* Blank / dash placeholders get a box too, so an empty value reads as an empty token
   rather than a gap — "a box everywhere" even when the cell has no value. Covers both an
   explicit .cell-dash chip and a bare .dim em-dash placeholder that is the sole direct
   child of a boxed cell (the common `<span class="dim">—</span>` pattern). */
table.data.cell-boxed .cell-dash,
table.data.cell-boxed td > .dim:only-child {
    display: flex;
    align-items: center;
    justify-content: center;
    width: 100%;
    box-sizing: border-box;
    padding: 1px 8px;
    border: 1px solid var(--table-accent-border);
    border-radius: var(--radius-sm);
    background: var(--bg-inset);
    color: var(--text-faint);
    font-family: var(--sans);
    font-size: 11.5px;
    line-height: 1.7;
}
table.data.cell-boxed td.num > .dim:only-child { justify-content: flex-end; }

/* State tints, matching the status-pill palette so colour meaning is consistent app-wide. */
table.data.cell-boxed .cell-val-ok {
    background: var(--ok-soft);    color: var(--ok);
    border-color: color-mix(in srgb, var(--ok) 30%, transparent);
}
table.data.cell-boxed .cell-val-warn {
    background: var(--warn-soft);  color: var(--warn);
    border-color: color-mix(in srgb, var(--warn) 30%, transparent);
}
table.data.cell-boxed .cell-val-error {
    background: var(--error-soft); color: var(--error);
    border-color: color-mix(in srgb, var(--error) 30%, transparent);
}
table.data.cell-boxed .cell-val-info {
    background: var(--info-soft);  color: var(--info);
    border-color: color-mix(in srgb, var(--info) 30%, transparent);
}

/* ---- Status pills ---- */

.pill {
    display: inline-flex;
    align-items: center;
    gap: 5px;
    padding: 2px 9px;
    border-radius: 999px;
    font-size: 10.5px;
    font-weight: 650;
    line-height: 1.5;
    white-space: nowrap;
    border: 1px solid transparent;
}
/* A pill that is a clickable link to the area's detail page. Keeps the pill look but
   signals interactivity on hover. */
.pill-btn {
    cursor: pointer;
    font-family: inherit;
    transition: filter .12s, box-shadow .12s;
}
.pill-btn:hover {
    filter: brightness(0.94);
    box-shadow: 0 0 0 2px color-mix(in srgb, currentColor 25%, transparent);
}
/* A tiny leading dot gives each status pill a modern "signal" glyph without an icon.
   Excludes .pill-sm, which is used as a bare colour swatch (no text) in some views. */
.pill:not(.pill-sm).pill-ok::before,
.pill:not(.pill-sm).pill-warn::before,
.pill:not(.pill-sm).pill-error::before,
.pill:not(.pill-sm).pill-info::before,
.pill:not(.pill-sm).pill-neutral::before {
    content: '';
    width: 5px;
    height: 5px;
    border-radius: 50%;
    background: currentColor;
    flex: 0 0 auto;
}

.pill-ok      { background: var(--ok-soft);    color: var(--ok);        border-color: color-mix(in srgb, var(--ok) 30%, transparent); }
.pill-warn    { background: var(--warn-soft);  color: var(--warn);      border-color: color-mix(in srgb, var(--warn) 30%, transparent); }
.pill-error   { background: var(--error-soft); color: var(--error);     border-color: color-mix(in srgb, var(--error) 30%, transparent); }
.pill-neutral { background: var(--bg-subtle);  color: var(--text-dim);  border-color: var(--border); }
.pill-info    { background: var(--info-soft);  color: var(--info);      border-color: color-mix(in srgb, var(--info) 30%, transparent); }

/* ---- States ---- */

.state-empty, .state-error {
    background: var(--surface);
    border: 1px solid var(--border);
    border-radius: var(--radius);
    padding: 26px;
    text-align: left;
    box-shadow: var(--shadow-sm);
}

.state-error { border-color: color-mix(in srgb, var(--error) 30%, transparent); }

.state-icon {
    width: 42px;
    height: 42px;
    border-radius: 50%;
    background: var(--bg-subtle);
    border: 1px solid var(--border);
    color: var(--text-faint);
    display: flex;
    align-items: center;
    justify-content: center;
    font-size: 19px;
    margin: 0 0 12px;
}

.state-title  { font-weight: 650; margin: 0 0 5px; font-size: 14.5px; }
.state-reason { color: var(--text-dim); margin: 0 0 6px; font-size: 13px; }

/*
 * Inline "all clear" panel, shown in place of a table when a diagnostic section has
 * nothing to report. Keeps the section visible and self-documenting rather than hiding it,
 * so a reader can tell the check ran and passed.
 */
.allclear .allclear-body {
    display: flex;
    align-items: center;
    gap: 9px;
    padding: 14px 16px;
    font-size: 13px;
    color: var(--text-dim);
}
.allclear-mark { font-size: 15px; font-weight: 700; }
.allclear-text { color: var(--text-dim); }

/*
 * Readiness-by-area breakdown: one horizontal bar per check category, showing where the
 * score is being lost. Bars share the same 0–100 scale as the headline score.
 */
/*
 * Readiness page header: verdict + score on the left, the per-area breakdown on the right,
 * side by side. The left column sizes to its content (the verdict banner narrows to the
 * width of the score/severity tile row beneath it), and the chart takes the rest of the row.
 */
.readiness-top {
    display: grid;
    grid-template-columns: minmax(420px, auto) minmax(0, 1fr);
    gap: 18px;
    align-items: start;
    margin-bottom: 22px;
}
.readiness-top-left {
    display: flex;
    flex-direction: column;
    gap: 14px;
    min-width: 0;
}
/* Inside the left column the verdict and score-row lose their own bottom margins;
   the grid gap spaces them. */
.readiness-top-left .verdict,
.readiness-top-left .score-row { margin-bottom: 0; }
.readiness-top-right { margin-bottom: 0; align-self: stretch; }

@media (max-width: 1100px) {
    .readiness-top { grid-template-columns: 1fr; }
}

.readiness-bars {
    display: flex;
    flex-direction: column;
    gap: 10px;
    padding: 14px 16px;
}
.readiness-bar-row {
    display: grid;
    grid-template-columns: minmax(120px, 170px) minmax(60px, 1fr) 56px auto;
    align-items: center;
    gap: 12px;
}
/* The whole row is a link to the dimension's detail page. */
.readiness-bar-btn {
    width: 100%;
    background: transparent;
    border: 0;
    border-radius: 6px;
    padding: 4px 6px;
    cursor: pointer;
    font-family: inherit;
    text-align: left;
    transition: background .12s;
}
.readiness-bar-btn:hover { background: var(--bg-hover); }
.readiness-bar-label {
    display: flex;
    flex-direction: column;
    line-height: 1.25;
}
.readiness-bar-label > span:first-child { font-weight: 600; font-size: 13px; }
.readiness-bar-note {
    color: var(--text-faint);
    font-size: 11px;
    overflow: hidden;
    text-overflow: ellipsis;
    white-space: nowrap;
    max-width: 200px;
}
.readiness-bar-track {
    height: 12px;
    border-radius: 6px;
    background: var(--bg-subtle);
    border: 1px solid var(--border);
    overflow: hidden;
}
.readiness-bar-fill {
    height: 100%;
    border-radius: 6px 0 0 6px;
    transition: width .3s ease;
}
.readiness-bar-fill.bar-ok    { background: var(--ok); }
.readiness-bar-fill.bar-warn  { background: var(--warn); }
.readiness-bar-fill.bar-error { background: var(--error); }
.readiness-bar-figure {
    font-weight: 700;
    font-size: 14px;
    text-align: right;
    font-variant-numeric: tabular-nums;
}
.readiness-bar-findings {
    display: flex;
    gap: 5px;
    flex-wrap: wrap;
    justify-content: flex-end;
}

@media (max-width: 720px) {
    .readiness-bar-row { grid-template-columns: 1fr 40px; grid-auto-flow: row; }
    .readiness-bar-track { grid-column: 1 / -1; }
    .readiness-bar-findings { grid-column: 1 / -1; justify-content: flex-start; }
}
.state-help   {
    color: var(--text-faint);
    margin: 0;
    font-size: 12.5px;
    text-align: left;
}

/*
 * Explanatory note at the foot of a view.
 *
 * Runs the full width of the page, left-aligned like .state-help — no max-width and no
 * auto margin, so prose is not artificially wrapped into extra lines.
 */
.page-note {
    color: var(--text-faint);
    font-size: 12.5px;
    line-height: 1.55;
    text-align: left;
    margin: 18px 0 0;
    max-width: none;
}

/* Same note placed inside a card, where the card supplies no padding of its own. */
.page-note.inset {
    margin: 0;
    padding: 12px 14px;
}

/* Partial failures shown alongside successful data (R16.1) */
.error-list {
    background: var(--error-soft);
    border: 1px solid color-mix(in srgb, var(--error) 30%, transparent);
    border-radius: var(--radius);
    padding: 11px 14px;
    margin-bottom: 16px;
    font-size: 12.5px;
}

.error-list-title { font-weight: 650; color: var(--error); margin-bottom: 3px; }
.error-list ul { margin: 0; padding-left: 18px; color: var(--text-dim); }

.notice {
    border-radius: var(--radius);
    padding: 11px 14px;
    margin-bottom: 16px;
    font-size: 12.5px;
}

.notice-warn { background: var(--warn-soft); border: 1px solid color-mix(in srgb, var(--warn) 30%, transparent); }

/* A healthy, reassuring notice (e.g. mesh-wide STRICT mTLS is on). */
.notice-ok { background: var(--ok-soft); border: 1px solid color-mix(in srgb, var(--ok) 30%, transparent); }
.notice-ok .notice-title { color: var(--ok); }

/* An absent optional capability is information, not a fault, so it is not styled as one. */
.notice-info {
    background: var(--info-soft);
    border: 1px solid color-mix(in srgb, var(--info) 30%, transparent);
    color: var(--text-dim);
    line-height: 1.55;
}
.notice-title { font-weight: 650; color: var(--warn); margin-bottom: 3px; }
.notice p { margin: 0; color: var(--text-dim); }

.truncated-note { font-size: 12px; color: var(--warn); margin-bottom: 10px; }

/* ================= Login ================= */

.login-body {
    min-height: 100vh;
    display: flex;
    align-items: center;
    justify-content: center;
    padding: 32px 24px;
}

/* Two columns: brand statement on the left, form on the right */
.login-split {
    position: relative;
    display: grid;
    grid-template-columns: 1fr 1fr;
    align-items: center;
    gap: 60px;
    width: 100%;
    max-width: 980px;
}

/* ---- Left column ---- */

.login-pitch { padding-left: 6px; }

/* Gradient pill, the loudest use of the brand colours */
.badge-pill {
    display: inline-block;
    background: var(--brand-gradient);
    color: #fff;
    font-size: 11px;
    font-weight: 700;
    letter-spacing: 1.4px;
    padding: 5px 15px;
    border-radius: 999px;
    margin-bottom: 18px;
}

/*
  Large, light-weight wordmark with the gradient carried by the letters. Falls
  back to the teal end where background-clip:text is unsupported.
*/
/*
  Gradient-filled text. The transparent fill is applied only inside @supports, so a
  browser that cannot clip a background to text keeps solid teal letters rather than
  rendering them invisible.
*/
.pitch-word {
    margin: 0;
    font-size: 62px;
    font-weight: 300;
    letter-spacing: -1.6px;
    line-height: 1.05;
    color: var(--grad-start);
}

@supports (background-clip: text) or (-webkit-background-clip: text) {
    .pitch-word {
        background: var(--brand-gradient);
        -webkit-background-clip: text;
        background-clip: text;
        -webkit-text-fill-color: transparent;
    }
}

.pitch-sub {
    margin: 10px 0 0;
    font-size: 16.5px;
    color: var(--text-dim);
}

.pitch-points {
    margin: 26px 0 0;
    padding: 0;
    list-style: none;
    display: flex;
    flex-direction: column;
    gap: 9px;
}

.pitch-points li {
    position: relative;
    padding-left: 20px;
    font-size: 13.5px;
    color: var(--text-faint);
}

.pitch-points li::before {
    content: '';
    position: absolute;
    left: 0;
    top: 7px;
    width: 7px;
    height: 7px;
    border-radius: 50%;
    background: var(--brand-gradient);
}

/* ---- Right column ---- */

.login-panel { display: flex; flex-direction: column; align-items: center; }

/*
  The glow is a sibling behind the card rather than a negative-z-index child: a
  negative child escapes the card's stacking context and can paint over unrelated
  content. isolation:isolate keeps this contained.
*/
.login-card {
    position: relative;
    isolation: isolate;
    width: 100%;
    max-width: 372px;
    background: color-mix(in srgb, var(--bg) 94%, transparent);
    backdrop-filter: blur(12px);
    border: 1px solid var(--border);
    border-radius: 16px;
    padding: 32px 30px 26px;
    box-shadow: 0 10px 34px var(--shadow-color);
}

/* Gradient glow bleeding out from behind the card */
.login-card::before {
    content: '';
    position: absolute;
    inset: -20px;
    border-radius: 26px;
    background: var(--brand-gradient);
    opacity: 0.15;
    filter: blur(26px);
    z-index: -1;
    pointer-events: none;
}

.card-title {
    margin: 0 0 4px;
    font-size: 20px;
    font-weight: 650;
    letter-spacing: -0.2px;
}

.card-sub { margin: 0 0 20px; font-size: 13px; color: var(--text-faint); }

/* Full-width gradient button, matching the reference */
.btn-gradient {
    margin-top: 22px;
    padding: 11px;
    background: var(--brand-gradient);
    border: none;
    border-radius: var(--radius-sm);
    color: #fff;
    font-size: 14px;
    font-weight: 650;
    font-family: inherit;
    cursor: pointer;
    transition: filter 0.14s, transform 0.06s;
}

.btn-gradient:hover  { filter: brightness(1.07) saturate(1.05); }
.btn-gradient:active { transform: translateY(1px); }

/* ---- SSO, not yet wired ---- */

.sso-block { margin-top: 22px; }

.divider {
    display: flex;
    align-items: center;
    gap: 12px;
    margin-bottom: 14px;
    color: var(--text-faint);
    font-size: 11.5px;
}

.divider::before, .divider::after {
    content: '';
    flex: 1;
    height: 1px;
    background: var(--border);
}

.btn-outline {
    width: 100%;
    padding: 10px;
    background: transparent;
    border: 1px solid var(--border-strong);
    border-radius: var(--radius-sm);
    color: var(--text-dim);
    font-size: 13.5px;
    font-weight: 600;
    font-family: inherit;
    cursor: pointer;
}

.btn-outline:disabled { opacity: 0.5; cursor: not-allowed; }

.sso-note {
    margin: 8px 0 0;
    font-size: 11.5px;
    color: var(--text-faint);
    text-align: center;
}

.login-form { display: flex; flex-direction: column; }

.login-form label {
    font-size: 11.5px;
    font-weight: 650;
    color: var(--text-dim);
    margin-top: 12px;
    margin-bottom: 5px;
}

.login-form input {
    background: var(--bg);
    border: 1px solid var(--border-strong);
    border-radius: var(--radius-sm);
    padding: 9px 11px;
    color: var(--text);
    font-size: 14px;
    font-family: inherit;
}

.login-form input:focus {
    outline: none;
    border-color: var(--accent);
    box-shadow: 0 0 0 3px var(--accent-soft);
}

/* Terms acceptance row: a checkbox and a short line with the modal link. */
.login-terms {
    display: flex;
    align-items: flex-start;
    gap: 8px;
    margin-top: 16px;
    font-size: 12px;
    color: var(--text-dim);
    line-height: 1.45;
}
.login-terms input[type="checkbox"] {
    margin: 1px 0 0;
    width: 15px;
    height: 15px;
    accent-color: var(--accent);
    flex-shrink: 0;
    cursor: pointer;
}

/* An inline text button that reads as a link — used for the Terms link inside a sentence. */
.link-inline {
    background: none;
    border: none;
    padding: 0;
    font: inherit;
    color: var(--accent);
    font-weight: 600;
    cursor: pointer;
    text-decoration: underline;
    text-underline-offset: 2px;
}
.link-inline:hover { color: var(--accent-hover); }

/* Disabled Sign in button (until terms are accepted). */
.btn-gradient:disabled {
    opacity: 0.5;
    cursor: not-allowed;
    filter: none;
}
.btn-gradient:disabled:hover { filter: none; }

/* Terms modal body: readable prose, roomier than a data table. */
.terms-body {
    font-size: 13px;
    color: var(--text-dim);
    line-height: 1.6;
    max-height: 60vh;
}
.terms-body p { margin: 0 0 10px; }
.terms-body ol { margin: 0 0 10px; padding-left: 20px; }
.terms-body li { margin: 0 0 8px; }
.terms-body strong { color: var(--text); }

.btn-primary {
    margin-top: 20px;
    padding: 10px;
    background: var(--accent);
    border: none;
    border-radius: var(--radius-sm);
    color: #fff;
    font-size: 14px;
    font-weight: 650;
    font-family: inherit;
    cursor: pointer;
    transition: background 0.12s;
}
.btn-primary:hover { background: var(--accent-hover); }

.alert {
    padding: 9px 12px;
    border-radius: var(--radius-sm);
    font-size: 12.5px;
    margin-bottom: 12px;
}

/* Borders derived from the status colour so both themes work from one rule */
.alert-error {
    background: var(--error-soft);
    border: 1px solid color-mix(in srgb, var(--error) 32%, transparent);
    color: var(--error);
}
.alert-info {
    background: var(--info-soft);
    border: 1px solid color-mix(in srgb, var(--info) 32%, transparent);
    color: var(--info);
}

.login-hint {
    margin-top: 18px;
    padding: 9px 11px;
    background: var(--warn-soft);
    border: 1px solid color-mix(in srgb, var(--warn) 32%, transparent);
    border-radius: var(--radius-sm);
    font-size: 12px;
    color: var(--warn);
    text-align: center;
}

.login-hint code { background: color-mix(in srgb, var(--bg) 60%, transparent); border-color: transparent; color: inherit; }

.login-footer {
    margin: 20px 0 0;
    text-align: center;
    font-size: 11.5px;
    color: var(--text-faint);
    letter-spacing: 0.4px;
    font-weight: 700;
}

.login-footer span:last-child {
    font-family: var(--mono);
    font-weight: 400;
    margin-left: 6px;
    letter-spacing: 0;
}

/* Single column below the split's comfortable width */
@media (max-width: 860px) {
    .login-split { grid-template-columns: 1fr; gap: 34px; max-width: 400px; }
    .login-pitch { text-align: center; padding-left: 0; }
    .pitch-word  { font-size: 46px; }
    .pitch-points { display: none; }
}

/* ================= Utility ================= */

.btn-sm {
    padding: 4px 10px;
    font-size: 12px;
    background: var(--bg);
    border: 1px solid var(--border-strong);
    border-radius: var(--radius-sm);
    color: var(--text-dim);
    cursor: pointer;
    font-family: inherit;
}
.btn-sm:hover { color: var(--accent); border-color: var(--accent-line); background: var(--accent-soft); }

.bar {
    height: 6px;
    background: color-mix(in srgb, var(--text-faint) 14%, transparent);
    border: none;
    border-radius: 999px;
    overflow: hidden;
    min-width: 54px;
    display: inline-block;
    vertical-align: middle;
}

/* Rounded, softly shaded fill — a subtle top-light gradient reads as a modern meter
   rather than a flat block. Colour still comes from the threshold class. */
.bar-fill {
    height: 100%;
    display: block;
    border-radius: 999px;
    background: var(--accent);
    background-image: linear-gradient(180deg, rgba(255,255,255,0.25), rgba(255,255,255,0));
    transition: width 0.3s ease;
}
.bar-fill.ok    { background-color: var(--ok); }
.bar-fill.warn  { background-color: var(--warn); }
.bar-fill.error { background-color: var(--error); }

/* ================= Toolbar and inputs ================= */

.toolbar {
    display: flex;
    align-items: flex-end;
    gap: 14px;
    margin-bottom: 16px;
    flex-wrap: wrap;
}

/*
  Reusable per-table filter bar (fragments/filters.html). It sits inside the
  padding-less .table-wrap, between the title and the table, so it needs its own
  horizontal padding to line up with the title rather than hugging the border.
*/
.table-wrap > .filter-bar {
    padding: 12px 15px 4px;
    margin-bottom: 0;
}
.filter-bar .input { min-width: 200px; }
.filter-bar select.input { min-width: 150px; }

/* Status filter control: a fixed width sized for the longest label ("Not in target"),
   so the trigger neither grows nor shrinks as the selection changes and reads the same
   on Compare and Promote. The status select becomes a .fcombo (the native select is
   hidden), so the width is locked on the field wrapper that holds the combo. */
.filter-bar .field:has([data-filter-status]) { flex: 0 0 auto; width: 168px; }
.filter-bar .field:has([data-filter-status]) .fcombo { width: 168px; }

/* Live row-count badge. In a filter bar it sits to the far right of the controls;
   in a table title (a table with no filter controls) it also floats right. vitals.js
   keeps its text in step with the serial-number column as filters change. */
.table-count {
    font-size: 12px;
    color: var(--text-faint);
    font-family: var(--mono);
    white-space: nowrap;
}
.filter-bar .table-count {
    margin-left: auto;
    align-self: flex-end;
    padding-bottom: 6px;
}
/* When placed in a table title, push it to the right edge of the title row. */
.table-title { display: flex; align-items: center; gap: 8px; }
.table-title > .table-count { margin-left: auto; }

/* Serial-number column: a narrow, right-aligned, faint index down the left edge.
   The "#" header must right-align too, or it sits left while every number under it
   sits right — the header and its own column visibly disagreeing. */
.data th.sl-col, .data td.sl-col { width: 1%; white-space: nowrap; text-align: center; }
.data td.sl-col { color: var(--text-faint); font-family: var(--mono); }
/* Center-align the serial number cell contents. This must override the generic
   `td.num .cell-val { justify-content: flex-end }` rule above, since the serial column
   carries both `num` and `sl-col` classes — the extra `.num` selector raises specificity
   to match, and being later in the file wins the tie. */
table.data.cell-boxed td.sl-col.num .cell-val,
table.data.cell-boxed td.sl-col .cell-val { justify-content: center; }

/* ---- Right-Sizing tree (namespace row expands to per-workload child rows) ---- */
/* The whole namespace row is the toggle: clicking anywhere on it expands the workloads.
   Show a pointer and a hover tint so the affordance is obvious. */
.rs-tree tr.rs-row-toggle { cursor: pointer; }
.rs-tree tr.rs-row-toggle:hover { background: color-mix(in srgb, var(--accent) 7%, transparent); }
.rs-tree tr.rs-row-toggle:hover .rs-caret { color: var(--accent); }
.rs-tree tr.rs-row-toggle:focus-visible {
    outline: 2px solid var(--accent);
    outline-offset: -2px;
}

/* A larger, easier-to-hit caret. Sized up from 12px and given a fixed-width gutter so the
   namespace name still lines up whether or not a row has children. */
.rs-caret {
    display: inline-block;
    width: 18px;
    margin-right: 4px;
    color: var(--text-dim);
    font-size: 17px;
    line-height: 1;
    text-align: center;
    transition: color 0.12s ease, transform 0.12s ease;
}
/* Reserve the same left gutter on rows with no children, so names line up with parents. */
.rs-toggle-spacer { display: inline-block; width: 18px; margin-right: 4px; }
.rs-child-count { font-size: 11px; margin-left: 6px; }

/* Child workload rows: a faint tinted band, mono name. The child name left-aligns with the
   PARENT namespace name (both start after the same 22px caret gutter) rather than being
   indented under the caret — a subtle guide line keeps the tree readable without pushing the
   child text further right than its parent. */
.rs-tree tr.rs-child { background: color-mix(in srgb, var(--text-faint) 6%, transparent); }
.rs-tree tr.rs-child:hover { background: color-mix(in srgb, var(--text-faint) 10%, transparent); }
.rs-child-name { padding-left: 22px; position: relative; }
.rs-child-name::before {
    content: '';
    position: absolute;
    left: 8px;
    top: 0;
    bottom: 0;
    border-left: 2px solid var(--border);
}
.rs-child .rs-kind { font-size: 10.5px; margin-left: 6px; }

/* The client-side filter hides non-matching items with the hidden attribute. A card/list
   item styled with its own `display` would otherwise ignore it, so enforce hiding for any
   filterable item. Table rows do not need this; card/list views (data-filter-item) do. */
[data-filter-item][hidden] { display: none !important; }

/* Replace the native <select> arrow with the same chevron the combobox uses, so the
   two controls match. Light theme stroke is --text-dim (#454f50); the dark theme
   overrides the image below with its lighter stroke. */
select.input {
    appearance: none;
    -webkit-appearance: none;
    -moz-appearance: none;
    padding-right: 30px;
    background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='12' height='12' viewBox='0 0 12 12'%3E%3Cpath d='M2.5 4.5 6 8l3.5-3.5' fill='none' stroke='%23454f50' stroke-width='1.5' stroke-linecap='round' stroke-linejoin='round'/%3E%3C/svg%3E");
    background-repeat: no-repeat;
    background-position: right 9px center;
    background-size: 12px 12px;
}
[data-theme="dark"] select.input {
    background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='12' height='12' viewBox='0 0 12 12'%3E%3Cpath d='M2.5 4.5 6 8l3.5-3.5' fill='none' stroke='%23b3c0c7' stroke-width='1.5' stroke-linecap='round' stroke-linejoin='round'/%3E%3C/svg%3E");
}

.field { display: flex; flex-direction: column; gap: 5px; }

.field-label {
    font-size: 10.5px;
    font-weight: 650;
    text-transform: uppercase;
    letter-spacing: 0.5px;
    color: var(--text-faint);
}

/* Filter controls are self-explanatory via placeholder / "All …" option, so the
   label row above them is redundant. Hide it for any field that holds a filter
   input (text/status/multi-select), app-wide, and for the Namespace Details picker,
   whose placeholder already names it — so its control matches the standard filter bar
   on every other page. Other functional pickers (diagnostics pod/container/lines)
   stay labelled. */
.field:has(> [data-filter-name]) > .field-label,
.field:has(> [data-filter-status]) > .field-label,
.field:has(> #ns-detail-select) > .field-label,
.combo[data-filter-select] > .field-label { display: none; }

.input {
    background: var(--bg);
    border: 1px solid var(--border-strong);
    border-radius: var(--radius-sm);
    padding: 7px 10px;
    color: var(--text);
    font-size: 13px;
    font-family: inherit;
    min-width: 180px;
}

.input:focus {
    outline: none;
    border-color: var(--accent);
    box-shadow: 0 0 0 3px var(--accent-soft);
}

/* ================= Searchable multi-select combobox ================= */

.combo { position: relative; }

.combo-control { position: relative; display: flex; align-items: center; }

.combo-input {
    min-width: 220px;
    width: 100%;
    padding-right: 58px;   /* room for the summary + caret */
}

/* When options are selected the count sits over the input's right side. */
.combo-summary {
    position: absolute;
    right: 26px;
    font-size: 11.5px;
    color: var(--text-dim);
    pointer-events: none;
}

/* Uses the exact same SVG chevron as the native <select>, so the two controls
   match pixel-for-pixel. The text glyph from the markup is hidden; the arrow is
   drawn as a 12px background image, with a dark-theme colour override below. */
.combo-caret {
    position: absolute;
    right: 9px;
    width: 12px;
    height: 12px;
    font-size: 0;                 /* hide the ▾ text glyph */
    color: transparent;
    pointer-events: none;
    transition: transform 0.12s ease;
    background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='12' height='12' viewBox='0 0 12 12'%3E%3Cpath d='M2.5 4.5 6 8l3.5-3.5' fill='none' stroke='%23454f50' stroke-width='1.5' stroke-linecap='round' stroke-linejoin='round'/%3E%3C/svg%3E");
    background-repeat: no-repeat;
    background-position: center;
    background-size: 12px 12px;
}
[data-theme="dark"] .combo-caret {
    background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='12' height='12' viewBox='0 0 12 12'%3E%3Cpath d='M2.5 4.5 6 8l3.5-3.5' fill='none' stroke='%23b3c0c7' stroke-width='1.5' stroke-linecap='round' stroke-linejoin='round'/%3E%3C/svg%3E");
}
.combo.combo-open .combo-caret { transform: rotate(180deg); }

/*
  Fixed positioning so the menu escapes the .table-wrap's overflow:hidden clip
  (the filter bar sits inside that clipped box). vitals.js sets top/left/width to
  align it under the control when opening, and keeps it in sync on scroll/resize.
*/
.combo-menu {
    display: none;
    position: fixed;
    z-index: 200;
    min-width: 240px;
    max-height: 320px;
    overflow-y: auto;
    background: var(--bg);
    border: 1px solid var(--border-strong);
    border-radius: var(--radius-sm);
    box-shadow: var(--shadow-md, 0 6px 20px rgba(0,0,0,0.18));
    padding: 4px;
}
.combo.combo-open .combo-menu { display: block; }

.combo-option {
    display: flex;
    align-items: center;
    gap: 9px;
    padding: 7px 9px;
    font-size: 13px;
    border-radius: var(--radius-sm);
    cursor: pointer;
}
.combo-option:hover { background: var(--bg-subtle); }
.combo-option input { flex-shrink: 0; }
.combo-option[hidden] { display: none; }

/* "Clear selection" action at the top of the namespace picker menu. */
.combo-clear {
    display: block;
    width: 100%;
    text-align: left;
    background: none;
    border: 0;
    border-bottom: 1px solid var(--border);
    padding: 7px 9px;
    margin-bottom: 4px;
    font: inherit;
    font-size: 12px;
    color: var(--accent);
    cursor: pointer;
    border-radius: var(--radius-sm) var(--radius-sm) 0 0;
}
.combo-clear:hover { background: var(--accent-soft); }

/* ================= Usage cells ================= */

/*
  Numbers on top, proportion bar underneath. Both matter: the numbers for capacity
  planning, the bar for spotting a workload near its limit at a glance.
*/
.usage-cell {
    display: flex;
    flex-direction: column;
    gap: 3px;
    min-width: 150px;
}

.usage-text { font-size: 12px; white-space: nowrap; }

.usage-cell .bar { width: 100%; min-width: 0; }

.filter-empty {
    margin: 0;
    padding: 16px;
    text-align: center;
    color: var(--text-faint);
    font-size: 13px;
}

/* ================= Drill-down modal ================= */

.modal-overlay {
    position: fixed;
    inset: 0;
    z-index: 100;
    display: flex;
    align-items: center;
    justify-content: center;
    padding: 32px 20px;
    background: color-mix(in srgb, var(--text) 34%, transparent);
    backdrop-filter: blur(3px);
}

.modal-overlay[hidden] { display: none; }

/* Prevents the page scrolling behind an open modal */
body.modal-open { overflow: hidden; }

.modal {
    width: 100%;
    /* Wide enough that a table of full pod names plus several columns (Restarts,
       State, Last restart) reads on one line without wrapping. Still width:100% so it
       shrinks on narrower viewports, and the min() keeps a gutter on small screens. */
    max-width: min(1040px, 94vw);
    max-height: 86vh;
    display: flex;
    flex-direction: column;
    background: var(--surface, var(--bg));
    border: 1px solid var(--border);
    border-radius: 16px;
    box-shadow: 0 24px 64px color-mix(in srgb, var(--text) 24%, transparent),
                0 2px 8px color-mix(in srgb, var(--text) 12%, transparent);
    overflow: hidden;
    /* A quick, calm entrance so the modal reads as a distinct surface popping forward. */
    animation: modal-pop 0.16s cubic-bezier(0.2, 0.8, 0.3, 1);
}

@keyframes modal-pop {
    from { opacity: 0; transform: translateY(8px) scale(0.98); }
    to   { opacity: 1; transform: none; }
}

/* Header bar sits on top: a titled band with a gentle accent wash and a clear divider,
   so every modal reads as a proper window. */
.modal-head {
    display: flex;
    align-items: center;
    gap: 12px;
    padding: 14px 18px;
    border-bottom: 1px solid var(--border);
    background: linear-gradient(180deg,
        color-mix(in srgb, var(--accent) 8%, var(--bg-inset)),
        var(--bg-inset));
    flex-shrink: 0;
}

/* App logo on every modal header: the VITALS brand mark (theme brand gradient + white
   pulse), so a modal reads as part of the application. Injected here so all modals — the
   server-loaded fragments and the client-built info modals, which all share .modal-head —
   get it without editing each one. */
.modal-head::before {
    content: "";
    flex: 0 0 auto;
    align-self: flex-start;
    width: 30px;
    height: 30px;
    border-radius: 9px;
    background-image:
        url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 32 32' fill='none' stroke='%23FFFFFF' stroke-width='2' stroke-linecap='round' stroke-linejoin='round'%3E%3Cpath d='M7.6 16.6h3.1l2.1-5.4 2.9 10.2 2.3-6.4 1.5 1.6h3'/%3E%3C/svg%3E"),
        var(--brand-gradient);
    background-repeat: no-repeat;
    background-position: center, center;
    background-size: 21px 21px, cover;
    box-shadow: inset 0 1px 0 rgba(255,255,255,0.25);
    margin-top: 1px;
}
/* The close button sits at the far right; the title block takes the middle. */
.modal-head .modal-close { margin-left: auto; }

.modal-title {
    margin: 0;
    font-size: 15.5px;
    font-weight: 650;
    letter-spacing: -0.2px;
    color: var(--text);
}

.modal-sub { margin: 4px 0 0; font-size: 11.5px; color: var(--text-faint); }

.modal-close {
    background: none;
    border: 1px solid transparent;
    color: var(--text-faint);
    font-size: 20px;
    line-height: 1;
    cursor: pointer;
    width: 30px; height: 30px;
    display: grid; place-items: center;
    border-radius: 8px;
    flex-shrink: 0;
    transition: background 0.12s ease, color 0.12s ease, border-color 0.12s ease;
}

.modal-close:hover {
    color: var(--text);
    background: var(--bg-subtle);
    border-color: var(--border);
}
.modal-close:focus-visible { outline: 2px solid var(--accent); outline-offset: 1px; }

.modal-body { overflow-y: auto; padding: 18px; }

.modal-body .table-scroll {
    border: 1px solid var(--border);
    border-radius: var(--radius-sm);
    overflow: auto;
}

/*
  Keep modal table cells on one line so identifier-style values (pod names like
  embedding-service-7d9f8c-x2kfp) are not broken mid-token. The wider modal fits the
  common tables outright; anything genuinely too wide scrolls horizontally inside the
  .table-scroll above rather than wrapping. The STATE column is allowed to wrap since it
  carries a longer phrase and reads fine on two lines.
*/
.modal-body table.data th,
.modal-body table.data td {
    white-space: nowrap;
}
.modal-body table.data td.wrap,
.modal-body table.data th.wrap {
    white-space: normal;
}

/*
  The insight line, which is the reason the modal exists: the table is evidence, this
  is the reading of it.
*/
.insight {
    padding: 11px 13px;
    border-radius: var(--radius-sm);
    font-size: 13px;
    line-height: 1.5;
    margin-bottom: 14px;
    border: 1px solid transparent;
}

.insight-ok    { background: var(--ok-soft);    color: var(--ok);
                 border-color: color-mix(in srgb, var(--ok) 26%, transparent); }
.insight-warn  { background: var(--warn-soft);  color: var(--warn);
                 border-color: color-mix(in srgb, var(--warn) 26%, transparent); }
.insight-error { background: var(--error-soft); color: var(--error);
                 border-color: color-mix(in srgb, var(--error) 26%, transparent); }

/* ---- Drillable cells ---- */

/*
  Cells are buttons but must not look like a wall of buttons: the affordance is a
  subtle hover, and the pill inside carries the meaning.
*/
.cell-btn {
    display: block;
    width: 100%;
    padding: 3px 5px;
    margin: -3px -5px;
    background: none;
    border: none;
    border-radius: 5px;
    font-family: inherit;
    font-size: inherit;
    color: inherit;
    text-align: inherit;
    cursor: pointer;
    transition: background 0.1s;
}

.cell-btn:hover { background: var(--bg-subtle); }

.cell-btn:focus-visible {
    outline: 2px solid var(--accent);
    outline-offset: -1px;
}

.cell-btn.dim { color: var(--text-faint); }

table.drillable td { padding-top: 6px; padding-bottom: 6px; }

/* ================= Readiness score ================= */

/*
  The go/no-go verdict. Sized to be the first thing read, because "should we ship" is the
  question being asked and a number does not answer it.
*/
.verdict {
    display: flex;
    align-items: flex-start;
    gap: 14px;
    padding: 16px 18px;
    border-radius: var(--radius);
    border: 1px solid transparent;
    margin-bottom: 18px;
}

.verdict-ok    { background: var(--ok-soft);
                 border-color: color-mix(in srgb, var(--ok) 30%, transparent); }
.verdict-warn  { background: var(--warn-soft);
                 border-color: color-mix(in srgb, var(--warn) 30%, transparent); }
.verdict-error { background: var(--error-soft);
                 border-color: color-mix(in srgb, var(--error) 34%, transparent); }

.verdict-mark {
    width: 34px;
    height: 34px;
    border-radius: 50%;
    display: flex;
    align-items: center;
    justify-content: center;
    font-size: 17px;
    font-weight: 700;
    flex-shrink: 0;
    background: color-mix(in srgb, var(--bg) 60%, transparent);
}

.verdict-ok .verdict-mark    { color: var(--ok); }
.verdict-warn .verdict-mark  { color: var(--warn); }
.verdict-error .verdict-mark { color: var(--error); }

.verdict-body { min-width: 0; }

.verdict-label {
    font-size: 16px;
    font-weight: 700;
    letter-spacing: -0.2px;
    margin-bottom: 3px;
}

.verdict-ok .verdict-label    { color: var(--ok); }
.verdict-warn .verdict-label  { color: var(--warn); }
.verdict-error .verdict-label { color: var(--error); }

.verdict-summary {
    margin: 0;
    font-size: 13px;
    color: var(--text-dim);
    line-height: 1.55;
    max-width: 90ch;
}

.score-row {
    display: grid;
    grid-template-columns: 190px 1fr;
    gap: 12px;
    margin-bottom: 22px;
    align-items: stretch;
}

.score-card {
    background: var(--surface);
    border: 1px solid var(--border);
    border-radius: var(--radius);
    padding: 16px 18px;
    box-shadow: var(--shadow-sm);
    display: flex;
    flex-direction: column;
    justify-content: center;
}

.score-caption {
    font-size: 10px;
    font-weight: 650;
    text-transform: uppercase;
    letter-spacing: 0.5px;
    color: var(--text-faint);
    margin-bottom: 4px;
}

.score-value {
    font-size: 46px;
    font-weight: 700;
    font-family: var(--mono);
    line-height: 1;
    letter-spacing: -2px;
}

/* The "/100" reads as a unit, not part of the figure, so keep it small and dim. */
.score-max {
    font-size: 18px;
    font-weight: 500;
    color: var(--text-faint);
    letter-spacing: 0;
    margin-left: 2px;
}

.score-ok    { color: var(--ok); }
.score-info  { color: var(--info); }
.score-warn  { color: var(--warn); }
.score-error { color: var(--error); }

.score-label {
    font-size: 13px;
    font-weight: 650;
    margin-top: 6px;
}

.score-sub { font-size: 11px; color: var(--text-faint); margin-top: 2px; }

.score-stats { margin-bottom: 0; }

/* ---- Findings ---- */

.finding-list { padding: 4px; }

.finding {
    padding: 12px 14px;
    border-bottom: 1px solid var(--border);
}

.finding:last-child { border-bottom: none; }

.finding-head {
    display: flex;
    align-items: baseline;
    gap: 9px;
    flex-wrap: wrap;
    margin-bottom: 6px;
}

.finding-subject { font-size: 12.5px; font-weight: 600; }

.finding-detail { font-size: 13px; }

/* Which rule produced this, pushed right so it does not compete with the finding */
.finding-check {
    margin-left: auto;
    font-size: 10.5px;
    color: var(--text-faint);
    text-transform: uppercase;
    letter-spacing: 0.5px;
}

.finding-impact {
    margin: 0 0 6px;
    font-size: 12.5px;
    color: var(--text-dim);
    line-height: 1.55;
}

.finding-remedy {
    margin: 0;
    font-size: 12.5px;
    color: var(--text);
    line-height: 1.55;
}

.remedy-label {
    display: inline-block;
    font-size: 10px;
    font-weight: 700;
    text-transform: uppercase;
    letter-spacing: 0.6px;
    color: var(--accent);
    background: var(--accent-soft);
    border: 1px solid var(--accent-line);
    border-radius: 4px;
    padding: 1px 6px;
    margin-right: 7px;
    vertical-align: 1px;
}

/* Workload Doctor: the "View crash log" button, pushed to the right of the head. */
.doctor-log-btn { margin-left: 8px; }

/* Readiness findings: the "Inspect" deep-link sits at the right of the finding head,
   just after the check label, so every finding is one click from the page that
   proves it and lets the reader act. */
.finding-inspect {
    margin-left: 12px;
    font-size: 11.5px;
    font-weight: 600;
    flex-shrink: 0;
}

/* Restart Leaderboard: inline severity legend beside a table title, and the dot
   separating a row's two actions. */
.legend-inline {
    margin-left: 12px;
    font-size: 11px;
    font-weight: 400;
    color: var(--text-dim);
    text-transform: none;
    letter-spacing: 0;
}

.legend-inline .pill { margin: 0 3px 0 9px; }
.legend-inline .pill:first-child { margin-left: 0; }

.row-action-sep { color: var(--text-faint); margin: 0 5px; }

/* A table row flagged as the highest-severity finding (e.g. an active blast radius). */
table.data tbody tr.row-error td { background: var(--error-soft, rgba(220,60,60,0.06)); }

/* ---- Help ---- */

.help-section { margin: 0 0 22px; max-width: 92ch; }
.help-section h3 { margin: 0 0 8px; font-size: 14px; }

.help-text {
    margin: 0 0 8px;
    font-size: 13px;
    color: var(--text-dim);
    line-height: 1.6;
}

.help-list {
    margin: 0;
    padding-left: 18px;
    font-size: 13px;
    color: var(--text-dim);
    line-height: 1.6;
}

.help-list li { margin-bottom: 6px; }
.help-list strong { color: var(--text); font-weight: 600; }

/* ---- Help page, modern layout ---- */

.help-heading {
    margin: 26px 0 12px;
    font-size: 13px;
    font-weight: 650;
    letter-spacing: 0.02em;
    text-transform: uppercase;
    color: var(--text-faint);
}

/* Hero band */
.help-hero {
    display: flex;
    gap: 16px;
    align-items: flex-start;
    padding: 18px 20px;
    margin: 2px 0 8px;
    border-radius: var(--radius);
    background: linear-gradient(135deg,
        color-mix(in srgb, var(--accent) 12%, transparent),
        color-mix(in srgb, var(--accent) 4%, transparent));
    border: 1px solid var(--accent-line, color-mix(in srgb, var(--accent) 24%, transparent));
}
.help-hero-mark {
    flex: 0 0 auto;
    width: 40px; height: 40px;
    display: grid; place-items: center;
    border-radius: 10px;
    background: color-mix(in srgb, var(--accent) 18%, transparent);
    color: var(--accent);
}
.help-hero-mark .icon { width: 22px; height: 22px; }
.help-hero-title { margin: 2px 0 6px; font-size: 15px; font-weight: 650; color: var(--text); }
.help-hero-text { margin: 0; font-size: 13px; line-height: 1.6; color: var(--text-dim); max-width: 90ch; }

/* Card grid for the safety promises and note cards */
.help-card-grid {
    display: grid;
    grid-template-columns: repeat(auto-fit, minmax(240px, 1fr));
    gap: 12px;
    margin: 0 0 8px;
}
.help-card {
    padding: 14px 16px;
    border-radius: var(--radius);
    background: var(--surface, var(--bg-subtle));
    border: 1px solid var(--border);
}
.help-card-icon {
    width: 30px; height: 30px;
    display: grid; place-items: center;
    border-radius: 8px;
    margin-bottom: 9px;
    background: var(--bg-subtle);
    color: var(--text-dim);
    border: 1px solid var(--border);
}
.help-card-icon .icon { width: 17px; height: 17px; }
.help-card-icon.ok   { background: var(--ok-soft);   color: var(--ok);   border-color: color-mix(in srgb, var(--ok) 30%, transparent); }
.help-card-icon.warn { background: var(--warn-soft); color: var(--warn); border-color: color-mix(in srgb, var(--warn) 30%, transparent); }
.help-card-title { font-size: 13.5px; font-weight: 650; color: var(--text); margin-bottom: 5px; }
.help-card-text  { margin: 0; font-size: 12.5px; line-height: 1.55; color: var(--text-dim); }
.help-card-demo  { border-color: color-mix(in srgb, var(--warn) 40%, transparent); background: var(--warn-soft); max-width: 520px; }

/* Navigation section grid */
.help-nav-grid {
    display: grid;
    grid-template-columns: repeat(auto-fit, minmax(280px, 1fr));
    gap: 10px;
    margin: 4px 0 8px;
}
.help-nav-item {
    display: flex;
    gap: 12px;
    align-items: flex-start;
    padding: 12px 14px;
    border-radius: var(--radius);
    background: var(--surface, var(--bg-subtle));
    border: 1px solid var(--border);
}
.help-nav-item .icon {
    flex: 0 0 auto;
    width: 18px; height: 18px;
    margin-top: 1px;
    color: var(--accent);
}
.help-nav-name { font-size: 13px; font-weight: 650; color: var(--text); margin-bottom: 2px; }
.help-nav-desc { font-size: 12px; line-height: 1.5; color: var(--text-dim); }

/* Questions VITALS answers: page label + answer, two columns on wide screens */
.help-qa-grid {
    display: grid;
    grid-template-columns: repeat(auto-fit, minmax(340px, 1fr));
    gap: 8px 20px;
    margin: 4px 0 14px;
}
.help-qa {
    display: grid;
    grid-template-columns: 150px 1fr;
    gap: 12px;
    padding: 9px 0;
    border-bottom: 1px solid var(--border);
    align-items: baseline;
}
.help-qa-page {
    font-size: 12.5px;
    font-weight: 650;
    color: var(--text);
}
.help-qa-ans { font-size: 12.5px; line-height: 1.5; color: var(--text-dim); }

@media (max-width: 640px) {
    .help-qa { grid-template-columns: 1fr; gap: 2px; }
    .help-hero { flex-direction: column; }
}

/* ---- Cluster Snapshot ---- */

/* Actions sit at the top-right of the module head, beside the title. */
.module-actions {
    display: flex;
    gap: 8px;
    align-items: center;
    flex-shrink: 0;
}

.snapshot-wrap {
    border: 1px solid var(--border);
    border-radius: var(--radius-sm);
    overflow: auto;
    max-height: 64vh;
}

.snapshot-md {
    margin: 0;
    padding: 16px 18px;
    font-family: var(--mono);
    font-size: 12px;
    line-height: 1.6;
    color: var(--text);
    white-space: pre-wrap;
    word-break: break-word;
    background: var(--bg-inset);
}

/* ---- Change Timeline ---- */

.timeline {
    margin-top: 6px;
    position: relative;
    padding-left: 6px;
}

/* The filter bar sits above the thread, not on it: cancel the left padding and give
   it breathing room so it does not align to the marker column. */
.timeline > .filter-bar {
    margin: 0 0 12px -6px;
}

/* The client-side filter hides non-matching entries with the hidden attribute. A class
   that sets `display` would otherwise override the attribute's default display:none, so
   filtered-out timeline entries must be explicitly hidden. */
.tl-entry[hidden] { display: none; }

.tl-entry {
    display: grid;
    grid-template-columns: 16px 1fr;
    gap: 12px;
    padding: 10px 10px 10px 8px;
    border-bottom: 1px solid var(--border);
    border-radius: var(--radius-sm);
    transition: background 0.12s ease, box-shadow 0.12s ease, transform 0.08s ease;
}

.tl-entry:last-child { border-bottom: none; }

/* A quiet hover lift so each entry reads as its own card in the thread. */
.tl-entry:hover {
    background: color-mix(in srgb, var(--accent) 4%, var(--bg));
    box-shadow: var(--shadow-sm);
    transform: translateY(-1px);
}

/* The marker dot and the thread it sits on. A soft ring in the category colour makes the
   node read as a real timeline stop rather than a flat dot. */
.tl-marker {
    position: relative;
    width: 11px;
    height: 11px;
    margin-top: 4px;
    border-radius: 50%;
    background: var(--text-faint);
    box-shadow: 0 0 0 3px color-mix(in srgb, var(--text-faint) 22%, transparent);
    flex-shrink: 0;
}

.tl-marker.tl-warn {
    background: var(--warn);
    box-shadow: 0 0 0 3px color-mix(in srgb, var(--warn) 22%, transparent);
}
.tl-marker.tl-info {
    background: var(--accent);
    box-shadow: 0 0 0 3px color-mix(in srgb, var(--accent) 22%, transparent);
}
.tl-marker.tl-neutral {
    background: var(--text-faint);
    box-shadow: 0 0 0 3px color-mix(in srgb, var(--text-faint) 20%, transparent);
}

/* A connecting line down the column, so the entries read as one thread. Drawn only
   when another entry follows (:has), so the filter bar, the empty-state message, or a
   hidden trailing entry never leaves a dangling connector below the last one. */
.tl-entry:has(~ .tl-entry) .tl-marker::after {
    content: "";
    position: absolute;
    left: 50%;
    top: 14px;
    transform: translateX(-50%);
    width: 1px;
    height: calc(100% + 18px);
    background: var(--border);
}

.tl-head {
    display: flex;
    align-items: baseline;
    gap: 9px;
    flex-wrap: wrap;
}

.tl-reason { font-size: 12.5px; font-weight: 600; }
.tl-object { font-size: 12px; color: var(--text-dim); }
.tl-count  { font-size: 10px; }
.tl-age    { margin-left: auto; font-size: 11.5px; }

.tl-message {
    margin: 3px 0 0;
    font-size: 12.5px;
    color: var(--text-dim);
    line-height: 1.5;
}

/* Explanatory line above the log tail in the Doctor modal. */
.log-note {
    margin: 0 0 10px;
    font-size: 12px;
    color: var(--text-dim);
    line-height: 1.5;
}

/* Constrain the log tail so a long trace scrolls inside the modal, not the page. */
.modal-body .log-body { max-height: 52vh; overflow: auto; }

.modal-actions {
    margin-top: 12px;
    display: flex;
    justify-content: flex-end;
    gap: 8px;
}

/*
  Hanging indent for any remedy line that carries a label (Fix / Next).
  Without this, a wrapped remedy sentence returns to the far-left margin under the
  label pill rather than under the sentence — so a fragment like "the node." reads
  as a stray line. Making the paragraph a flex row puts the label in its own column
  and lets the text wrap as a block beside it. Applies wherever the pattern is used
  (Readiness findings, Workload Doctor next steps); the label-less .finding-remedy on
  the Overview card is untouched because it has no .remedy-label child.
*/
.finding-remedy:has(> .remedy-label) {
    display: flex;
    align-items: baseline;
    gap: 7px;
}

/* The flex gap handles spacing now, so the label's block-layout margin is dropped. */
.finding-remedy:has(> .remedy-label) .remedy-label { margin-right: 0; }

/* Everything after the label shares one wrapping column, so wraps hang correctly. */
.finding-remedy:has(> .remedy-label) > :not(.remedy-label) { flex: 1; }

.next-step { margin-bottom: 4px; }

/* Workload Doctor: the correlated evidence behind a verdict, one fact per row. */
.signal-list {
    margin: 2px 0 10px;
    display: flex;
    flex-direction: column;
    gap: 5px;
}

.signal-row {
    display: flex;
    align-items: baseline;
    gap: 9px;
}

.signal-detail {
    font-size: 12.5px;
    color: var(--text-dim);
    line-height: 1.5;
    max-width: 92ch;
}

/* A compact pill for the signal "kind" label, narrower than a table pill. */
.pill-sm {
    font-size: 9.5px;
    padding: 0 6px;
    min-width: 58px;
    text-align: center;
    flex-shrink: 0;
}

/* In the grouped tables the impact sits under the detail rather than beside it */
.finding-impact-inline {
    display: block;
    font-size: 11.5px;
    color: var(--text-faint);
    margin-top: 3px;
    line-height: 1.5;
}

.category-note {
    margin-left: auto;
    font-size: 11px;
    font-weight: 400;
    color: var(--text-faint);
    text-transform: none;
    letter-spacing: 0;
}

@media (max-width: 720px) {
    .score-row { grid-template-columns: 1fr; }
}

/* ================= Connectivity matrix ================= */

/*
  Sized to content rather than stretched to the container: with `width:100%` the row-header
  column absorbed all the slack and pushed the cells into a cramped strip on the right.
*/
table.matrix {
    border-collapse: separate;
    border-spacing: 0;
    width: auto;
    min-width: 0;
}

.matrix-corner {
    font-size: 9.5px !important;
    color: var(--text-faint);
    white-space: nowrap;
    position: sticky;
    /* Pinned on both axes: the corner must stay put whether the matrix scrolls
       sideways (many columns) or down (many rows), so it sits above every other
       sticky element. */
    left: 0;
    top: 0;
    background: var(--bg-inset);
    z-index: 5;
    /* Bounded, so the header cannot claim the space the cells need */
    width: 1%;
}

/*
  Column headers are rotated fully vertical (reading bottom-to-top). A 45/60-degree
  slant drifted each label away from its column, so a header no longer sat above the
  cells it names. At 90 degrees the label is centred on its own 34px column and lines
  up with the marks beneath it.
*/
.matrix-head {
    height: 120px;
    padding: 0 !important;
    vertical-align: bottom;
    white-space: nowrap;
    width: 34px;
    max-width: 34px;
    /* Pin the rotated column headers to the top so they stay visible when a tall
       matrix scrolls down, matching the sticky behaviour of data tables. */
    position: sticky;
    top: 0;
    background: var(--bg-inset);
    z-index: 3;
}

.matrix-head span {
    /*
      Vertical label using writing-mode, which lays the text out bottom-to-top
      within the cell reliably — unlike a rotated flex box, which rendered outside
      the 34px cell and appeared blank. mixed orientation keeps the glyphs upright
      but stacked; the 180deg rotate makes it read bottom-to-top so it sits under
      the column rather than over it.
    */
    display: inline-block;
    writing-mode: vertical-rl;
    transform: rotate(180deg);
    text-orientation: mixed;
    white-space: nowrap;
    max-height: 112px;
    overflow: hidden;
    text-overflow: ellipsis;
    font-size: 10.5px;
    font-family: var(--mono);
    text-transform: none;
    letter-spacing: 0;
}

/* The row header stays visible while the matrix scrolls sideways */
.matrix-row-head {
    position: sticky;
    left: 0;
    background: var(--bg);
    font-size: 12px;
    white-space: nowrap;
    z-index: 1;
    border-right: 1px solid var(--border);
    /* Shrink to the longest namespace name; the cells take the rest */
    width: 1%;
    padding-right: 18px !important;
}

table.matrix tbody tr:hover .matrix-row-head { background: var(--bg-inset); }

.matrix-cell {
    text-align: center;
    padding: 4px !important;
    width: 34px;
}

.matrix-mark {
    display: inline-flex;
    align-items: center;
    justify-content: center;
    width: 22px;
    height: 22px;
    border-radius: 6px;
    font-size: 12px;
    font-weight: 700;
    cursor: default;
    border: 1px solid transparent;
}

.mark-allowed    { background: var(--ok-soft);    color: var(--ok);
                   border-color: color-mix(in srgb, var(--ok) 26%, transparent); }
.mark-open       { background: var(--bg-subtle);  color: var(--text-faint);
                   border-color: var(--border); }
.mark-denied     { background: var(--bg-subtle);  color: var(--text-faint); }
.mark-referenced { background: var(--accent-soft); color: var(--accent);
                   border-color: var(--accent-line); }
/* A configured link that policy blocks: the one cell that demands attention */
.mark-conflict   { background: var(--error-soft); color: var(--error);
                   border-color: color-mix(in srgb, var(--error) 40%, transparent); }
.mark-self       { background: none; color: var(--border-strong); }
.mark-unknown    { background: var(--bg-subtle);  color: var(--text-faint); }

.matrix-legend {
    display: flex;
    flex-wrap: wrap;
    gap: 16px;
    padding: 12px 14px;
    border-top: 1px solid var(--border);
    font-size: 11.5px;
    color: var(--text-dim);
}

.matrix-legend span { display: inline-flex; align-items: center; gap: 6px; }

.matrix-legend .matrix-mark { width: 18px; height: 18px; font-size: 10.5px; }

/* ================= Diagnostic console ================= */

.target-fields { display: flex; gap: 14px; align-items: flex-end; flex-wrap: wrap; }

.console-layout {
    display: grid;
    grid-template-columns: 290px 1fr;
    gap: 16px;
    align-items: start;
}

.console-library {
    background: var(--surface);
    border: 1px solid var(--border);
    border-radius: var(--radius);
    overflow: hidden;
    box-shadow: var(--shadow-sm);
}

.library-scroll { max-height: 62vh; overflow-y: auto; padding: 6px; }

.command-group { margin-bottom: 8px; }

.command-group-label {
    font-size: 10px;
    font-weight: 700;
    text-transform: uppercase;
    letter-spacing: 0.7px;
    color: var(--text-faint);
    padding: 7px 9px 4px;
}

.command-item {
    display: flex;
    align-items: center;
    justify-content: space-between;
    gap: 8px;
    width: 100%;
    padding: 6px 9px;
    margin-bottom: 1px;
    background: none;
    border: none;
    border-radius: var(--radius-sm);
    color: var(--text-dim);
    font-family: inherit;
    font-size: 12.5px;
    text-align: left;
    cursor: pointer;
    transition: background 0.1s, color 0.1s;
}

.command-item:hover { background: var(--bg-subtle); color: var(--text); }

.command-item.active {
    background: var(--accent-soft);
    color: var(--accent);
    font-weight: 600;
}

.command-item:focus-visible {
    outline: 2px solid var(--accent);
    outline-offset: -2px;
}

.command-label { flex: 1; min-width: 0; }

/* Scope badge — a small square, one letter, colour-keyed to the inspection's scope so the
   grouped library reads at a glance (echoes the launchpad command library rhythm, in
   vitals' own tokens). Every inspection is a read-only typed API call; the colour marks
   what target it needs, not a command class. */
.command-badge {
    flex-shrink: 0;
    width: 15px;
    height: 15px;
    border-radius: 3px;
    display: inline-grid;
    place-items: center;
    font-family: var(--mono);
    font-size: 9px;
    font-weight: 700;
    line-height: 1;
    color: #fff;
    background: var(--text-faint);
}
.command-badge-cluster   { background: var(--accent); }
.command-badge-namespace { background: var(--info, var(--accent)); }
.command-badge-pod       { background: var(--ok, #2e7d32); }
.command-badge-container { background: var(--warn, #b26a00); }

/* Marks inspections that need a pod or container selected first */
.command-scope {
    font-size: 9.5px;
    font-family: var(--mono);
    color: var(--text-faint);
    background: var(--bg-subtle);
    border: 1px solid var(--border);
    border-radius: 8px;
    padding: 0 5px;
    flex-shrink: 0;
}

/* The result panel is a defined, bordered surface with a header, so the area reads as a
   result pane even before an inspection is picked — never a blank void. */
.console-output {
    min-width: 0;
    border: 1px solid var(--border);
    border-radius: var(--radius);
    background: var(--surface);
    overflow: hidden;
    display: flex;
    flex-direction: column;
}

.console-output-head {
    display: flex;
    align-items: baseline;
    justify-content: space-between;
    padding: 9px 14px;
    border-bottom: 1px solid var(--border);
    background: var(--bg-subtle);
}
.console-output-title { font-size: 12.5px; font-weight: 650; color: var(--text); }
.console-output-hint  { font-size: 11px; color: var(--text-faint); }

.console-output-body { min-height: 320px; }

.console-result {
    border: none;
    border-radius: 0;
    overflow: hidden;
    background: var(--surface);
}

.console-result .log-body { border: none; border-radius: 0; max-height: 56vh; }

.console-result .table-scroll { max-height: 56vh; overflow: auto; }

/* Dark terminal surface for text inspection output (describe, logs, manifests). Fixed dark
   palette in both themes so it always reads as a console, not a document — the same intent
   as the launchpad Cluster Insight output pane, expressed with vitals' spacing/mono token. */
.console-term {
    background: #1e1e1e;
    color: #e6e6e6;
    padding: 12px 14px;
    margin: 0;
    font-family: var(--mono);
    font-size: 12px;
    line-height: 1.5;
    white-space: pre-wrap;
    word-break: break-word;
    overflow: auto;
    max-height: 56vh;
}

/* Empty state centred inside the bordered result body. */
.console-empty {
    min-height: 320px;
    display: flex;
    flex-direction: column;
    align-items: center;
    justify-content: center;
    text-align: center;
    padding: 30px 22px;
    color: var(--text-faint);
    font-size: 13px;
}
.console-empty-icon {
    width: 44px; height: 44px;
    display: grid; place-items: center;
    border-radius: 12px;
    margin-bottom: 12px;
    background: var(--bg-subtle);
    border: 1px solid var(--border);
    color: var(--text-faint);
}
.console-empty-icon .icon { width: 22px; height: 22px; }

@media (max-width: 1100px) {
    .console-layout { grid-template-columns: 1fr; }
    .library-scroll { max-height: 34vh; }
}

/* ================= Log browser ================= */

.log-layout {
    display: grid;
    grid-template-columns: 320px 1fr;
    gap: 16px;
    align-items: start;
}

.log-pods {
    background: var(--surface);
    border: 1px solid var(--border);
    border-radius: var(--radius);
    overflow: hidden;
    box-shadow: var(--shadow-sm);
}

.pod-list {
    list-style: none;
    margin: 0;
    padding: 0;
    max-height: 62vh;
    overflow-y: auto;
}

.pod-entry {
    padding: 10px 13px;
    border-bottom: 1px solid var(--border);
}

.pod-entry:last-child { border-bottom: none; }

.pod-head {
    display: flex;
    align-items: center;
    gap: 7px;
    margin-bottom: 7px;
    flex-wrap: wrap;
}

.pod-name {
    font-size: 12px;
    word-break: break-all;
    flex: 1;
    min-width: 0;
}

.pod-restarts {
    font-size: 10.5px;
    color: var(--warn);
    font-weight: 600;
}

.pod-containers { display: flex; gap: 5px; flex-wrap: wrap; }

.container-chip {
    padding: 3px 9px;
    font-size: 11.5px;
    font-family: var(--mono);
    background: var(--bg-subtle);
    border: 1px solid var(--border);
    border-radius: 11px;
    color: var(--text-dim);
    cursor: pointer;
}

.container-chip:hover {
    background: var(--accent-soft);
    border-color: var(--accent-line);
    color: var(--accent);
}

.log-output { min-width: 0; }

.log-header {
    display: flex;
    align-items: center;
    justify-content: space-between;
    gap: 12px;
    padding: 9px 13px;
    background: var(--bg-inset);
    border: 1px solid var(--border);
    border-bottom: none;
    border-radius: var(--radius) var(--radius) 0 0;
    font-size: 12.5px;
    flex-wrap: wrap;
}

.log-target { font-weight: 600; }

.log-actions { display: flex; align-items: center; gap: 10px; font-size: 11.5px; }

/* Inline line-count selector in the log header */
.log-lines {
    display: inline-flex;
    align-items: center;
    gap: 6px;
}
.log-lines .field-label { margin: 0; }

.input-sm {
    padding: 3px 8px;
    font-size: 11.5px;
    height: auto;
    width: auto;
    min-width: 64px;
}

/*
  Log output is a dark surface in both themes: log text is monospaced, dense and
  read for long stretches, and light-on-dark is easier for that than the reverse.
*/
.log-body {
    margin: 0;
    padding: 13px;
    background: #0d1117;
    color: #d5dde5;
    border: 1px solid var(--border);
    border-radius: 0 0 var(--radius) var(--radius);
    font-family: var(--mono);
    font-size: 12px;
    line-height: 1.55;
    max-height: 62vh;
    overflow: auto;
    white-space: pre;
    tab-size: 4;
}

@media (max-width: 1100px) {
    .log-layout { grid-template-columns: 1fr; }
    .pod-list { max-height: 30vh; }
}

@media (max-width: 900px) {
    :root { --sidebar-w: 208px; }
    .content { padding: 16px 14px 30px; }
}

/* ============================================================
   Home page
   ============================================================ */

/*
 * Screen-reader-only text.
 *
 * Deliberately not the classic `position: absolute` recipe. Absolute positioning resolves
 * against the nearest positioned ancestor, and the shell has none between here and the
 * document, so such an element is measured against the document rather than the scrolling
 * panel. Placed far down a long view it extends the document height and raises a second,
 * browser-level scrollbar beside the panel's own.
 *
 * Clipping to a zero-size box keeps the text in the accessibility tree while occupying no
 * space and, being still in flow, it cannot escape its container.
 */
.sr-only {
    display: inline-block;
    width: 1px;
    height: 1px;
    padding: 0;
    border: 0;
    overflow: hidden;
    clip-path: inset(50%);
    white-space: nowrap;
}

/* Overview sits above the sections and has no collapsible heading of its own */
.nav-section-flat { margin-bottom: 4px; }
.nav-item-home .label { font-weight: 600; }

/* ---- Verdict banner ---- */

.verdict {
    display: grid;
    grid-template-columns: auto minmax(0, 1fr) auto auto;
    gap: 18px;
    align-items: center;
    background: var(--bg);
    border: 1px solid var(--border);
    border-left-width: 3px;
    border-radius: var(--radius);
    padding: 18px 20px;
    margin-bottom: 22px;
    box-shadow: var(--shadow-sm);
}

.verdict-ok    { border-left-color: var(--ok);    background: var(--ok-soft); }
.verdict-warn  { border-left-color: var(--warn);  background: var(--warn-soft); }
.verdict-error { border-left-color: var(--error); background: var(--error-soft); }

.verdict-mark {
    width: 40px; height: 40px;
    border-radius: 50%;
    display: flex; align-items: center; justify-content: center;
    font-size: 20px; font-weight: 600;
    background: var(--bg);
    border: 1px solid var(--border);
}

.verdict-ok    .verdict-mark { color: var(--ok); }
.verdict-warn  .verdict-mark { color: var(--warn); }
.verdict-error .verdict-mark { color: var(--error); }

.verdict-body h3 {
    margin: 0 0 4px;
    font-size: 15.5px;
    font-weight: 650;
}

.verdict-body p {
    margin: 0;
    font-size: 13px;
    color: var(--text-dim);
    line-height: 1.5;
    max-width: 92ch;
}

.verdict-counts {
    display: flex;
    flex-wrap: wrap;
    gap: 8px;
    margin-top: 12px;
}

.count-chip {
    display: inline-flex;
    align-items: baseline;
    gap: 5px;
    background: var(--bg);
    border: 1px solid var(--border);
    border-radius: 999px;
    padding: 4px 11px;
    font-size: 12px;
    color: var(--text-dim);
    cursor: pointer;
    font-family: inherit;
    transition: border-color .12s, background .12s;
}

.count-chip:hover { border-color: var(--accent); background: var(--bg-hover); }
.count-chip strong { font-size: 13.5px; font-weight: 650; color: var(--text); }
.count-error strong { color: var(--error); }
.count-warn  strong { color: var(--warn); }

/*
 * The shared readiness summary: two tiles side by side — the verdict, and "what the score is
 * measuring" with the score figure. Used identically on the Overview and the Production
 * Readiness page.
 */
.readiness-summary {
    /* Flexbox wrap rather than grid auto-fit: grid auto-fit will keep two columns and shrink
       both below their comfortable width whenever 2×min just barely fits, which is what let
       the two tiles stay cramped side by side on a 13–14" laptop. Flex wrap is decisive —
       each tile declares the width it needs, and the verdict tile drops to its own full-width
       line the instant both cannot get that width. Measured against the real content area
       (not the viewport), so it accounts for the sidebar. */
    display: flex;
    flex-wrap: wrap;
    gap: 16px;
    margin-bottom: 22px;
    align-items: stretch;
    /* Fill the full content width, edge to edge, matching the tile grid below. The score
       block and verdict banner share the row proportionally (see their flex weights). */
}
/* Each tile has a real min-width (so flex-wrap drops the verdict to its own line the moment
   both cannot fit without cramming) and proportional grow weights that restore the original
   ~1.15 : 1 split when they share a row. They grow to fill the row as the original grid did,
   keeping the score block the wider of the two. */
.readiness-summary-score {
    flex: 1.15 1 520px;
    min-width: 520px;
}
.readiness-summary-verdict {
    flex: 1 1 400px;
    min-width: 400px;
    margin-bottom: 0;
}
/* Overview: the verdict tile is omitted, so the score tile takes the whole row rather than
   sitting at ~60% with an empty gap beside it. */
.readiness-summary-score-only .readiness-summary-score {
    flex-basis: 100%;
}
.readiness-summary-score {
    display: grid;
    /* The score ring sizes to content; the measuring grid takes the rest. flex-wrap-like
       behaviour comes from the measure grid's own auto-fit below, so nothing overflows. */
    grid-template-columns: auto minmax(0, 1fr);
    gap: 18px;
    align-items: center;
    background: var(--bg);
    border: 1px solid var(--border);
    border-radius: var(--radius);
    padding: 16px 18px;
    box-shadow: var(--shadow-sm);
}
.rs-measure { min-width: 0; }
.rs-measure-title {
    font-size: 10.5px;
    font-weight: 700;
    letter-spacing: .06em;
    text-transform: uppercase;
    color: var(--text-faint);
    margin-bottom: 8px;
}
.rs-measure-grid {
    display: grid;
    /* Three rows, edge to edge: the column count is ceil(count / 3), supplied per-render as
       --rs-cols by the template, and every column is an equal 1fr so the chips fill the full
       width whatever the dimension count. Falls back to 4 columns if the variable is absent. */
    grid-template-columns: repeat(var(--rs-cols, 4), minmax(0, 1fr));
    gap: 4px 8px;
    width: 100%;
}
/* Step the column count down on narrower content so chips never squeeze below a readable
   width; this adds rows rather than shrinking cells. The full six-across layout holds on a
   normal desktop; below that it halves to three, then two, then one. The score tile is
   full-width on the Overview, so these track the real content area. */
@media (max-width: 1300px) {
    .rs-measure-grid { grid-template-columns: repeat(min(var(--rs-cols, 6), 3), minmax(0, 1fr)); }
}
@media (max-width: 720px) {
    .rs-measure-grid { grid-template-columns: repeat(2, minmax(0, 1fr)); }
}
@media (max-width: 460px) {
    .rs-measure-grid { grid-template-columns: 1fr; }
}
.rs-dim {
    display: grid;
    /* Label on the left, figure+bar on the right. The figure now carries "problems/total ·
       pct%", so it gets a touch more of the chip than the label; both tracks are zero-min so
       neither overruns. A long label truncates with an ellipsis and shows in full on hover. */
    grid-template-columns: minmax(0, 0.85fr) minmax(0, 1.15fr);
    align-items: center;
    gap: 8px;
    /* Resting state carries the accent tint by default — the chips read as a lit, cohesive
       strip rather than plain outlines that only light up on hover. Hover then adds a small
       lift and a stronger border for feedback, without a jarring colour change. */
    background: var(--accent-soft);
    border: 1px solid var(--accent-line, color-mix(in srgb, var(--accent) 35%, transparent));
    border-radius: 6px;
    box-shadow: var(--shadow-sm);
    /* Vertical padding kept tiny; horizontal padding is absorbed by the reduced grid gap
       above, so rows read as tappable chips without making the strip taller or wider. */
    padding: 2px 6px;
    cursor: pointer;
    font-family: inherit;
    text-align: left;
    transition: background 0.12s ease, border-color 0.12s ease,
                box-shadow 0.12s ease, transform 0.12s ease;
}
.rs-dim:hover,
.rs-dim:focus-visible {
    /* Sharp accent border on hover, matching the Overview tile cards: a full-accent edge
       plus a deeper shadow and a small lift, so the chip pops the same way the tiles do. */
    border-color: var(--accent);
    box-shadow: var(--shadow-md), 0 0 0 1px var(--accent);
    transform: translateY(-1px);
    outline: none;
}
/* Parameters with active Critical/High problems are what pull the score down, so give
   them a faint tinted background and a stronger name, drawing the eye to the cells worth
   acting on. Clean parameters stay quiet. */
.rs-dim-problem {
    background: color-mix(in srgb, var(--error) 8%, transparent);
    border-radius: 7px;
}
.rs-dim-problem .rs-dim-name { color: var(--text); font-weight: 600; }

.rs-dim-name {
    font-size: 11.5px;
    color: var(--text-dim);
    overflow: hidden;
    text-overflow: ellipsis;
    white-space: nowrap;
}
.rs-dim:hover .rs-dim-name { text-decoration: underline; text-underline-offset: 2px; }

/* The figure box: the coloured percentage bar is its background, the percentage text reads
   on top. Merging the two lets the label take the freed width. The track base tint sits
   under the fill so a low percentage still shows the box outline. */
.rs-dim-figure {
    position: relative;
    display: flex;
    align-items: center;
    justify-content: flex-end;
    height: 16px;
    padding: 0 5px;
    border-radius: 4px;
    overflow: hidden;
    white-space: nowrap;
    background: color-mix(in srgb, var(--text-faint) 18%, transparent);
}
.rs-dim-fill {
    position: absolute;
    left: 0;
    top: 0;
    bottom: 0;
    border-radius: 4px 0 0 4px;
    /* Softer than the old solid bar, so the percentage text stays legible on top. */
    opacity: 0.45;
}
.rs-dim-fill.bar-ok    { background: var(--ok); }
.rs-dim-fill.bar-warn  { background: var(--warn); }
.rs-dim-fill.bar-error { background: var(--error); }
/* Unassessed (N/A): no fill, a neutral flat box. */
.rs-dim-figure-na { background: color-mix(in srgb, var(--text-faint) 12%, transparent); }
.rs-dim-figure-text {
    position: relative;   /* above the fill */
    z-index: 1;
    font-size: 10px;
    font-weight: 650;
    color: var(--text);
    font-variant-numeric: tabular-nums;
    white-space: nowrap;
}
.rs-dim-figure-na .rs-dim-figure-text { color: var(--text-faint); }
.rs-dim:hover .rs-dim-name { color: var(--text); }
.rs-score-figure { align-self: center; }

/* ---- Readiness page: per-parameter "how the score is calculated" cards ----
   One card per parameter (the 18 that feed the score), three across, each showing its
   problem/total, a health bar, and the plain-English detail — so the page explains exactly
   why the score is what it is and which parameter to open to lift it. */
.section-intro {
    margin: 2px 0 12px;
    font-size: 12.5px;
    color: var(--text-faint);
    line-height: 1.5;
}
.rs-param-grid {
    display: grid;
    grid-template-columns: repeat(3, minmax(0, 1fr));
    gap: 10px;
    margin-bottom: 22px;
}
@media (max-width: 900px)  { .rs-param-grid { grid-template-columns: repeat(2, minmax(0, 1fr)); } }
@media (max-width: 560px)  { .rs-param-grid { grid-template-columns: 1fr; } }
.rs-param {
    display: block;
    width: 100%;
    text-align: left;
    font: inherit;
    color: inherit;
    cursor: pointer;
    background: var(--surface);
    border: 1px solid var(--border-strong);
    border-radius: var(--radius-md, 10px);
    padding: 12px 14px;
    box-shadow: 0 1px 2px rgba(0,0,0,0.14);
    transition: box-shadow 0.15s ease, transform 0.15s ease, border-color 0.15s ease;
}
.rs-param:hover {
    transform: translateY(-2px);
    box-shadow: 0 6px 16px rgba(0,0,0,0.18);
    border-color: var(--accent);
}
.rs-param:focus-visible { outline: 2px solid var(--accent); outline-offset: 2px; }
/* A problem parameter gets a red left edge; a clean one green; unassessed stays neutral. */
.rs-param-problem { border-left: 3px solid var(--error); }
.rs-param-clean   { border-left: 3px solid var(--ok); }
.rs-param-na      { border-left: 3px solid var(--border-strong); opacity: 0.75; }
.rs-param-head {
    display: flex;
    align-items: baseline;
    justify-content: space-between;
    gap: 8px;
    margin-bottom: 8px;
}
.rs-param-name { font-size: 13px; font-weight: 650; color: var(--text); }
.rs-param-figure {
    font-size: 15px;
    font-weight: 700;
    font-variant-numeric: tabular-nums;
    white-space: nowrap;
}
/* The figure is now the healthy count (good / total · pct%), tinted by health tier. */
.rs-param-figure.bar-ok      { color: var(--ok); }
.rs-param-figure.bar-warn    { color: var(--warn); }
.rs-param-figure.bar-error   { color: var(--error); }
.rs-param-figure.bar-neutral { color: var(--text-faint); }
.rs-param-figure.na          { color: var(--text-faint); font-weight: 600; }
.rs-param-bar {
    display: block;
    height: 6px;
    border-radius: 999px;
    background: color-mix(in srgb, var(--text-faint) 16%, transparent);
    overflow: hidden;
    margin-bottom: 9px;
}
.rs-param-fill { display: block; height: 100%; border-radius: 999px; }
.rs-param-fill.bar-ok      { background: var(--ok); }
.rs-param-fill.bar-warn    { background: var(--warn); }
.rs-param-fill.bar-error   { background: var(--error); }
.rs-param-fill.bar-neutral { background: var(--text-faint); }
.rs-param-detail {
    margin: 0 0 6px;
    font-size: 12px;
    color: var(--text-dim);
    line-height: 1.4;
}
.rs-param-note { margin: 0; font-size: 11px; color: var(--text-faint); }
.rs-param-health.health-ok    { color: var(--ok); }
.rs-param-health.health-warn  { color: var(--warn); }
.rs-param-health.health-error { color: var(--error); }

/* The readiness summary and its measure grid now reflow via auto-fit against their own
   container width (see .readiness-summary / .rs-measure-grid above), so they adapt to the
   real content area — including the space the sidebar takes — without viewport media
   queries. Only the very narrow case stacks the score ring above the dimensions. */
@media (max-width: 600px) {
    .readiness-summary-score { grid-template-columns: 1fr; }
}

/*
 * (Legacy) "Where the score is going" panel — kept for any view still referencing it.
 */
.verdict-areas {
    align-self: stretch;
    display: flex;
    flex-direction: column;
    gap: 8px;
    padding: 10px 16px;
    border-left: 1px solid var(--border);
    border-right: 1px solid var(--border);
}
.verdict-areas-title {
    font-size: 10.5px;
    font-weight: 700;
    letter-spacing: .06em;
    text-transform: uppercase;
    color: var(--text-faint);
}
.verdict-areas-grid {
    display: grid;
    grid-template-columns: repeat(3, minmax(140px, 1fr));
    gap: 6px 16px;
}
.verdict-area-row {
    display: grid;
    grid-template-columns: 84px 1fr 24px;
    align-items: center;
    gap: 8px;
    background: transparent;
    border: 0;
    padding: 2px 0;
    cursor: pointer;
    font-family: inherit;
    text-align: left;
}
.verdict-area-name {
    font-size: 11.5px;
    color: var(--text-dim);
    overflow: hidden;
    text-overflow: ellipsis;
    white-space: nowrap;
}
.verdict-area-track {
    height: 7px;
    border-radius: 4px;
    background: color-mix(in srgb, var(--text-faint) 22%, transparent);
    overflow: hidden;
}
.verdict-area-fill { display: block; height: 100%; border-radius: 4px; }
.verdict-area-fill.bar-ok    { background: var(--ok); }
.verdict-area-fill.bar-warn  { background: var(--warn); }
.verdict-area-fill.bar-error { background: var(--error); }
.verdict-area-figure {
    font-size: 11.5px;
    font-weight: 650;
    color: var(--text-dim);
    text-align: right;
    font-variant-numeric: tabular-nums;
}
.verdict-area-row:hover .verdict-area-name,
.verdict-area-row:hover .verdict-area-figure { color: var(--text); }

/* Below three columns' worth of width, drop to two then one, and unstack the verdict grid. */
@media (max-width: 1200px) {
    .verdict-areas-grid { grid-template-columns: repeat(2, minmax(140px, 1fr)); }
}
@media (max-width: 980px) {
    .verdict { grid-template-columns: auto 1fr auto; }
    .verdict-areas {
        grid-column: 1 / -1;
        border-left: 0; border-right: 0;
        border-top: 1px solid var(--border);
        padding: 12px 0 0;
    }
    .verdict-areas-grid { grid-template-columns: repeat(3, minmax(120px, 1fr)); }
}

/* Score is secondary to the verdict, so it is quieter and set to one side */
.verdict-score {
    display: flex;
    flex-direction: column;
    align-items: center;
    gap: 1px;
    background: var(--bg);
    border: 1px solid var(--border);
    border-radius: var(--radius);
    padding: 10px 18px;
    cursor: pointer;
    font-family: inherit;
    transition: border-color .12s;
}

.verdict-score:hover { border-color: var(--accent); }

/*
 * The readiness-summary score tile gets a soft teal wash from the app's accent, so the
 * headline figure reads as the hero of the summary rather than a plain white box. The tint
 * is gentle enough that the coloured figure (ok/warn/error) still stands out on top.
 */
.rs-score-figure {
    background: linear-gradient(150deg,
                var(--accent-soft) 0%,
                color-mix(in srgb, var(--accent-soft) 55%, var(--bg)) 60%,
                var(--bg) 100%);
    border-color: var(--accent-line);
    box-shadow: var(--shadow-sm);
    padding: 14px 20px;
}
.rs-score-figure .score-caption { color: var(--accent); }
.rs-score-figure:hover {
    border-color: var(--accent);
    box-shadow: var(--shadow-md, var(--shadow-sm));
}

.score-figure {
    font-size: 30px;
    font-weight: 300;
    line-height: 1.05;
    letter-spacing: -0.5px;
}

.score-figure.ok    { color: var(--ok); }
.score-figure.info  { color: var(--info); }
.score-figure.warn  { color: var(--warn); }
.score-figure.error { color: var(--error); }

.score-of    { font-size: 11px; color: var(--text-faint); }
.score-grade { font-size: 11.5px; color: var(--text-dim); font-weight: 600; }

/* ---- Section blocks ---- */

.home-block { margin-bottom: 26px; }
/* A section whose heading was removed for density; tighter top margin so it sits close
   to the block above without the heading's visual anchor. */
.home-block-tight { margin-bottom: 18px; }

.home-block-head {
    display: flex;
    align-items: baseline;
    justify-content: space-between;
    gap: 12px;
    margin-bottom: 12px;
}

.home-block-head h3 {
    margin: 0;
    font-size: 13px;
    font-weight: 650;
    text-transform: uppercase;
    letter-spacing: .05em;
    color: var(--text-dim);
}

.home-block-meta { font-size: 12.5px; color: var(--text-faint); }
.ok-text    { color: var(--ok);    font-weight: 650; }
.error-text { color: var(--error); font-weight: 650; }

.link-btn {
    background: none;
    border: none;
    padding: 0;
    font: inherit;
    font-size: 12.5px;
    color: var(--accent);
    cursor: pointer;
    white-space: nowrap;
}

.link-btn:hover { color: var(--accent-hover); text-decoration: underline; }

/* ---- Finding cards ---- */

.finding-cards {
    list-style: none;
    margin: 0;
    padding: 0;
    display: grid;
    grid-template-columns: repeat(auto-fill, minmax(390px, 1fr));
    gap: 12px;
}

.finding-card {
    background: var(--bg);
    border: 1px solid var(--border);
    border-radius: var(--radius);
    padding: 13px 15px;
    box-shadow: var(--shadow-sm);
    display: flex;
    flex-direction: column;
    gap: 7px;
}

.finding-card-top {
    display: flex;
    align-items: center;
    gap: 8px;
    flex-wrap: wrap;
}

.finding-subject {
    font-size: 12.5px;
    font-weight: 600;
    color: var(--text);
}

.finding-area {
    font-size: 11px;
    color: var(--text-faint);
    margin-left: auto;
    text-transform: uppercase;
    letter-spacing: .04em;
}

.finding-detail {
    margin: 0;
    font-size: 13px;
    color: var(--text);
    line-height: 1.45;
}

/* The consequence, set apart so it is not skimmed past as more description */
.finding-impact {
    margin: 0;
    font-size: 12.5px;
    color: var(--text-dim);
    line-height: 1.5;
    border-left: 2px solid var(--border-strong);
    padding-left: 10px;
}

.finding-card-foot {
    display: flex;
    align-items: flex-end;
    justify-content: space-between;
    gap: 12px;
    margin-top: auto;
    padding-top: 4px;
}

.finding-remedy {
    font-size: 12px;
    color: var(--accent);
    line-height: 1.45;
}

/* ---- Ring charts ---- */

.chart-row {
    display: grid;
    grid-template-columns: repeat(auto-fit, minmax(340px, 1fr));
    gap: 14px;
}

.chart-card {
    background: var(--bg);
    border: 1px solid var(--border);
    border-radius: var(--radius);
    padding: 15px 17px;
    box-shadow: var(--shadow-sm);
}

.chart-card-head h4 {
    margin: 0 0 2px;
    font-size: 13.5px;
    font-weight: 650;
}

.chart-card-head p {
    margin: 0 0 14px;
    font-size: 11.5px;
    color: var(--text-faint);
}

.chart-body {
    display: flex;
    align-items: center;
    gap: 20px;
    flex-wrap: wrap;
}

.donut-wrap {
    position: relative;
    width: 132px;
    height: 132px;
    flex: 0 0 auto;
}

.donut { width: 100%; height: 100%; }

.donut-track {
    fill: none;
    stroke: var(--bg-subtle);
    stroke-width: 13;
}

.donut-slice {
    fill: none;
    stroke-width: 13;
    stroke-linecap: butt;
    transition: stroke-width .12s;
}

.donut-slice:hover { stroke-width: 15.5; }

/* Centre figure is the number a reader takes away, so it sits inside the hole */
.donut-centre {
    position: absolute;
    inset: 0;
    display: flex;
    flex-direction: column;
    align-items: center;
    justify-content: center;
    pointer-events: none;
}

.donut-figure {
    font-size: 21px;
    font-weight: 300;
    letter-spacing: -0.4px;
    line-height: 1.1;
}

.donut-unit { font-size: 10.5px; color: var(--text-faint); }

.chart-legend {
    list-style: none;
    margin: 0;
    padding: 0;
    flex: 1 1 170px;
    min-width: 0;
    font-size: 12px;
}

.chart-legend li {
    display: grid;
    grid-template-columns: 9px 1fr auto auto;
    gap: 8px;
    align-items: center;
    padding: 2.5px 0;
}

.legend-swatch {
    width: 9px; height: 9px;
    border-radius: 2px;
    display: inline-block;
}

.legend-label {
    color: var(--text);
    overflow: hidden;
    text-overflow: ellipsis;
    white-space: nowrap;
}

.legend-value   { color: var(--text-dim); font-variant-numeric: tabular-nums; }
.legend-percent {
    color: var(--text-faint);
    font-variant-numeric: tabular-nums;
    min-width: 32px;
    text-align: right;
}

/* ---- Workload block grid ---- */

.grid-legend {
    display: flex;
    flex-wrap: wrap;
    gap: 14px;
    margin-bottom: 12px;
    font-size: 11.5px;
    color: var(--text-faint);
}

.grid-legend span { display: inline-flex; align-items: center; gap: 5px; }

.swatch {
    width: 10px; height: 10px;
    border-radius: 2px;
    display: inline-block;
    border: 1px solid transparent;
}

.swatch-ok      { background: var(--ok); }
.swatch-warn    { background: var(--warn); }
.swatch-error   { background: var(--error); }
.swatch-neutral { background: var(--border-strong); }
.swatch-restart {
    background: var(--ok);
    border-color: var(--warn);
    box-shadow: inset 0 0 0 1.5px var(--warn);
}

.ns-grid {
    display: grid;
    grid-template-columns: 190px 1fr;
    gap: 12px;
    align-items: start;
    padding: 7px 0;
    border-bottom: 1px solid var(--border);
}

.ns-grid:last-of-type { border-bottom: none; }

.ns-grid-label {
    font-size: 12.5px;
    color: var(--text);
    overflow: hidden;
    text-overflow: ellipsis;
    padding-top: 2px;
}

.ns-grid-label em {
    display: block;
    font-style: normal;
    font-size: 11px;
    color: var(--error);
    margin-top: 1px;
}

.ns-grid-blocks {
    display: flex;
    flex-wrap: wrap;
    gap: 4px;
}

.wl-block {
    width: 17px;
    height: 17px;
    border-radius: 3px;
    border: 1px solid transparent;
    padding: 0;
    cursor: pointer;
    transition: transform .1s, outline-color .1s;
    outline: 2px solid transparent;
    outline-offset: 1px;
}

.wl-block:hover {
    transform: scale(1.22);
    outline-color: var(--accent);
}

.wl-ok      { background: var(--ok); }
.wl-warn    { background: var(--warn); }
.wl-error   { background: var(--error); }
.wl-neutral { background: var(--border-strong); }

/*
 * Restarting is orthogonal to status: a workload can be fully ready and still be
 * crash-looping. Shown as an inset ring so it overlays any base colour.
 */
.wl-restart { box-shadow: inset 0 0 0 2px var(--warn); }

@media (max-width: 820px) {
    .verdict { grid-template-columns: auto 1fr; }
    .verdict-score { grid-column: 2; justify-self: start; }
    .ns-grid { grid-template-columns: 1fr; }
}

/* ================= Dependency graph (Blast Radius) ================= */

.graph-wrap {
    padding: 10px 14px 4px;
    display: flex;
    justify-content: center;
}

/* Fill the full page width so the oval is large and readable; the wide viewBox keeps a
   landscape aspect. No max-width cap — the diagram is meant to dominate this page. */
.dep-graph {
    width: 100%;
    height: auto;
}
/* The graph card spans the panel edge to edge rather than centring a narrow drawing. */
.graph-wrap { justify-content: stretch; padding: 8px 10px; }

.dep-edge {
    stroke: var(--text-faint);
    stroke-width: 2.4;
    opacity: 0.85;
}

/* Arrowhead: solid and in the text-dim ink so direction reads clearly against the wash. */
.dep-arrowhead {
    fill: var(--text-dim);
    opacity: 1;
}

/* Node fill is set inline per namespace (its own colour); health is shown by the ring. */
.dep-node {
    stroke: var(--bg);
    stroke-width: 3;
}
/* Health ring: a coloured halo around the namespace-coloured dot. */
.dep-node.ring-ok      { stroke: var(--ok);    stroke-width: 3; }
.dep-node.ring-warn    { stroke: var(--warn);  stroke-width: 4; }
.dep-node.ring-error   { stroke: var(--error); stroke-width: 5; }
.dep-node.ring-neutral { stroke: var(--bg);    stroke-width: 3; }

.dep-label {
    font-size: 17px;
    fill: var(--text-dim);
    font-family: var(--mono, monospace);
    paint-order: stroke;
    stroke: var(--bg);
    stroke-width: 3.5px;
    stroke-linejoin: round;
}

/* ================= Page help panel (modal) ================= */

.help-lead {
    font-size: 14.5px;
    font-weight: 600;
    line-height: 1.5;
    color: var(--text);
    margin: 0 0 12px;
}

.help-text {
    font-size: 13.5px;
    line-height: 1.6;
    color: var(--text-dim);
    margin: 0 0 12px;
}
.help-text:last-child { margin-bottom: 0; }

/* Inline risk badge next to an image tag (e.g. :latest). Compact so it sits on the row. */
.tag-risk {
    margin-left: 6px;
    font-size: 10px;
    vertical-align: middle;
}

/* Extra separation before the probe-risk table so it doesn't crowd the note above it. */
.probe-risk-table { margin-top: 26px; }

/* Sub-section heading within a merged page (e.g. Right-sizing inside Waste Sweep). */
.section-heading {
    margin: 30px 0 12px;
    font-size: 15px;
    font-weight: 650;
    color: var(--text);
    padding-bottom: 6px;
    border-bottom: 1px solid var(--border);
}

/* ===== On Fire Now (incident triage) ===================================== */

/* Situation card: the severity donut + legend on the left, scope verdict on the right.
   Reuses .chart-card so it matches the Overview's resource-distribution panels exactly. */
.fire-situation {
    display: grid;
    grid-template-columns: minmax(260px, 340px) 1fr;
    gap: 24px;
    align-items: center;
}
.fire-situation-chart {
    display: flex;
    align-items: center;
    gap: 22px;
}
.fire-legend { align-self: center; }
.fire-legend li { grid-template-columns: 9px 1fr auto; }

/* Scope verdict: a tinted panel that reads as the headline takeaway. */
.fire-situation-scope {
    border-left: 3px solid var(--border-strong);
    padding: 4px 0 4px 16px;
}
.fire-situation-scope.scope-error { border-left-color: var(--error); }
.fire-situation-scope.scope-warn  { border-left-color: var(--warn); }
.fire-situation-scope.scope-ok    { border-left-color: var(--ok); }
.fire-scope-text {
    margin: 2px 0 0;
    font-size: 16px;
    font-weight: 600;
    color: var(--text);
    line-height: 1.45;
}

@media (max-width: 760px) {
    .fire-situation { grid-template-columns: 1fr; gap: 16px; }
    .fire-situation-scope { border-left: none; border-top: 3px solid var(--border-strong); padding: 12px 0 0; }
}

/* Incident cards, stacked worst-first — matched to the Overview finding cards. */
.incident-list {
    display: flex;
    flex-direction: column;
    gap: 12px;
    margin-bottom: 18px;
}
.incident {
    position: relative;
    border: 1px solid var(--border);
    border-radius: var(--radius);
    box-shadow: var(--shadow-sm);
    padding: 14px 16px 14px 18px;
    background: var(--bg);
    display: flex;
    flex-direction: column;
    gap: 9px;
    transition: box-shadow 0.15s ease, border-color 0.15s ease;
}
/* A slim severity rail on the left edge, drawn with a border so it hugs the radius. */
.incident-error { border-left: 3px solid var(--error); }
.incident-warn  { border-left: 3px solid var(--warn); }
.incident:hover { box-shadow: var(--shadow); border-color: var(--border-strong); }

.incident-head {
    display: flex;
    align-items: center;
    gap: 9px;
}
.incident-dot {
    width: 8px;
    height: 8px;
    border-radius: 50%;
    flex: 0 0 auto;
}
.incident-dot.dot-error { background: var(--error); }
.incident-dot.dot-warn  { background: var(--warn); }
.incident-title {
    font-weight: 650;
    font-size: 14px;
    margin: 0;
    flex: 1 1 auto;
    color: var(--text);
}
/* A quiet, tinted badge rather than a loud pill. */
.incident-badge {
    font-size: 10.5px;
    font-weight: 650;
    text-transform: uppercase;
    letter-spacing: 0.04em;
    padding: 2px 8px;
    border-radius: 999px;
}
.incident-badge.badge-error { color: var(--error); background: var(--error-soft); }
.incident-badge.badge-warn  { color: var(--warn);  background: var(--warn-soft); }

.incident-cause {
    margin: 0;
    font-size: 13px;
    line-height: 1.5;
    color: var(--text-dim);
}

/* Small uppercase eyebrow label above the evidence and action blocks. */
.incident-eyebrow {
    display: block;
    font-size: 10px;
    text-transform: uppercase;
    letter-spacing: 0.06em;
    font-weight: 650;
    color: var(--text-faint);
    margin-bottom: 5px;
}

/* Evidence: a light left-rule, not a heavy filled slab. */
.incident-evidence {
    border-left: 2px solid var(--border-strong);
    padding-left: 11px;
}
.incident-evidence ul {
    margin: 0;
    padding-left: 15px;
}
.incident-evidence li {
    font-family: var(--mono, monospace);
    font-size: 12px;
    color: var(--text-dim);
    line-height: 1.65;
}
.incident-foot {
    display: flex;
    align-items: flex-end;
    justify-content: space-between;
    gap: 16px;
    margin-top: 1px;
    padding-top: 11px;
    border-top: 1px solid var(--border);
}
.incident-action {
    flex: 1 1 auto;
    margin: 0;
    font-size: 12.5px;
    line-height: 1.5;
}
.incident-action > span:last-child { color: var(--text); }

/* The call-to-action: subtle outline button, filling with accent on hover. */
.incident-cta {
    flex: 0 0 auto;
    background: var(--accent-soft);
    color: var(--accent);
    border: 1px solid transparent;
    border-radius: var(--radius-sm);
    padding: 7px 13px;
    font-size: 12px;
    font-weight: 600;
    font-family: inherit;
    cursor: pointer;
    white-space: nowrap;
    transition: background 0.14s ease, color 0.14s ease;
}
.incident-cta:hover { background: var(--accent); color: #fff; }

@media (max-width: 720px) {
    .incident-foot { flex-direction: column; align-items: stretch; }
}

/* ===== Insights at-a-glance strip (Overview) ============================= */

.insight-strip {
    display: grid;
    /* Exactly 10 chips per row, matching the pre-workload-tile layout. A fixed 10-column
       grid is used instead of auto-fill/minmax because auto-fill picks the column count from
       the min width and would fit an 11th narrow chip on a wide panel; pinning to 10 keeps
       the row count stable and the tiles at their original width. Narrows to fewer columns
       on smaller screens via the media queries below. */
    grid-template-columns: repeat(10, 1fr);
    gap: 12px;
}

/* Both the top ring-tile row and the chip strip step down together on smaller screens, so a
   fixed 10-wide grid never squeezes tiles unreadably narrow on a laptop or tablet. */
@media (max-width: 1500px) {
    .tile-grid, .insight-strip { grid-template-columns: repeat(8, 1fr); }
}
@media (max-width: 1200px) {
    .tile-grid, .insight-strip { grid-template-columns: repeat(6, 1fr); }
}
@media (max-width: 900px) {
    .tile-grid, .insight-strip { grid-template-columns: repeat(4, 1fr); }
}
@media (max-width: 600px) {
    .tile-grid, .insight-strip { grid-template-columns: repeat(2, 1fr); }
}

/* Each chip: a big count, a label, and a short note; a coloured top rule encodes
   severity so a healthy cluster reads as a calm row and problems light up. */
.insight-chip {
    text-align: left;
    background: var(--surface);
    border: 1px solid var(--border);
    border-top: 3px solid var(--border-strong);
    border-radius: var(--radius);
    box-shadow: var(--shadow-sm);
    padding: 12px 14px;
    font: inherit;
    color: inherit;
    cursor: pointer;
    display: flex;
    flex-direction: column;
    gap: 2px;
    transition: box-shadow 0.15s ease, border-color 0.15s ease, transform 0.15s ease;
}
.insight-chip:hover {
    box-shadow: var(--shadow);
    transform: translateY(-2px);
}
.insight-chip:focus-visible {
    outline: 2px solid var(--accent);
    outline-offset: 2px;
}

.insight-count {
    font-size: 26px;
    font-weight: 700;
    line-height: 1;
    letter-spacing: -0.02em;
}
.insight-label {
    font-size: 12px;
    font-weight: 650;
    color: var(--text);
    margin-top: 5px;
}
.insight-note { font-size: 11px; color: var(--text-faint); }

/* Severity: the top rule and the count colour. ok stays neutral-green and quiet. */
.insight-chip.chip-ok    { border-top-color: color-mix(in srgb, var(--ok) 55%, var(--border)); }
.insight-chip.chip-ok    .insight-count { color: var(--ok); }
.insight-chip.chip-warn  { border-top-color: var(--warn); }
.insight-chip.chip-warn  .insight-count { color: var(--warn); }
.insight-chip.chip-error { border-top-color: var(--error); }
.insight-chip.chip-error .insight-count { color: var(--error); }
.insight-chip.chip-na    .insight-count { color: var(--text-faint); }


/* ============================================================================
 * 3D depth
 *
 * A raised, tactile look for the primary surfaces — nav items, Overview tiles and
 * chips, and the readiness summary. Depth comes from each theme's own --lift-*
 * tokens (a coloured top highlight and a tinted surface gradient) plus the layered
 * shadow tokens, so the effect is always in the theme's colour family and never a
 * flat grey. Hover lifts; active presses in.
 * ==========================================================================*/

/* ---- Left navigation items ---- */
.nav-item {
    border: 1px solid transparent;
    transition: background .12s, color .12s, box-shadow .12s, transform .08s, border-color .12s;
}
.nav-item:hover {
    background: linear-gradient(180deg, var(--lift-hover-a) 0%, var(--lift-hover-b) 100%);
    border-color: var(--accent-line);
    box-shadow: var(--shadow-sm), inset 0 1px 0 var(--lift-top);
    transform: translateY(-1px);
}
.nav-item.selected {
    background: linear-gradient(180deg, var(--lift-surface-a) 0%, var(--lift-surface-b) 100%);
    border-color: var(--accent-line);
    box-shadow: var(--shadow-sm), inset 0 1px 0 var(--lift-top);
    color: var(--text);
    font-weight: 600;
}
.nav-item:active { transform: translateY(0); }

.item-icon { box-shadow: var(--shadow-sm), inset 0 1px 0 var(--lift-top); }
.nav-item.selected .item-icon { box-shadow: var(--shadow), inset 0 1px 0 var(--lift-top); }

/* ---- Overview tiles ---- */
.tile-card,
.count-card {
    background: linear-gradient(180deg, var(--lift-surface-a) 0%, var(--lift-surface-b) 100%);
    border-color: var(--border);
    box-shadow: var(--shadow), inset 0 1px 0 var(--lift-top);
}
.tile-card:hover,
.count-card:hover {
    background: linear-gradient(180deg, var(--lift-hover-a) 0%, var(--lift-hover-b) 100%);
    border-color: var(--accent);
    box-shadow: var(--shadow-md), inset 0 1px 0 var(--lift-top);
    transform: translateY(-3px);
}
.tile-card:active,
.count-card:active { transform: translateY(-1px); box-shadow: var(--shadow-sm); }

/* ---- Overview insight chips ---- */
.insight-chip {
    background: linear-gradient(180deg, var(--lift-surface-a) 0%, var(--lift-surface-b) 100%);
    box-shadow: var(--shadow-sm), inset 0 1px 0 var(--lift-top);
    transition: background .12s, border-color .12s, box-shadow .12s, transform .08s;
}
.insight-chip:hover {
    background: linear-gradient(180deg, var(--lift-hover-a) 0%, var(--lift-hover-b) 100%);
    border-color: var(--accent);
    box-shadow: var(--shadow), inset 0 1px 0 var(--lift-top);
    transform: translateY(-2px);
}
.insight-chip:active { transform: translateY(0); box-shadow: var(--shadow-sm); }

/* ---- Readiness summary tiles ---- */
.readiness-summary-score {
    background: linear-gradient(180deg, var(--lift-surface-a) 0%, var(--lift-surface-b) 100%);
    box-shadow: var(--shadow-md), inset 0 1px 0 var(--lift-top);
}
/* The verdict tile keeps its status tint (error/warn/ok soft), so only lift its
   shadow and border, not its background. */
.readiness-summary-verdict {
    box-shadow: var(--shadow-md), inset 0 1px 0 var(--lift-top);
}

/* ===== Latency page ===================================================== */

/* The p99 figure, tinted by how slow it is, so the eye lands on the tail. */
.lat-p99 {
    font-variant-numeric: tabular-nums;
    font-weight: 650;
    padding: 1px 6px;
    border-radius: 4px;
}
.lat-p99.lat-ok    { color: var(--ok); }
.lat-p99.lat-warn  { color: var(--warn); background: color-mix(in srgb, var(--warn) 12%, transparent); }
.lat-p99.lat-error { color: var(--error); background: color-mix(in srgb, var(--error) 14%, transparent); }

/* The inline diagnosis under a slow service: root cause, evidence and specific fixes. */
.lat-diag-row td { background: color-mix(in srgb, var(--text-faint) 5%, transparent); }
.lat-diag { padding: 4px 2px 8px; }
.lat-diag-head { display: flex; align-items: center; gap: 10px; margin-bottom: 4px; }
.lat-conf { font-size: 11px; color: var(--text-faint); }
.lat-diag-summary { margin: 2px 0 8px; color: var(--text-dim); font-size: 12.5px; }
.lat-diag-cols {
    display: grid;
    grid-template-columns: minmax(220px, 1fr) minmax(260px, 1.4fr);
    gap: 18px;
}
.lat-diag-label {
    font-size: 10.5px;
    text-transform: uppercase;
    letter-spacing: 0.04em;
    color: var(--text-faint);
    margin-bottom: 4px;
}
.lat-diag-evidence ul,
.lat-diag-fixes ul { margin: 0; padding: 0; list-style: none; }
.lat-diag-evidence li {
    font-size: 12px;
    color: var(--text-dim);
    padding: 2px 0 2px 14px;
    position: relative;
}
.lat-diag-evidence li::before {
    content: '·';
    position: absolute;
    left: 4px;
    color: var(--text-faint);
}
.lat-diag-fixes li {
    font-size: 12px;
    padding: 4px 0;
    border-top: 1px solid var(--border);
}
.lat-diag-fixes li:first-child { border-top: 0; }
.fix-kind {
    display: inline-block;
    font-size: 10px;
    text-transform: uppercase;
    letter-spacing: 0.03em;
    color: var(--accent);
    background: var(--accent-soft);
    border-radius: 3px;
    padding: 1px 5px;
    margin-right: 6px;
    vertical-align: middle;
}
.fix-action { font-weight: 600; color: var(--text); }
.fix-detail { display: block; color: var(--text-dim); font-size: 11.5px; margin: 2px 0 0 0; }

/* ===== Shared diagnosis card (Workload Doctor, On Fire, Latency) ========= */

.diag-card {
    border: 1px solid var(--border);
    border-left: 3px solid var(--border-strong);
    border-radius: 8px;
    background: var(--surface);
    box-shadow: var(--shadow-sm);
    padding: 12px 14px;
    margin-bottom: 10px;
}
.diag-card.diag-error   { border-left-color: var(--error); }
.diag-card.diag-warn    { border-left-color: var(--warn); }
.diag-card.diag-info    { border-left-color: var(--info, var(--accent)); }
.diag-card.diag-neutral { border-left-color: var(--border-strong); }

.diag-card-top {
    display: flex;
    align-items: center;
    justify-content: space-between;
    gap: 10px;
    margin-bottom: 8px;
}
.diag-subject { display: flex; align-items: baseline; gap: 8px; min-width: 0; }
.diag-subject-kind {
    font-size: 10.5px;
    text-transform: uppercase;
    letter-spacing: 0.04em;
    color: var(--text-faint);
}
.diag-subject-name {
    font-weight: 650;
    color: var(--text);
    overflow: hidden;
    text-overflow: ellipsis;
    white-space: nowrap;
}

.diag-head { display: flex; align-items: center; gap: 10px; margin-bottom: 3px; }
.diag-title { font-weight: 650; color: var(--text); font-size: 13px; }
.diag-conf { font-size: 11px; color: var(--text-faint); }
.diag-summary { margin: 2px 0 8px; color: var(--text-dim); font-size: 12.5px; }

.diag-cols {
    display: grid;
    grid-template-columns: minmax(200px, 1fr) minmax(260px, 1.4fr);
    gap: 18px;
}
@media (max-width: 720px) {
    .diag-cols { grid-template-columns: 1fr; gap: 10px; }
}
.diag-label {
    font-size: 10.5px;
    text-transform: uppercase;
    letter-spacing: 0.04em;
    color: var(--text-faint);
    margin-bottom: 4px;
}
.diag-evidence ul, .diag-fixes ul { margin: 0; padding: 0; list-style: none; }
.diag-evidence li {
    font-size: 12px;
    color: var(--text-dim);
    padding: 2px 0 2px 14px;
    position: relative;
}
.diag-evidence li::before {
    content: '·';
    position: absolute;
    left: 4px;
    color: var(--text-faint);
}
.diag-fixes li {
    font-size: 12px;
    padding: 4px 0;
    border-top: 1px solid var(--border);
}
.diag-fixes li:first-child { border-top: 0; }
.diag-inspect { margin-top: 8px; }

/* ===== On Fire "Situations by cause" multi-column grid ================== */

.situation-grid {
    display: grid;
    grid-template-columns: repeat(auto-fill, minmax(220px, 1fr));
    gap: 6px 10px;
    padding: 4px 2px;
}
.situation-cell {
    display: flex;
    align-items: center;
    gap: 8px;
    padding: 6px 10px;
    border: 1px solid var(--border);
    border-radius: 6px;
    background: var(--surface);
    cursor: pointer;
    font: inherit;
    text-align: left;
    transition: border-color 0.12s ease, background 0.12s ease;
}
.situation-cell:hover { border-color: var(--accent); background: var(--accent-soft); }
.situation-dot {
    width: 8px; height: 8px; border-radius: 50%;
    flex: 0 0 auto;
    background: var(--border-strong);
}
.situation-dot.dot-error { background: var(--error); }
.situation-dot.dot-warn  { background: var(--warn); }
.situation-dot.dot-info  { background: var(--info, var(--accent)); }
.situation-dot.dot-neutral { background: var(--text-faint); }
.situation-name {
    flex: 1 1 auto;
    font-size: 12px;
    color: var(--text);
    overflow: hidden;
    text-overflow: ellipsis;
    white-space: nowrap;
}
.situation-count {
    flex: 0 0 auto;
    font-size: 12px;
    font-weight: 700;
    font-variant-numeric: tabular-nums;
    color: var(--text-dim);
    min-width: 18px;
    text-align: right;
}

/* ===== On Fire header band (donut · 2×2 counts · scope) ================= */

/* The consolidated header tile: scope strip on top, then the donut + 2×2 + situations row. */
.fire-header {
    margin-bottom: 16px;
    padding: 14px 16px;
    background: var(--surface);
    border: 1px solid var(--border);
    border-radius: var(--radius);
    box-shadow: var(--shadow-sm);
}

/* Scope: a thin, tinted one-line strip across the top of the tile. */
.fire-scope-strip {
    display: flex;
    align-items: baseline;
    gap: 10px;
    padding: 6px 10px;
    margin-bottom: 14px;
    border: 1px solid var(--border);
    border-left: 3px solid var(--border-strong);
    border-radius: var(--radius-sm);
    background: color-mix(in srgb, var(--text-faint) 5%, transparent);
}
.fire-scope-strip.scope-error { border-left-color: var(--error); }
.fire-scope-strip.scope-warn  { border-left-color: var(--warn); }
.fire-scope-strip.scope-ok    { border-left-color: var(--ok); }
.fire-scope-strip .fire-scope-text {
    font-size: 13px;
    color: var(--text-dim);
    line-height: 1.4;
}

/* The header row: donut on the left, then the counts+scope group filling the rest. Centered
   vertically so the group sits mid-height in the tile; the scope still matches the count-tile
   height because .fire-header-main stretches its own children (not the donut). */
.fire-header-row {
    display: flex;
    align-items: center;
    gap: 18px;
}
/* Counts + scope, side by side, stretched to each other's height. */
.fire-header-main {
    flex: 1 1 auto;
    min-width: 0;
    display: flex;
    align-items: stretch;
    gap: 8px;
}
/* Scope takes the remaining width beside the counts, and stretches to the counts' height. */
.fire-scope-inline {
    flex: 1 1 auto;
    min-width: 0;
    display: flex;
    align-items: baseline;
    gap: 10px;
    padding: 8px 12px;
    border: 1px solid var(--border);
    border-left: 3px solid var(--border-strong);
    border-radius: var(--radius-sm);
    background: color-mix(in srgb, var(--text-faint) 5%, transparent);
}
.fire-scope-inline.scope-error { border-left-color: var(--error); }
.fire-scope-inline.scope-warn  { border-left-color: var(--warn); }
.fire-scope-inline.scope-ok    { border-left-color: var(--ok); }
.fire-scope-inline .fire-scope-text {
    font-size: 13px;
    color: var(--text-dim);
    line-height: 1.4;
}

/* Compact donut for the header. */
.donut-wrap.donut-sm { width: 84px; height: 84px; }
.donut-wrap.donut-sm .donut-figure { font-size: 20px; }
.donut-wrap.donut-sm .donut-unit { font-size: 9.5px; }

/* The four counts in a single 1×4 row, each a bordered tile. */
.fire-header-stats {
    display: grid;
    grid-template-columns: repeat(4, minmax(84px, auto));
    gap: 8px;
}
.fire-stat {
    display: flex;
    flex-direction: column;
    line-height: 1.15;
    padding: 8px 10px;
    border: 1px solid var(--border);
    border-radius: var(--radius-sm);
    background: var(--bg);
}
.fire-stat-value {
    font-size: 20px;
    font-weight: 700;
    letter-spacing: -0.02em;
}
.fire-stat-value.ok    { color: var(--ok); }
.fire-stat-value.error { color: var(--error); }
.fire-stat-value.warn  { color: var(--warn); }
.fire-stat-label {
    font-size: 10.5px;
    color: var(--text-faint);
    text-transform: uppercase;
    letter-spacing: 0.04em;
}

/* Situations by cause: a separate full-width tile below the header band. An even grid that
   fills the width — ~6 across on a wide screen (12 causes → 2 rows), fewer as it narrows,
   wrapping cleanly. Cells share the row evenly (1fr) so they align, and the label ellipsises
   rather than pushing the count off. */
.fire-situations-tile {
    margin-bottom: 16px;
    padding: 10px 12px;
    background: var(--surface);
    border: 1px solid var(--border);
    border-radius: var(--radius);
    box-shadow: var(--shadow-sm);
}
.fire-situations-tile .situation-grid {
    grid-template-columns: repeat(auto-fill, minmax(160px, 1fr));
    padding: 0;
}

/* Let the one-line header wrap gracefully when the content area is too narrow for donut +
   1×4 + scope: the scope drops below, then the counts wrap to 2×2 on very narrow widths. */
@media (max-width: 900px) {
    .fire-header-row { flex-wrap: wrap; }
    .fire-scope-inline { flex-basis: 100%; }
}
@media (max-width: 560px) {
    .fire-header-stats { grid-template-columns: repeat(2, minmax(84px, 1fr)); }
}

/* ================= Drain Simulation ================= */
/*
  Each node is its own card so the per-node assessments read as separate blocks rather
  than one undifferentiated list. A severity-tinted left edge lets the eye find the risky
  nodes at a glance, and the at-risk workloads sit in a responsive grid so they use the
  full width instead of stacking in a narrow column with dead space to the right.
*/
.drain-nodes {
    display: flex;
    flex-direction: column;
    gap: 12px;
    margin-bottom: 18px;
}

.drain-card {
    background: var(--surface);
    border: 1px solid var(--border);
    border-left: 3px solid var(--border-strong);
    border-radius: var(--radius);
    box-shadow: var(--shadow-sm);
    padding: 12px 14px 14px;
}

/* Severity edge: risky nodes stand out, survivable ones stay quiet. */
.drain-card.drain-error { border-left-color: var(--error); }
.drain-card.drain-warn  { border-left-color: var(--warn); }
.drain-card.drain-ok    { border-left-color: var(--ok, var(--accent)); }

.drain-card-head {
    display: flex;
    flex-wrap: wrap;
    align-items: baseline;
    justify-content: space-between;
    gap: 6px 12px;
}

.drain-card-title {
    display: flex;
    align-items: baseline;
    gap: 8px;
    min-width: 0;
}

.drain-node {
    font-size: 13px;
    font-weight: 600;
    color: var(--text);
}

.drain-card-meta {
    display: flex;
    align-items: baseline;
    gap: 7px;
    font-size: 12px;
    color: var(--text-dim);
    white-space: nowrap;
}

.drain-dot { color: var(--text-faint); }

.drain-fit.ok  { color: var(--text-dim); }
.drain-fit.bad { color: var(--error); font-weight: 600; }

.drain-impact {
    margin: 8px 0 0;
    font-size: 12.5px;
    color: var(--text-dim);
    line-height: 1.5;
}

/* The at-risk workloads: a grid of small cells rather than a long vertical list. Each
   cell holds the workload pill and the one-line reason, so many risks fit in the width. */
.drain-risks {
    margin-top: 10px;
    display: grid;
    grid-template-columns: repeat(auto-fill, minmax(300px, 1fr));
    gap: 6px;
}

/* Stack the workload pill above its reason, so the reason text uses the full width of the
   cell (edge to edge) instead of wrapping in the narrow column beside the pill. */
.drain-risk {
    display: flex;
    flex-direction: column;
    align-items: flex-start;
    gap: 5px;
    padding: 8px 10px;
    background: var(--bg);
    border: 1px solid var(--border);
    border-radius: var(--radius-sm);
    min-width: 0;
}

/* A hairline severity tint on each cell, kept subtle so the grid stays calm. */
.drain-risk.drain-risk-error { border-color: color-mix(in srgb, var(--error) 32%, var(--border)); }
.drain-risk.drain-risk-warn  { border-color: color-mix(in srgb, var(--warn) 32%, var(--border)); }

.drain-risk .pill { flex-shrink: 0; align-self: flex-start; }

.drain-risk-detail {
    font-size: 12px;
    color: var(--text-dim);
    line-height: 1.45;
    width: 100%;
    min-width: 0;
    overflow-wrap: anywhere;
}

.drain-clear {
    margin: 10px 0 0;
    display: flex;
    align-items: baseline;
    gap: 8px;
    font-size: 12.5px;
    color: var(--text-dim);
    line-height: 1.5;
}
.drain-clear .pill { flex-shrink: 0; }

/* On narrow content areas collapse the risk grid to one column so cells never squeeze. */
@media (max-width: 720px) {
    .drain-risks { grid-template-columns: 1fr; }
    .drain-card-head { flex-direction: column; }
}

/* ============================================================================
 * 3D depth — extended to every button and tile
 *
 * Continues the raised, tactile treatment from the section above onto the rest of
 * the interactive surfaces: general buttons, the stat tiles used across module pages,
 * the On Fire severity chips, and the Drain Simulation cards. Everything consumes the
 * same theme-aware --lift-* tokens (lit top edge + tinted surface gradient) and the
 * layered shadow tokens, so the depth is always in the theme's colour family, never a
 * flat grey. Hover lifts; active presses in. Solid/gradient-filled buttons (primary,
 * gradient) keep their fill and gain depth from shadow + a lit inset edge only.
 * ==========================================================================*/

/* ---- Icon buttons (topbar: refresh, copy, export, theme) ---- */
.btn-icon {
    box-shadow: var(--shadow-sm), inset 0 1px 0 var(--lift-top);
    transition: color .12s, background .12s, border-color .12s, box-shadow .12s, transform .08s;
}
.btn-icon:hover {
    box-shadow: var(--shadow), inset 0 1px 0 var(--lift-top);
    transform: translateY(-1px);
}
.btn-icon:active { transform: translateY(0); box-shadow: var(--shadow-sm); }

/* ---- Small utility buttons ---- */
.btn-sm {
    box-shadow: var(--shadow-sm), inset 0 1px 0 var(--lift-top);
    transition: color .12s, background .12s, border-color .12s, box-shadow .12s, transform .08s;
}
.btn-sm:hover  { box-shadow: var(--shadow), inset 0 1px 0 var(--lift-top); transform: translateY(-1px); }
.btn-sm:active { transform: translateY(0); box-shadow: var(--shadow-sm); }

/* ---- Filled action buttons: depth from shadow + a lit top edge, fill unchanged ---- */
.btn-primary,
.btn-gradient {
    box-shadow: var(--shadow), inset 0 1px 0 color-mix(in srgb, #ffffff 30%, transparent);
}
.btn-primary:hover,
.btn-gradient:hover {
    box-shadow: var(--shadow-md), inset 0 1px 0 color-mix(in srgb, #ffffff 34%, transparent);
    transform: translateY(-1px);
}
.btn-primary:active,
.btn-gradient:active { transform: translateY(1px); box-shadow: var(--shadow-sm); }

/* ---- Outline button (SSO) ---- */
.btn-outline {
    box-shadow: var(--shadow-sm), inset 0 1px 0 var(--lift-top);
    transition: color .12s, background .12s, border-color .12s, box-shadow .12s, transform .08s;
}
.btn-outline:hover:not(:disabled) { box-shadow: var(--shadow), inset 0 1px 0 var(--lift-top); transform: translateY(-1px); }
.btn-outline:active:not(:disabled) { transform: translateY(0); box-shadow: var(--shadow-sm); }

/* ---- Stat tiles across every module page (rendered as <a>/<button>) ---- */
/* These already carry a resting shadow; give them the full lift gradient + lit edge so
   they match the Overview count cards everywhere, not just on the landing page. */
a.stat, button.stat {
    background: linear-gradient(180deg, var(--lift-surface-a) 0%, var(--lift-surface-b) 100%);
    box-shadow: var(--shadow), inset 0 1px 0 var(--lift-top);
}
a.stat:hover, button.stat:hover,
a.stat:focus-visible, button.stat:focus-visible {
    background: linear-gradient(180deg, var(--lift-hover-a) 0%, var(--lift-hover-b) 100%);
    box-shadow: var(--shadow-md), inset 0 1px 0 var(--lift-top);
    transform: translateY(-3px);
}
a.stat:active, button.stat:active { transform: translateY(-1px); box-shadow: var(--shadow-sm); }

/* ---- Drillable table cells ---- */
.cell-btn:hover { box-shadow: inset 0 1px 0 var(--lift-top); }

/* ---- On Fire: situation chips and count tiles ---- */
.situation-cell {
    background: linear-gradient(180deg, var(--lift-surface-a) 0%, var(--lift-surface-b) 100%);
    box-shadow: var(--shadow-sm), inset 0 1px 0 var(--lift-top);
    transition: border-color .12s ease, background .12s ease, box-shadow .12s ease, transform .08s ease;
}
.situation-cell:hover {
    background: linear-gradient(180deg, var(--lift-hover-a) 0%, var(--lift-hover-b) 100%);
    box-shadow: var(--shadow), inset 0 1px 0 var(--lift-top);
    transform: translateY(-2px);
}
.situation-cell:active { transform: translateY(0); box-shadow: var(--shadow-sm); }
/* Selected cause: the chip stays lit while its cards are the only ones shown, so it is
   obvious which filter is active and how to clear it (click it again). */
.situation-cell.is-active {
    border-color: var(--accent);
    background: var(--accent-soft);
    box-shadow: var(--shadow), inset 0 1px 0 var(--lift-top);
}
.situation-cell.is-active .situation-name { color: var(--text); font-weight: 600; }

.fire-stat {
    box-shadow: var(--shadow-sm), inset 0 1px 0 var(--lift-top);
}

/* ---- Drain Simulation cards ---- */
.drain-card {
    box-shadow: var(--shadow), inset 0 1px 0 var(--lift-top);
}

/* ===== PDB status tiles (clickable quick filters) ===================== */
/* The tiles reuse .stat for the card look; these make them read as buttons and mark the
   active filter. Clicking a tile sets the table's status filter (see wirePdbTiles). */
.pdb-tile {
    cursor: pointer;
    font-family: inherit;
    text-align: left;
    border: 1px solid var(--border);
}
.pdb-tile:hover {
    border-color: var(--accent);
    box-shadow: var(--shadow-md), 0 0 0 1px var(--accent);
    transform: translateY(-2px);
}
.pdb-tile.is-active {
    border-color: var(--accent);
    background: var(--accent-soft);
    box-shadow: var(--shadow), 0 0 0 1px var(--accent);
}

/*
  Compact status-filter tiles. These sit above every table and were consuming a full card's
  worth of height; halved here so they read as a slim filter strip, not a KPI band. The tile
  drops to a single dense line: label + big value inline, with the descriptive sub-line
  hidden (its text lives in the tooltip/context). Scoped to .pdb-tiles so the Overview
  landing KPI cards are untouched.
*/
.stat-grid.pdb-tiles { gap: 10px; margin-bottom: 16px; }
/* Page tiles: a compact, modern KPI card with a leading icon chip.
   Layout is a horizontal row — a soft-tinted rounded icon square on the left (injected via
   ::before so no per-page markup is needed), and a tight text block on the right whose
   value and sub-line share ONE line. This trims roughly 30% off the stacked version's
   height while restoring the icon that made the old tiles read as polished KPI cards. */
.pdb-tiles .stat {
    display: flex;
    align-items: center;
    gap: 11px;
    padding: 8px 13px;
}
/* Leading brand chip — the VITALS mark: the theme's brand gradient square with the white
   pulse glyph on top, so every page tile carries the application logo. The gradient follows
   the active theme via --brand-gradient (sunset on light/dark, teal→blue on graphite,
   indigo→cyan on navy), matching the sidebar logo. Injected via ::before so no per-page
   markup is needed. The white pulse is a data-URI background layered above the gradient. */
.pdb-tiles .stat::before {
    content: "";
    flex: 0 0 auto;
    width: 34px;
    height: 34px;
    border-radius: 10px;
    /* The FULL VITALS mark, matching the sidebar brand: the white pulse trace over a faint
       (22%) Kubernetes helm-wheel watermark, on the theme brand gradient. Both the wheel and
       the pulse live in one SVG data-URI layered above the gradient, so every page tile
       carries the same logo the sidebar shows. */
    background-image:
        url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 32 32' fill='none'%3E%3Cg stroke='%23FFFFFF' stroke-opacity='0.22' stroke-linecap='round' stroke-linejoin='round'%3E%3Cpath d='M16 4.3 25.1 8.7 27.4 18.6 21 26.5 11 26.5 4.6 18.6 6.9 8.7Z' stroke-width='1.4'/%3E%3Ccircle cx='16' cy='16' r='4.1' stroke-width='1.4'/%3E%3Cpath d='M16 11.9V6.6M19.9 14.7 24.2 11.6M18.7 20 21.9 24.4M13.3 20 10.1 24.4M12.1 14.7 7.8 11.6' stroke-width='1.1'/%3E%3C/g%3E%3Cpath d='M9 10.5 14.5 16 9 21.5' stroke='%23FFFFFF' stroke-opacity='0.5' stroke-width='2.6' stroke-linecap='round' stroke-linejoin='round'/%3E%3Cpath d='M15 10.5 20.5 16 15 21.5' stroke='%23FFFFFF' stroke-width='2.6' stroke-linecap='round' stroke-linejoin='round'/%3E%3Cpath d='M7 25c2.4 0 2.4-1.6 4.8-1.6S14.2 25 16.6 25s2.4-1.6 4.8-1.6S23.8 25 26 25' stroke='%23FFFFFF' stroke-opacity='0.85' stroke-width='1.6' stroke-linecap='round'/%3E%3C/svg%3E"),
        var(--brand-gradient);
    background-repeat: no-repeat;
    background-position: center, center;
    background-size: 26px 26px, cover;
    box-shadow: inset 0 1px 0 rgba(255,255,255,0.25);
    align-self: center;
}
/* Block flow (not flex) so the merged value + sub sit together on one inline line under
   the label. The label is a block on its own line; value and sub are inline and share the
   second line, value bold and sub dimmed right after it. */
.pdb-tiles .stat .stat-body {
    display: block;
    min-width: 0;
}
.pdb-tiles .stat .stat-label { display: block; margin-bottom: 2px; font-size: 9.5px; letter-spacing: 0.5px; }
.pdb-tiles .stat .stat-value {
    display: inline;
    font-size: 18px;
    line-height: 1.1;
    vertical-align: baseline;
}
.pdb-tiles .stat .stat-sub {
    display: inline;
    font-size: 10.5px;
    margin: 0 0 0 6px;
    vertical-align: baseline;
}
/* Keep the merged value + sub on a single line so a long sub can never wrap and grow the
   tile taller than its neighbours. The value stays put; the sub takes the remaining width
   and ellipsizes if it would overflow (the full text remains in the tile's title/tooltip
   and on the page). This guarantees a uniform tile height across every page. */
.pdb-tiles .stat .stat-body { white-space: nowrap; overflow: hidden; }
.pdb-tiles .stat .stat-label { overflow: hidden; text-overflow: ellipsis; }
.pdb-tiles .stat .stat-sub { overflow: hidden; text-overflow: ellipsis; max-width: 100%; }
/* The navigable chevron sits in the tile's top-right, a touch smaller than the KPI cards. */
.pdb-tiles a.stat::after, .pdb-tiles button.stat::after { top: 6px; right: 9px; font-size: 12px; }

/* ---- 3D / elevated tile look (all page-level tiles) ----
   Give tiles a raised card feel: a gradient surface, a lit top edge, a soft drop shadow
   and a crisper border, so they read as physical chips above the page rather than flat
   panels. Applies to every .stat tile (filter tiles, info tiles, scroll tiles). */
.stat-grid .stat {
    /* The former hover look is now the resting default: accent border and the stronger
       elevation, so tiles read as raised, active chips without needing a mouseover. */
    border: 1px solid var(--accent);
    border-radius: var(--radius-md, 10px);
    background: linear-gradient(180deg,
        color-mix(in srgb, var(--surface) 92%, #fff) 0%,
        var(--surface) 100%);
    box-shadow: 0 2px 4px rgba(0,0,0,0.22),
                0 8px 18px rgba(0,0,0,0.20),
                inset 0 1px 0 color-mix(in srgb, #fff 65%, transparent);
    transition: box-shadow 0.15s ease, transform 0.15s ease, border-color 0.15s ease;
}
/* Hover keeps only a small lift and a touch more shadow, so there is still feedback on
   interactive tiles now that the accent look is the resting state. */
.stat-grid a.stat:hover, .stat-grid button.stat:hover {
    transform: translateY(-2px);
    box-shadow: 0 3px 6px rgba(0,0,0,0.26),
                0 12px 24px rgba(0,0,0,0.24),
                inset 0 1px 0 color-mix(in srgb, #fff 70%, transparent);
}

/* ===== Zone list cells ================================================= */
/*
  Availability-zone lists like "us-west-2a, us-west-2b, us-west-2c" contain spaces, commas
  and hyphens, all of which are break opportunities — so on a narrow screen the browser
  fragments each zone vertically ("us- / west- / 2a,"), which is unreadable. Keep the whole
  list on one line and let the table's own horizontal scroll absorb the width, matching how
  the other identifier columns behave. Used wherever a zone list is shown (Namespaces,
  Version & Upgrade).
*/
.zone-list {
    white-space: nowrap;
}

/* ---- Client-side info modal (status/phase/reason explainers) ---- */
/* Sits inside .modal-body, so it inherits the modal chrome; these style the structured body. */
.info-modal { display: flex; flex-direction: column; gap: 2px; }
.info-modal-title { margin: 0 0 10px; font-size: 15px; font-weight: 650; color: var(--text); }
/* The first line reads as the lead statement, a touch stronger. */
.info-modal-text { margin: 0 0 10px; font-size: 13px; line-height: 1.6; color: var(--text-dim); }
.info-modal-text:first-child { color: var(--text); font-weight: 550; }
.info-modal-text:last-child { margin-bottom: 0; }
/* A "•" line becomes a bordered, indented point — used for suggested fixes and detail lists. */
.info-modal-point {
    margin: 0 0 6px;
    padding: 8px 12px;
    font-size: 12.5px;
    line-height: 1.5;
    color: var(--text-dim);
    background: var(--bg-subtle);
    border: 1px solid var(--border);
    border-left: 3px solid var(--accent);
    border-radius: var(--radius-sm);
}
.info-modal-point:last-child { margin-bottom: 0; }

/*
 * A cell whose pill/text opens an info modal. Rendered as a bare button so it is keyboard
 * reachable, but styled to look like the surrounding cell content — a subtle dotted underline
 * on hover signals it is interactive without shouting.
 */
.info-trigger {
    background: none;
    border: 0;
    padding: 0;
    margin: 0;
    font: inherit;
    color: inherit;
    cursor: pointer;
    text-align: left;
}
.info-trigger:hover { text-decoration: underline dotted; text-underline-offset: 3px; }
.info-trigger:focus-visible { outline: 2px solid var(--accent); outline-offset: 2px; border-radius: 4px; }

/* A clickable link cell (namespace/workload/pod cross-links). Reuses link-btn where present;
 * this is the lighter inline variant for table cells. */
.xlink {
    background: none;
    border: 0;
    padding: 0;
    font: inherit;
    color: var(--accent);
    cursor: pointer;
}
.xlink:hover { text-decoration: underline; }
.xlink:focus-visible { outline: 2px solid var(--accent); outline-offset: 2px; border-radius: 4px; }

/* CronJobs: recent Job runs shown as a wrapping row of small outcome pills. */
.cron-runs { display: inline-flex; flex-wrap: wrap; gap: 4px; max-width: 260px; }


/* ═══════════════════════════════════════════════════════════════════════════
   FERRY OVERRIDES
   Ferry reuses the VITALS design system verbatim (layout, components, shadows,
   themes) but is its own product: a cool BLUE identity, not VITALS' teal→gold
   sunset. These rules come last so they win by cascade order. They only retint
   the brand gradient + logo stops per theme and add two Ferry-specific controls
   (the environment selector and the greyed branch chip). Everything else — the
   surfaces, tables, tiles, pills, sidebar and topbar — is inherited unchanged.
   ═══════════════════════════════════════════════════════════════════════════ */

/* Blue brand gradient for every theme. Light/dark share it; slate and navy
   inherit it too, so Ferry reads blue everywhere rather than VITALS' palette. */
:root,
[data-theme="light"],
[data-theme="dark"],
[data-theme="slate"],
[data-theme="navy"] {
    /* Muted slate palette, in the range of VITALS' slate theme: a desaturated
       teal → steel → slate-blue sweep. The earlier vivid blue/cyan/teal read as too
       bright against the surfaces, so every brand surface (logo, tile chips, buttons)
       now sits a few steps down in saturation and lightness. */
    --grad-start: #3d8079;
    --grad-mid:   #35708a;
    --grad-end:   #3a5f85;
    --brand-gradient: linear-gradient(120deg,
                        var(--grad-start) 0%, var(--grad-mid) 52%, var(--grad-end) 100%);
    --logo-a: #3d8079;
    --logo-b: #35708a;
    --logo-c: #3a5f85;

    /* Interactive accent follows the same muted family, so selected nav items,
       focus rings and accent borders match the brand instead of shouting. */
    --accent:       #35708a;
    --accent-hover: #2d6076;
    --accent-line:  color-mix(in srgb, #35708a 55%, transparent);
    --accent-soft:  color-mix(in srgb, #35708a 13%, transparent);
}

/* Environment selector in the topbar — a first-class, always-present control. */
.env-select {
    display: inline-flex;
    align-items: center;
    gap: 7px;
    padding: 3px 6px 3px 11px;
    border: 1px solid var(--border-strong);
    border-radius: 999px;
    background: var(--surface);
}
.env-select-label {
    font-size: 10px;
    font-weight: 700;
    letter-spacing: 0.6px;
    color: var(--text-faint);
}
.env-select select {
    border: none;
    background: transparent;
    color: var(--text);
    font: inherit;
    font-weight: 600;
    font-size: 12.5px;
    padding: 3px 4px;
    border-radius: 999px;
    cursor: pointer;
}
.env-select select:focus { outline: 2px solid var(--accent-line); }

/* Branch chip — display-only, greyed, dashed, non-interactive (R1.6). */
.chip-branch {
    background: var(--bg-subtle);
    color: var(--text-faint);
    border: 1px dashed var(--border-strong);
    font-family: var(--mono);
    font-weight: 500;
    cursor: default;
    user-select: none;
}
.chip-branch-icon { width: 12px; height: 12px; opacity: 0.7; }


/* Ferry: result banners (VITALS ships only .banner-warn). */
.banner-ok    { background: var(--ok-soft);    color: var(--ok);    border-bottom-color: color-mix(in srgb, var(--ok) 30%, transparent); }
.banner-info  { background: var(--info-soft);  color: var(--info);  border-bottom-color: color-mix(in srgb, var(--info) 30%, transparent); }
.banner-error { background: var(--error-soft); color: var(--error); border-bottom-color: color-mix(in srgb, var(--error) 30%, transparent); }

/* Ferry: the env label pill shown in a module head. */
.env-pill {
    display: inline-flex; align-items: center;
    padding: 3px 11px; border-radius: 999px;
    background: var(--accent-soft); color: var(--accent);
    font-weight: 700; font-size: 12px; font-family: var(--mono);
}

/* Ferry: greyed, non-interactive branch label used inside table cells (R1.6). */
.branch-tag {
    display: inline-block; padding: 2px 8px; border-radius: 7px;
    background: var(--bg-subtle); color: var(--text-faint);
    border: 1px dashed var(--border-strong);
    font-family: var(--mono); font-size: 11px; user-select: none;
}

/* Ferry: the inline "new tag" apply form in the Deployment table. */
.inline-tag-form { display: inline-flex; align-items: center; gap: 8px; }
.inline-tag-form .tag-input {
    width: 120px; padding: 5px 9px;
    font-family: var(--mono); font-size: 12px;
}
.inline-tag-form .btn-primary { margin-top: 0; padding: 5px 12px; width: auto; font-size: 12px; }


/* Ferry: split sidebar — OPERATIONS (menu) on top, OPTIONS (per-feature controls)
   on the bottom, with OPTIONS starting at the vertical middle of the nav. The nav
   is a flex column; the two halves each take 50% so the divider sits mid-sidebar. */
/* Ferry's OPTIONS controls live inside a standard VITALS .nav-section, so the section
   heading and indent come from VITALS' own styles. The only additions here are the
   control field layout, the static (never-collapsing) heading, and the separator. */
/* Indent matches the menu items exactly: #nav-controls carries .section-items' 8px,
   and this adds .nav-item's 12px, so labels and selects line up with the item icons
   above rather than sitting flush to the sidebar edge. */
.controls-body { display: flex; flex-direction: column; gap: 10px; padding: 2px 10px 4px 12px; }
.control-group { display: flex; flex-direction: column; gap: 6px; }

/* Label line: the icon sits beside the label text, and the control drops to the next
   line beneath it at full panel width. */
.control-label-icon {
    display: flex;
    align-items: center;
    gap: 6px;
}
/* Sizing/colour for .control-icon itself lives further down, where it's set to match
   the nav item icon tiles exactly (see "OPTIONS breathing room" below). */
/* Controls span the panel, so every dropdown shares the same left and right edge. */
.controls-body .control-select { width: 100%; max-width: none; }
.controls-body .control-load-btn { width: 100%; max-width: none; margin: 0; }

/* Separator between controls — the same brand hairline used between OPERATIONS and
   OPTIONS, so the sidebar reads with one consistent divider. */
.control-sep {
    height: 2px;
    border-radius: 2px;
    background: var(--brand-gradient);
    opacity: 0.55;
    margin: 4px 0 2px;
}

/* Static section heading: same look as VITALS' collapsible head, minus the pointer
   affordance, because Ferry's navigation is always expanded. */
.section-head-static { cursor: default; }
.section-head-static:hover { background: none; }

/* Separator between OPERATIONS and OPTIONS — a brand-gradient hairline with breathing
   room above and below, so the two groups read as distinct without boxing them in. */
.nav-section-options {
    margin-top: 18px;
    padding-top: 16px;
    position: relative;
}
.nav-section-options::before {
    content: "";
    position: absolute;
    top: 0;
    left: 12px;
    right: 12px;
    height: 2px;
    border-radius: 2px;
    background: var(--brand-gradient);
    opacity: 0.55;
}
.control-label {
    font-size: 10.5px; text-transform: uppercase; letter-spacing: 0.6px;
    color: var(--text-faint); font-weight: 700;
}
.control-select {
    width: 100%;
    max-width: 168px;
    padding: 7px 10px;
    border: 1px solid var(--border-strong);
    border-radius: var(--radius-sm);
    background: var(--bg);
    color: var(--text);
    font: inherit; font-weight: 600; font-size: 12.5px;
    cursor: pointer;
}
.control-select:focus { outline: none; border-color: var(--accent); box-shadow: 0 0 0 3px var(--accent-soft); }
.control-note {
    display: inline-flex; align-items: center; gap: 7px;
    padding: 7px 10px;
    border: 1px dashed var(--border-strong);
    border-radius: var(--radius-sm);
    background: var(--bg-subtle);
    color: var(--text-faint);
    font-size: 12px; cursor: default; user-select: none;
}
.control-note-icon { width: 13px; height: 13px; opacity: 0.7; }
.control-note strong { color: var(--text-dim); font-weight: 700; font-family: var(--mono); margin-left: 2px; }


/* ─── Ferry brand tagline (under the wordmark in the sidebar) ──────────────── */
.brand-text { display: flex; flex-direction: column; line-height: 1.1; min-width: 0; }
.brand-tagline {
    font-size: 9.5px;
    font-weight: 600;
    letter-spacing: 0.2px;
    color: var(--text-faint);
    margin-top: 2px;
    white-space: nowrap;
}
.control-hint { color: var(--text-faint); font-size: 12px; line-height: 1.5; margin: 0; }

/* ─── Home / landing page ──────────────────────────────────────────────────── */
.ferry-home { padding: 4px 2px; }

.home-hero { max-width: 760px; margin: 8px 0 22px; }
.home-title {
    font-size: 30px; font-weight: 800; letter-spacing: -0.6px; margin: 0 0 10px;
    background: var(--brand-gradient);
    -webkit-background-clip: text; background-clip: text;
    -webkit-text-fill-color: transparent; color: var(--grad-start);
}
.home-lede { font-size: 15px; color: var(--text-dim); margin: 0 0 18px; line-height: 1.6; }
.home-cta .btn-primary { margin-top: 0; padding: 10px 20px; width: auto; font-size: 14px; }

/* The animated stage */
.home-stage {
    background: var(--surface);
    border: 1px solid var(--border);
    border-radius: var(--radius);
    box-shadow: var(--shadow-sm);
    padding: 18px 18px 12px;
    margin-bottom: 22px;
}
#ferry-flow { width: 100%; height: auto; display: block; }
.home-caption {
    text-align: center; margin-top: 6px;
    color: var(--text-dim); font-size: 13.5px; font-weight: 600;
    min-height: 20px;
}

/* Node/link states in the flow diagram */
.fh-lane { font-size: 11px; font-weight: 700; letter-spacing: 0.6px; fill: var(--text-faint); }
.fh-emoji { font-size: 26px; text-anchor: middle; }
.fh-emoji-sm { font-size: 22px; text-anchor: middle; }
.fh-hero-emoji { font-size: 24px; text-anchor: middle; }
.fh-label { font-size: 11px; font-weight: 700; fill: var(--text-dim); text-anchor: middle; }
.fh-hero-label { font-size: 11.5px; font-weight: 800; fill: #fff; text-anchor: middle; }

.fh-node circle {
    fill: var(--surface-2);
    stroke: var(--border-strong);
    stroke-width: 2;
    transition: stroke 0.3s, stroke-width 0.3s, filter 0.3s, opacity 0.3s;
}
.fh-node.on circle {
    stroke: var(--accent);
    stroke-width: 3.5;
    filter: drop-shadow(0 0 10px color-mix(in srgb, var(--accent) 55%, transparent));
}
.fh-node.done circle { opacity: 0.5; }
.fh-node.done .fh-label, .fh-node.done .fh-emoji, .fh-node.done .fh-emoji-sm { opacity: 0.5; }

.fh-node-hero rect {
    transition: filter 0.3s;
    filter: drop-shadow(0 4px 10px color-mix(in srgb, var(--accent) 35%, transparent));
}
.fh-node-hero.on rect {
    filter: drop-shadow(0 0 16px color-mix(in srgb, var(--accent) 70%, transparent));
}

.fh-link { stroke: var(--border-strong); stroke-width: 2.4; }
.fh-fan {
    fill: none; stroke: var(--border-strong); stroke-width: 2.2;
    stroke-dasharray: 6 5; opacity: 0.45; transition: stroke 0.3s, opacity 0.3s;
}
.fh-fan.on { stroke: var(--accent); opacity: 1; }
.fh-token { filter: drop-shadow(0 0 6px color-mix(in srgb, var(--accent) 70%, transparent)); }

/* Feature quick-link cards */
.home-cards { display: grid; grid-template-columns: repeat(4, 1fr); gap: 14px; }
.home-card {
    display: flex; flex-direction: column; align-items: flex-start; gap: 8px;
    padding: 16px;
    background: var(--surface);
    border: 1px solid var(--border);
    border-radius: var(--radius-md);
    box-shadow: var(--shadow-sm);
    cursor: pointer; text-align: left;
    font: inherit;
    transition: transform 0.12s, box-shadow 0.12s, border-color 0.12s;
}
.home-card:hover {
    transform: translateY(-2px);
    box-shadow: var(--shadow);
    border-color: var(--accent-line);
}
.home-card-icon {
    width: 34px; height: 34px; border-radius: 9px;
    display: grid; place-items: center;
    background: var(--accent-soft); color: var(--accent);
}
.home-card-icon svg { width: 18px; height: 18px; }
.home-card-title { font-weight: 700; font-size: 14px; color: var(--text); }
.home-card-sub { font-size: 12px; color: var(--text-faint); }

@media (max-width: 960px) {
    .home-cards { grid-template-columns: repeat(2, 1fr); }
}


/* ─── Toasts (bottom-right, auto-fading action results) ───────────────────── */
.toast-host {
    position: fixed;
    right: 18px;
    bottom: 18px;
    z-index: 1200;
    display: flex;
    flex-direction: column;
    gap: 10px;
    pointer-events: none;
    max-width: min(360px, calc(100vw - 36px));
}
.toast {
    pointer-events: auto;
    display: flex;
    align-items: flex-start;
    gap: 10px;
    padding: 11px 13px;
    border-radius: var(--radius);
    background: var(--surface);
    border: 1px solid var(--border-strong);
    box-shadow: var(--shadow-md, 0 8px 24px rgba(0,0,0,0.18));
    color: var(--text);
    /* Enter animation; the fade-out is added as a class by ferry.js. */
    opacity: 0;
    transform: translateY(8px);
    animation: toast-in 0.22s ease forwards;
}
@keyframes toast-in  { to { opacity: 1; transform: translateY(0); } }
@keyframes toast-out { to { opacity: 0; transform: translateY(8px); } }
.toast.toast-hide { animation: toast-out 0.4s ease forwards; }

.toast-icon { flex: 0 0 auto; width: 18px; height: 18px; margin-top: 1px; }
.toast-icon svg { width: 18px; height: 18px; }
.toast-ok    { border-left: 3px solid var(--ok, #16a34a); }
.toast-ok    .toast-icon { color: var(--ok, #16a34a); }
.toast-info  { border-left: 3px solid var(--accent); }
.toast-info  .toast-icon { color: var(--accent); }
.toast-error { border-left: 3px solid var(--error, #dc2626); }
.toast-error .toast-icon { color: var(--error, #dc2626); }

.toast-body  { display: flex; flex-direction: column; gap: 2px; min-width: 0; }
.toast-title { font-weight: 700; font-size: 13px; }
.toast-msg   { font-size: 12.5px; color: var(--text-dim); word-break: break-word; }


/* ─── Panel tile row (VITALS pdb-tiles; logo chip injected by ::before) ───── */
.stat-grid.pdb-tiles { margin-bottom: 16px; }

/* No bespoke brand tile: every page tile already carries the Ferry mark via
   `.pdb-tiles .stat::before` (VITALS' own pattern), so the logo is always visible in
   the tile row without extra markup. */

/* Compare's environment picker is a multi-select; the Ferry combo renders its list, so
   the native listbox sizing no longer applies. */


/* ═══════════════════════════════════════════════════════════════════════════
   UNIFIED BUTTONS — one look for every action button in the app.
   Every filled/primary action (Sign in, Apply, Promote, Load, home CTA) uses the
   brand gradient, white text, and the same hover lift, so the app reads as one
   product. Placed at the end so source order beats earlier per-class backgrounds
   (e.g. .btn-sm's flat fill) without needing !important.
   ═══════════════════════════════════════════════════════════════════════════ */
.btn-primary,
.btn.btn-primary,
.btn-primary.btn-sm,
.btn-gradient,
.control-load-btn {
    background: var(--brand-gradient);
    color: #fff;
    border: none;
    border-radius: var(--radius-sm);
    font-family: inherit;
    font-weight: 650;
    cursor: pointer;
    transition: filter 0.14s ease, transform 0.06s ease, box-shadow 0.15s ease;
    box-shadow: var(--shadow), inset 0 1px 0 color-mix(in srgb, #fff 30%, transparent);
}
.btn-primary:hover,
.btn.btn-primary:hover,
.btn-primary.btn-sm:hover,
.btn-gradient:hover,
.control-load-btn:hover {
    filter: brightness(1.07) saturate(1.05);
    color: #fff;
    background: var(--brand-gradient);
    border-color: transparent;
    transform: translateY(-1px);
    box-shadow: var(--shadow-md), inset 0 1px 0 color-mix(in srgb, #fff 34%, transparent);
}
.btn-primary:active,
.btn.btn-primary:active,
.btn-primary.btn-sm:active,
.btn-gradient:active,
.control-load-btn:active {
    transform: translateY(1px);
    box-shadow: var(--shadow-sm);
}
.btn-primary:disabled,
.btn.btn-primary:disabled,
.control-load-btn:disabled {
    opacity: 0.5;
    cursor: not-allowed;
    filter: none;
    transform: none;
}

/* The Load button spans the OPTIONS column like the selects above it. */
.control-load-btn {
    width: 100%;
    max-width: 168px;
    margin-top: 4px;
    padding: 9px 12px;
    font-size: 13px;
}

/* Inline table action buttons (Apply / Promote) keep their compact size but the
   unified gradient fill above. */
.inline-tag-form .btn-primary,
td .btn-primary.btn-sm { margin-top: 0; padding: 6px 14px; font-size: 12px; width: auto; }


/* ─── Deployment bulk actions (inside the filter toolbar, pinned far right) ── */
.bulk-actions {
    display: inline-flex;
    align-items: center;
    gap: 8px;
    margin-left: 10px;
}
.bulk-bar .btn-primary,
.bulk-bar .btn-sm { margin-top: 0; }
.bulk-bar .btn-primary:disabled,
.btn-reset:disabled { opacity: 0.42; cursor: not-allowed; transform: none; filter: none; }

/* Reset carries a warm amber tint so it reads as "revert", clearly distinct from the
   brand-gradient Apply without shouting like a destructive red. */
.btn-reset {
    background: color-mix(in srgb, var(--warn) 14%, transparent);
    border: 1px solid color-mix(in srgb, var(--warn) 48%, transparent);
    color: color-mix(in srgb, var(--warn) 78%, var(--text));
    font-weight: 650;
}
.btn-reset:hover:not(:disabled) {
    background: color-mix(in srgb, var(--warn) 22%, transparent);
    border-color: var(--warn);
    color: var(--warn);
}

/* Actions column: a fixed width shared by header and cells.
   Content is 2 buttons (82px each) + an 8px gap + the 16px right inset = 188px; the
   column was fixed at 176px, 12px short of its own content, so the header (sized by its
   text, no override) and the cell (clipped to the too-small fixed width) disagreed —
   the header looked wider than the column because the column was narrower than what it
   had to hold. The header text stays left-aligned like every other header (see the
   override further down); only the cells — which hold the buttons that must line up
   under Apply all/Reset all — are right-aligned. */
.col-actions { white-space: nowrap; text-align: right; width: 188px; }
/* New Tag column: fixed width, and no cell box — the input carries the only border. */
.col-newtag { width: 168px; }
/* Current Tag column: same fixed width as New Tag, and its value centered inside the
   boxed cell so it lines up directly above/beside the centered New Tag input. */
.col-curtag { width: 168px; text-align: center; }
table.data.cell-boxed td.col-curtag .cell-val { justify-content: center; }
table.data.cell-boxed td.no-box,
table.data.cell-boxed td.no-box > .cell-val,
table.data.cell-boxed td.col-actions,
table.data.cell-boxed td.col-actions > .cell-val { border: none; background: none; box-shadow: none; }
.col-actions .inline-tag-form {
    display: inline-flex;
    gap: 8px;
    align-items: center;
    justify-content: flex-end;
}

/* All four action buttons — Apply, Reset, Apply all, Reset all — share one size, so the
   column reads as a single aligned block rather than four different chips. */
.bulk-actions .btn-sm,
.bulk-actions .btn-primary,
.col-actions .btn-sm,
.col-actions .btn-primary {
    width: 76px;
    height: 28px;
    padding: 0;
    margin: 0;
    display: inline-flex;
    align-items: center;
    justify-content: center;
    font-size: 12px;
    line-height: 1;
    box-sizing: border-box;
}

/* The New Tag field owns its single border (its cell has none), so there is exactly one
   outline around the control. */
.col-newtag .tag-input {
    width: 100%;
    box-sizing: border-box;
    text-align: center;
    height: 28px;
}
/* A field whose tag differs from the one recorded in Git, so pending edits are
   obvious before the user commits them. */
.tag-input.tag-dirty {
    border-color: var(--accent);
    background: var(--accent-soft);
    font-weight: 700;
}


/* ─── Native control rendering (datalist popups, select lists, scrollbars) ───
   A <datalist> popup is drawn by the browser, not by CSS, so it cannot be themed
   directly — it follows `color-scheme`. Without this declaration the browser falls back
   to the OS preference, which is why the New Tag suggestions rendered as a black panel
   on a light page. Binding color-scheme to the active theme makes every native popup,
   dropdown list and scrollbar match the application instead of the operating system. */
:root,
[data-theme="light"],
[data-theme="slate"] { color-scheme: light; }

[data-theme="dark"],
[data-theme="navy"]  { color-scheme: dark; }

/* The tag field and its suggestion affordance: keep the input itself on the app's
   surface colours, with a subtle caret hint that suggestions exist. */
.tag-input {
    background: var(--bg);
    color: var(--text);
    border: 1px solid var(--border-strong);
    border-radius: var(--radius-sm);
    padding: 5px 9px;
    font-family: var(--mono);
    font-size: 12px;
    min-width: 132px;
}
.tag-input:focus {
    outline: none;
    border-color: var(--accent);
    box-shadow: 0 0 0 3px var(--accent-soft);
}


/* ═══════════════════════════════════════════════════════════════════════════
   FINAL OVERRIDES — last in the file so these win on source order.
   ═══════════════════════════════════════════════════════════════════════════ */

/* ---- All four action buttons identical in width and height ---------------
   Text size may differ between "Apply" and "Reset all"; the control box does not.
   Declared here (and not earlier) because the unified-button block sets width:auto
   for table buttons, which would otherwise win. */
.bulk-actions > .btn-primary,
.bulk-actions > .btn-sm,
.row-actions > .btn-primary,
.row-actions > .btn-sm,
td.col-actions .btn-primary.btn-sm,
td.col-actions .btn-sm {
    width: 82px;
    min-width: 82px;
    max-width: 82px;
    height: 27px;
    padding: 0;
    margin: 0;
    display: inline-flex;
    align-items: center;
    justify-content: center;
    line-height: 1;
    box-sizing: border-box;
    border-radius: var(--radius-sm);
}
.row-actions { display: inline-flex; gap: 8px; align-items: center; justify-content: flex-end; }

/* ---- Uniform cell height, matched to the New Tag control (27px) ---------- */
table.data.cell-boxed td > .cell-val {
    min-height: 27px;
    display: flex;
    align-items: center;
}
table.data.cell-boxed td.num > .cell-val { justify-content: flex-end; }
.col-newtag .tag-input { height: 27px; }

/* ---- Toast in the theme's own colours, not a generic green -------------- */
.toast-ok {
    border-left: 3px solid var(--accent);
    background: var(--surface);
}
.toast-ok .toast-icon { color: var(--accent); }
.toast-ok .toast-title { color: var(--text); }

/* ---- OPTIONS breathing room + control icons sized like the nav item icons -
   The nav item icons are 26px tiles with a 14px stroked svg inside (.item-icon /
   .item-icon svg above); OPTIONS' Branch/Environment icons now use the exact same
   tile so both groups read as one visual language and stay visible on dark themes
   instead of the smaller, dimmer icon this replaced. */
.nav-section-options .section-head { margin-bottom: 6px; }
.nav-section-options .controls-body { padding-top: 8px; }
.control-label-icon { gap: 8px; }
.control-icon {
    flex-shrink: 0;
    width: 26px;
    height: 26px;
    padding: 6px;
    border-radius: 7px;
    border: 1px solid var(--border);
    background: var(--bg);
    color: var(--text-faint);
    box-sizing: border-box;
    opacity: 1;
    fill: none;
    stroke: currentColor;
    stroke-width: 1.4;
    stroke-linecap: round;
    stroke-linejoin: round;
}


/* ─── Export format menu (Markdown / Excel) ───────────────────────────────── */
.export-wrap { position: relative; display: inline-flex; }

.export-menu {
    /* Fixed, not absolute: the topbar/content flex layout and .main{overflow:hidden}
       put .content in a stacking context that painted OVER an absolutely-positioned
       menu dropping below the topbar — the menu showed but the panel intercepted every
       click, so the items looked present yet were unclickable. Fixed positioning (with
       JS-computed top/left, same as the combo menus) escapes that stacking trap. */
    position: fixed;
    z-index: 3000;
    min-width: 168px;
    padding: 5px;
    background: var(--surface);
    border: 1px solid var(--border-strong);
    border-radius: var(--radius);
    box-shadow: var(--shadow-md, 0 10px 28px rgba(0,0,0,0.22));
}
.export-menu[hidden] { display: none; }

.export-menu-item {
    display: flex;
    align-items: center;
    justify-content: space-between;
    gap: 12px;
    width: 100%;
    padding: 8px 10px;
    background: none;
    border: none;
    border-radius: var(--radius-sm);
    color: var(--text);
    font: inherit;
    font-size: 13px;
    text-align: left;
    cursor: pointer;
    transition: background 0.12s ease, color 0.12s ease;
}
/* A clear bluish highlight on hover/focus so it is obvious the row is clickable. Uses the
   info blue mixed toward the surface — distinctly blue, but not so saturated it fights the
   text, which turns white for contrast against the fill. */
.export-menu-item:hover,
.export-menu-item:focus-visible {
    background: color-mix(in srgb, var(--info) 82%, var(--surface));
    color: #ffffff;
    outline: none;
}
.export-menu-item:hover .export-ext,
.export-menu-item:focus-visible .export-ext { color: rgba(255, 255, 255, 0.85); }
.export-fmt { font-weight: 600; }
.export-ext {
    font-family: var(--mono);
    font-size: 11px;
    color: var(--text-faint);
}
.export-menu-item:hover .export-ext { color: var(--accent); }


/* ═══════════════════════════════════════════════════════════════════════════
   FERRY COMBO — a fully themed replacement for native <select> and <datalist>
   popups.

   A native popup list is drawn by the browser, so CSS cannot reach it; the only
   control is `color-scheme`. This component renders the list as ordinary DOM, so every
   theme token applies — surface, border, radius, accent, shadow — and the branch, env
   and image-tag pickers all look identical.

   The original control is kept in the DOM (hidden) as the value holder, so anything
   reading `select.value` or listening for `change` keeps working.
   ═══════════════════════════════════════════════════════════════════════════ */

.fcombo { position: relative; width: 100%; }
.fcombo-native { display: none !important; }

/* ---- Trigger (the closed control) ---- */
.fcombo-trigger {
    display: flex;
    align-items: center;
    gap: 8px;
    width: 100%;
    padding: 6px 9px 6px 11px;
    background: var(--bg);
    border: 1px solid var(--border-strong);
    border-radius: var(--radius-sm);
    color: var(--text);
    font: inherit;
    font-size: 12.5px;
    font-weight: 600;
    text-align: left;
    cursor: pointer;
    box-shadow: var(--shadow-sm), inset 0 1px 0 var(--lift-top);
    transition: border-color .12s, box-shadow .12s, background .12s;
}
.fcombo-trigger:hover { border-color: var(--accent-line); }
.fcombo-trigger:focus-visible,
.fcombo.open > .fcombo-trigger {
    outline: none;
    border-color: var(--accent);
    box-shadow: 0 0 0 3px var(--accent-soft);
}
.fcombo-value {
    flex: 1 1 auto;
    min-width: 0;
    overflow: hidden;
    text-overflow: ellipsis;
    white-space: nowrap;
}
.fcombo-value.is-placeholder { color: var(--text-faint); font-weight: 500; }
.fcombo-caret {
    flex: 0 0 auto;
    width: 12px;
    height: 12px;
    color: var(--text-faint);
    transition: transform .14s ease, color .12s;
}
.fcombo.open .fcombo-caret { transform: rotate(180deg); color: var(--accent); }

/* ---- Menu (the open list) ---- */
.fcombo-menu {
    position: absolute;
    top: calc(100% + 5px);
    left: 0;
    right: 0;
    z-index: 1150;
    padding: 5px;
    background: var(--surface);
    border: 1px solid var(--accent-line);
    border-radius: var(--radius);
    box-shadow: 0 12px 30px rgba(0,0,0,0.26), inset 0 1px 0 var(--lift-top);
    animation: fcombo-in .13s ease;
}
.fcombo-menu[hidden] { display: none; }
@keyframes fcombo-in { from { opacity: 0; transform: translateY(-4px); } }

/* Flipping above when there is no room below is now computed directly as an inline
   `top` by positionMenu() in JS (measured from the trigger's real position), not by
   toggling this class — kept only as a harmless fallback for the instant before JS
   repositions on open. */

/* Type-to-filter box, shown when the list is long enough to warrant it. */
.fcombo-search { padding: 2px 3px 6px; }
.fcombo-filter {
    width: 100%;
    box-sizing: border-box;
    padding: 5px 8px;
    background: var(--bg);
    border: 1px solid var(--border);
    border-radius: var(--radius-sm);
    color: var(--text);
    font: inherit;
    font-size: 12px;
}
.fcombo-filter:focus { outline: none; border-color: var(--accent); }

.fcombo-options { max-height: 232px; overflow-y: auto; }

.fcombo-option {
    display: flex;
    align-items: center;
    gap: 8px;
    width: 100%;
    padding: 7px 9px;
    background: none;
    border: none;
    border-radius: var(--radius-sm);
    color: var(--text);
    font: inherit;
    font-size: 12.5px;
    text-align: left;
    cursor: pointer;
}
.fcombo-option:hover,
.fcombo-option.active { background: var(--accent-soft); color: var(--accent); }
.fcombo-option[aria-selected="true"] {
    background: var(--brand-gradient);
    color: #fff;
    font-weight: 700;
}
.fcombo-option[aria-selected="true"] .fcombo-check { opacity: 1; }
.fcombo-check {
    flex: 0 0 auto;
    width: 13px;
    height: 13px;
    opacity: 0;
}
.fcombo-option-label { flex: 1 1 auto; min-width: 0; overflow: hidden; text-overflow: ellipsis; }
.fcombo-empty { padding: 9px; color: var(--text-faint); font-size: 12px; text-align: center; }

/* Monospace list for image tags, so versions align and read as versions. */
.fcombo-mono .fcombo-option,
.fcombo-mono .fcombo-filter { font-family: var(--mono); font-size: 12px; }

/* ---- The image-tag field: the input stays typeable, the panel is ours ---- */
.fcombo-input-wrap { position: relative; width: 100%; }
.fcombo-input-wrap .fcombo-menu { left: 0; right: 0; }


/* ─── Table filter controls, matched to the combo triggers ─────────────────
   The filter inputs and status dropdown sat on the card background, so in the light
   theme they read as unfilled outlines while every other control had a surface. Giving
   them the same fill, border and lit edge as .fcombo-trigger makes the whole toolbar
   read as one family of controls in every theme. */
.filter-bar .input,
.filter-bar select,
.filter-bar .field .input {
    background: var(--bg);
    border: 1px solid var(--border-strong);
    border-radius: var(--radius-sm);
    color: var(--text);
    font: inherit;
    font-size: 12.5px;
    padding: 6px 10px;
    box-shadow: var(--shadow-sm), inset 0 1px 0 var(--lift-top);
    transition: border-color .12s, box-shadow .12s;
}
.filter-bar .input::placeholder { color: var(--text-faint); }
.filter-bar .input:hover,
.filter-bar select:hover { border-color: var(--accent-line); }
.filter-bar .input:focus,
.filter-bar select:focus {
    outline: none;
    border-color: var(--accent);
    box-shadow: 0 0 0 3px var(--accent-soft);
}
/* A select needs room for its caret and should read as a dropdown, not a text box. */
.filter-bar select {
    cursor: pointer;
    font-weight: 600;
    padding-right: 26px;
}


/* ─── Tiles show label + value only ────────────────────────────────────────
   With the third line gone the value owns its own line and can use the tile's full
   width, so a long value (a branch name, an environment) ellipsizes at the tile edge
   instead of being clipped mid-glyph by a shared line. */
.pdb-tiles .stat .stat-value {
    display: block;
    overflow: hidden;
    text-overflow: ellipsis;
    white-space: nowrap;
    max-width: 100%;
}
.pdb-tiles .stat .stat-sub { display: none; }


/* ═══════════════════════════════════════════════════════════════════════════
   New Tag cell + action-column alignment (final word on both)
   ═══════════════════════════════════════════════════════════════════════════ */

/* ---- New Tag looks exactly like every other boxed cell ------------------
   Same border colour, inset fill, radius, mono type and metrics as
   `table.data.cell-boxed .cell-val`, so nothing marks this column out as special. The
   accent only appears while the field has focus. */
.col-newtag .tag-input {
    width: 100%;
    box-sizing: border-box;
    padding: 1px 8px;
    border: 1px solid var(--table-accent-border);
    border-radius: var(--radius-sm);
    background: var(--bg-inset);
    color: var(--text-dim);
    font-family: var(--mono);
    font-size: 11.5px;
    line-height: 1.7;
    min-height: 27px;
    height: 27px;
    text-align: center;
    box-shadow: none;
}
.col-newtag .tag-input:hover { border-color: var(--table-accent-border); }
.col-newtag .tag-input:focus {
    outline: none;
    border-color: var(--accent);
    box-shadow: 0 0 0 2px var(--accent-soft);
}
/* A pending edit is shown by weight and colour, not by a different border. */
.col-newtag .tag-input.tag-dirty { font-weight: 700; color: var(--accent); }

/* ---- The four action buttons form one column ----------------------------
   Apply all / Reset all sit in the toolbar; Apply / Reset sit in the table's last
   column. They only line up if both are right-aligned AND inset from the card's right
   edge by the same amount, so that inset is declared once here and used by both. */
.table-wrap { --ferry-actions-inset: 16px; }

.bulk-bar { padding-right: var(--ferry-actions-inset); }
.bulk-actions { margin-left: 10px; margin-right: 0; gap: 8px; }

table.data.cell-boxed td.col-actions {
    padding-right: var(--ferry-actions-inset);
    text-align: right;
}
/* Header text left-aligned, matching every other column header; only the cell content
   (the buttons) is right-aligned above. */
table.data.cell-boxed th.col-actions { text-align: left; }
.row-actions { gap: 8px; }


/* ─── VITALS filter combobox, themed to match the Ferry combos ─────────────
   The multi-select filter menu shipped without a surface of its own, so it rendered on
   the page's white with the browser's default bright-blue checkboxes. Giving it the same
   panel treatment as .fcombo-menu, and pointing `accent-color` at the brand accent,
   makes every dropdown in the app read the same. */
/* --surface is intentionally 6% transparent (color-mix with transparent) so cards let a
   hint of the page wash through. That is wrong for a floating popup, which would show the
   table rows behind it — hence an explicitly OPAQUE surface token for every overlay. */
.combo-menu {
    background: var(--surface-opaque);
    border: 1px solid var(--accent-line);
    border-radius: var(--radius);
    box-shadow: 0 12px 30px rgba(0,0,0,0.26), inset 0 1px 0 var(--lift-top);
    padding: 5px;
    z-index: 1150;
}

.combo-option {
    display: flex;
    align-items: center;
    gap: 9px;
    padding: 7px 9px;
    border-radius: var(--radius-sm);
    color: var(--text);
    font-size: 12.5px;
    cursor: pointer;
}
.combo-option:hover { background: var(--accent-soft); color: var(--accent); }

/* Native checkbox, recoloured to the brand accent instead of the OS blue. */
.combo-option input[type="checkbox"],
.filter-bar input[type="checkbox"] {
    accent-color: var(--accent);
    width: 14px;
    height: 14px;
    margin: 0;
    flex: 0 0 auto;
    cursor: pointer;
}

/* "Clear selection" reads as a quiet action, separated from the list. */
.combo-clear {
    display: block;
    width: 100%;
    padding: 6px 9px 8px;
    margin-bottom: 4px;
    border: none;
    border-bottom: 1px solid var(--border);
    background: none;
    color: var(--accent);
    font: inherit;
    font-size: 12px;
    font-weight: 600;
    text-align: left;
    cursor: pointer;
}
.combo-clear:hover { color: var(--accent-hover); text-decoration: underline; }

/* The closed control, matched to the other triggers. */
.combo .combo-control {
    background: var(--bg);
    border: 1px solid var(--border-strong);
    border-radius: var(--radius-sm);
    box-shadow: var(--shadow-sm), inset 0 1px 0 var(--lift-top);
}
.combo.combo-open .combo-control {
    border-color: var(--accent);
    box-shadow: 0 0 0 3px var(--accent-soft);
}
.combo-caret { color: var(--text-faint); }
.combo.combo-open .combo-caret { color: var(--accent); }


/* ═══════════════════════════════════════════════════════════════════════════
   OPAQUE OVERLAY SURFACE

   `--surface` is deliberately slightly transparent so cards let a little of the page
   wash through. Any element that FLOATS over content needs a fully opaque fill instead,
   or whatever sits beneath shows through it — which is what made the filter dropdown
   look see-through over the table.

   One token per theme, used by every popup: dropdown menus, the export menu and toasts.
   ═══════════════════════════════════════════════════════════════════════════ */
/* The light theme needs a *tinted* panel, not plain white: on a white page a white popup
   reads as having no surface at all, which is why the dropdown looked unstyled there while
   the darker themes looked fine. A faint cool tint plus a firmer border makes it read as a
   floating panel and still keeps it fully opaque. */
:root,
[data-theme="light"] { --surface-opaque: #e4edf3; }
[data-theme="slate"] { --surface-opaque: #c9d0d8; }
[data-theme="dark"]  { --surface-opaque: #131d27; }
[data-theme="navy"]  { --surface-opaque: #1a2f59; }

.fcombo-menu,
.export-menu,
.toast,
.combo-menu {
    background: var(--surface-opaque);
}

/* The option rows must not tint the panel translucent either. */
.combo-option,
.fcombo-option { background-color: transparent; }


/* ─── Wide tile for long values (e.g. a 40-character branch name) ──────────
   Tiles sit on an auto-fill grid of ~178px tracks, so a long branch would ellipsize
   almost immediately. A value over 16 characters takes two tracks instead, and the full
   string is always available as the tile's tooltip. */
.stat-grid .stat.stat-wide { grid-column: span 2; }
.pdb-tiles .stat.stat-wide .stat-value { font-size: 16px; }


/* ─── Actions column ────────────────────────────────────────────────────────
   Apply all / Reset all live on the filter row (.bulk-row) now, not in the Actions
   <th> — this rule is retained only for the .bulk-actions button spacing it still
   provides there. */
.bulk-actions { display: inline-flex; gap: 8px; margin: 0; }

/* Header cell needs room for a stacked label + button pair. */
/* Dropdown panels get a firmer border in the light theme so the tinted surface reads as
   a raised panel rather than a flat block. */
[data-theme="light"] .combo-menu,
[data-theme="light"] .fcombo-menu,
[data-theme="light"] .export-menu {
    border-color: color-mix(in srgb, var(--accent) 38%, var(--border-strong));
    box-shadow: 0 14px 34px rgba(31, 60, 78, 0.18), inset 0 1px 0 #fff;
}


/* --- Native selects that a combo owns are never shown ---------------------
   Any <select data-fcombo> is hidden by the stylesheet itself, not only by the inline
   style the builder sets. This means a select can never be visible between arriving in a
   swap and being wired, and it cannot reappear if a teardown runs without a rebuild �
   both of which produced a duplicate control next to its own combo. */
select[data-fcombo] { display: none !important; }


/* --- Bulk actions back on the filter row ----------------------------------
   Apply all / Reset all sit on the same line as the Application filter. Their exact
   right-edge alignment with the per-row buttons is measured by ferry.js
   (alignBulkActions), because a toolbar and a table cell cannot be reconciled by CSS. */
.filter-bar .bulk-actions { display: inline-flex; gap: 8px; margin-left: 10px; }

/* The Actions header is a plain label again. */
.col-actions-head, .col-actions-title { display: inline; }
table.data.cell-boxed th.col-actions { vertical-align: middle; }


/* --- Dropdown panel reads the same whether or not anything is selected ----
   The panel tint was subtle enough that with no selection it looked white, and only
   appeared blue once a row carried the selected gradient (simultaneous contrast). The
   surface is now stated explicitly on the panel AND its option list, so the fill never
   depends on selection state, and unselected rows sit on that same surface rather than
   on whatever shows through. */
.fcombo-menu,
.fcombo-menu .fcombo-options,
.combo-menu {
    background-color: var(--surface-opaque);
}
.fcombo-option,
.combo-option { background-color: transparent; }

/* Keep the selected row's gradient from dominating the panel: a touch smaller and with
   the surface still visible around it. */
.fcombo-menu .fcombo-options { border-radius: var(--radius-sm); padding: 1px; }


/* --- Long values (e.g. a 40-character branch name) -----------------------
   The sidebar control is ~170px, so a long branch truncates in the closed trigger (its
   full text is on the tooltip). The OPEN list, however, must not truncate: two branches
   sharing a prefix would be indistinguishable. */
/* Both combo flavours are position:fixed now (positionMenu/place() set left/top/width
   inline from the trigger's own rect, in JS) so a container's `overflow: hidden` — e.g.
   .table-wrap rounding off the card's corners — can never clip the open panel again, the
   same fix already applied to the tag-suggestion panel. A PERCENTAGE width here would
   resolve against the viewport on a fixed element, not the control, so only a pixel
   ceiling is set; the floor comes from the inline width JS already applied. */
.fcombo:not(.fcombo-input-wrap) > .fcombo-menu {
    max-width: 320px;
}
.fcombo-option-label {
    overflow: hidden;
    text-overflow: ellipsis;
    white-space: nowrap;
    max-width: 280px;
}
/* The tag panel is sized inline by place() from the field's own rect. Only a sane floor
   and ceiling here - no percentage, because this panel is position:fixed. */
.fcombo-input-wrap .fcombo-menu {
    min-width: 150px;
    max-width: 260px;
}


/* --- The dropdown panel never gains a horizontal scrollbar ----------------
   A long message (or, before this, a long typed value echoed back) could force the
   panel wider than its column and add an unwanted horizontal scrollbar. Options and any
   status text now wrap and clip to the panel's own width instead of stretching it. */
.fcombo-menu, .fcombo-options { overflow-x: hidden; }
.fcombo-empty { white-space: normal; word-break: break-word; }


/* --- Deployment's filter bar + bulk actions share one visual row ----------
   The Workload table lost its Group facet (Custom/Vendor) when the bulk-action row
   was hand-built as a plain text-only toolbar, leaving typing as the only filter � every
   other table (Compare, Promote) still had the shared bar() with its Status facet AND-
   combined with the text filter. This restores bar() as-is (so the facet logic is not
   duplicated) and lays its own .toolbar.filter-bar plus the bulk actions on one flex row.
*/
/* Restates the top/side breathing room `.table-wrap > .filter-bar` gives every other
   table's filter row. That selector requires a DIRECT child, and here the filter bar
   is nested one level deeper inside .bulk-row (so the bulk-action buttons can share its
   line) - which silently dropped the gap and put the filter row flush against the
   card's top border. */
.bulk-row {
    display: flex;
    align-items: center;
    flex-wrap: wrap;
    gap: 10px;
    padding: 12px 15px 4px;
}
.bulk-row .filter-bar {
    flex: 1 1 auto;
    padding: 0;
    margin-bottom: 0;
}
.bulk-row .bulk-actions { flex: 0 0 auto; }


/* --- Compare's Compare/Clear button pair (mirrors Promote's Load) ---------- */
.control-row {
    display: flex;
    gap: 8px;
}
.control-clear-btn {
    flex: 0 0 auto;
    width: 72px;
    margin: 0;
}
.control-load-btn { flex: 1 1 auto; }


/* --- Compare: inline match/different flags next to each tag ---------------
   A small icon ahead of the tag pill, so a difference is visible at a glance across a
   wide comparison (many environment columns) without reading every value character by
   character � inspired by Launchpad's per-cell match/diff glyphs. */
.tag-flag { width: 13px; height: 13px; flex: 0 0 auto; }
.tag-flag-match   { color: var(--ok); }
.tag-flag-diff    { color: var(--warn); }
.tag-flag-missing { color: var(--error); }

/* Status column: narrower than a normal column (it holds one small icon + one short
   word). The icon sits inside the ordinary boxed cell (one border, from the cell, same
   as every other column). */
.col-status { width: 118px; }

/* Status fill: the verdict IS the cell now, not a pill floating inside it — the cell's
   own background and text colour carry the status, and the text itself is the status
   word (Match / Different / Staged / …), with no separate label and no leading dot (the
   dot is a .pill affordance; these cells are not .pill). Colours follow the app's own
   theme accents rather than literal traffic-light green/amber/red: Match uses the brand
   accent (the same teal/blue as every primary button and the sidebar), Different uses
   the same warm tone as the Reset button (var(--warn) — Reset already uses this colour
   to mean "this will change something"), and a true problem state (Missing / Not in
   target) keeps var(--error), since that is a fault, not a normal outcome. */
table.data.cell-boxed .status-fill.cell-val {
    font-weight: 700;
    border-color: transparent;
    justify-content: center;
}
table.data.cell-boxed .status-fill-match  { background: var(--accent); color: #ffffff; }
/* "Different" is a normal, expected outcome, not a fault — so it should not shout in
   bright amber. Mix the warn tone toward the app's teal accent so it sits in the same
   colour family as the rest of the UI while still reading as "not the same". */
table.data.cell-boxed .status-fill-diff   { background: color-mix(in srgb, var(--warn) 55%, var(--accent) 45%); color: #ffffff; }
table.data.cell-boxed .status-fill-error  { background: color-mix(in srgb, var(--error) 78%, var(--accent) 22%); color: #ffffff; }
/* Staged overrides whichever status colour the cell would otherwise carry — once a row
   is queued for Apply all, that is the only fact the cell needs to state (point 5: no
   more "Different" + "Staged" side by side). Reuses the accent tint Stage itself uses,
   so "staged" reads as one consistent colour everywhere on the row. */
table.data.cell-boxed .status-fill-staged { background: var(--accent); color: #ffffff; }

/* Status icons: small SVGs displayed inline with the status label in each status cell.
   Icons are colored via currentColor to inherit the cell's text color (which matches
   the status: accent for Match/Same, warn for Different, error for Missing/Not in target).
   Icons are sized to match the text height and given a small gap to the label. */
.status-icon {
    width: 14px;
    height: 14px;
    flex: 0 0 auto;
    margin-right: 6px;
}

/* Multi-select combo footer (Clear all / Done), inside the OPEN panel — Compare's
   Environments picker and any future multi-select share this. */
.fcombo-footer {
    display: flex;
    gap: 8px;
    padding: 6px 3px 2px;
    margin-top: 4px;
    border-top: 1px solid var(--border);
}
.fcombo-footer .btn-sm,
.fcombo-footer .btn-primary { flex: 1 1 50%; margin: 0; height: 28px; }


/* ═══════════════════════════════════════════════════════════════════════════
   PROMOTE — Stage/Unstage arrow, staged-row highlight, no double borders
   ═══════════════════════════════════════════════════════════════════════════ */

/* Fixed column widths: every Promote column carries an explicit width so the table's
   layout never shifts as data loads or a filter narrows the row set (no auto-sizing to
   content). table-layout:fixed makes these widths authoritative instead of advisory —
   without it the browser still redistributes space by content length. The drag-resize
   grip (wireColumnResize, shared with every other table) still lets a user override
   these by hand; only the automatic, content-driven resizing is removed. */
table.promote-table { table-layout: fixed; }
/* Serial-number column needs an explicit width under table-layout:fixed — the generic
   `width: 1%` collapses to nothing here (every other column has a fixed width, so fixed
   layout gives the 1% column its literal 1%, which rounds to invisible). A small fixed
   width keeps the "#" and its numbers readable. */
.promote-table .col-workload { width: 150px; }
.promote-table .col-repo     { width: 300px; }
.promote-table .col-envtag   { width: 130px; }
/* Bare minimum to fit the 30px icon button plus the cell's own small horizontal
   padding — no room to spare, since the column holds nothing else. */
.promote-table .col-stage    { width: 42px; padding-left: 4px; padding-right: 4px; }
/* Status holds one short word plus a small icon; it does not need to be wide. */
.promote-table .col-status   { width: 92px; }
/* Actions holds a single 76px "Apply" button and nothing else — size the column to just
   that button plus the boxed table's own last-column right padding (12px), with no extra
   slack. */
.promote-table .col-actions  { width: 76px; padding-left: 2px; padding-right: 8px; }
/* Serial column: explicit width so it survives table-layout:fixed (see note above). */
.promote-table td.sl-col, .promote-table th.sl-col { width: 34px; }

/* Source/target tag cells: plain mono text, no pill — same fix as Compare and
   Deployment. A pill's own border nested inside the boxed cell's border drew two
   borders for one value; here there is no pill at all in those columns. */

/* Pending target tag: while a row is staged, the target cell previews the source tag it
   is about to receive. Tint it with the accent (same "about to change" language as a
   dirty New Tag field on Deployment and the staged-row highlight) so the preview reads
   as provisional, not yet committed. */
.promote-table td.col-envtag .cell-val.tag-pending {
    border-color: var(--accent);
    background: var(--accent-soft);
    color: var(--accent);
    font-weight: 700;
}

/* Stage column: a small icon button between source and target, matching Launchpad's
   arrow. No cell box (a button supplies its own shape); narrow, centered. */
.col-stage { text-align: center; }
.stage-btn {
    width: 30px;
    height: 26px;
    display: inline-flex;
    align-items: center;
    justify-content: center;
    color: var(--text-dim);
}
.stage-arrow { width: 15px; height: 15px; }

/* Both icons (arrow + revert) are always in the button; only one shows at a time. Idle
   shows the arrow (→ "stage this"); staged shows the revert glyph (↺ "unstage this") —
   the same convention Launchpad uses for its own promote/revert toggle. */
.stage-btn .icon-revert { display: none; }
.stage-btn.active .icon-arrow { display: none; }
.stage-btn.active .icon-revert { display: inline-flex; }
.stage-btn.active {
    background: var(--accent);
    border-color: var(--accent);
    color: #fff;
}
.stage-btn:disabled { opacity: 0.35; cursor: not-allowed; }

/* A staged row reads as "pending", the same visual language as a dirty New Tag field
   on Deployment (accent tint), so "about to change" looks the same everywhere. The tint
   must be set on .cell-val, not the <td>: cell-boxed cells paint their own background
   (var(--bg-inset)) on top of the <td>, so a <td>-level background is invisible under a
   boxed cell — it only ever showed on the two unboxed columns (Stage, Actions), which is
   exactly backwards from the intent, so those are excluded here instead.

   The Status cell is also excluded: while staged it carries its own solid accent fill
   (.status-fill-staged) with white "Staged" text. Painting the pale accent-soft tint
   over it left near-white text on a near-white background — unreadable. Leaving it out
   keeps the solid, high-contrast staged chip. */
tr.row-staged > td:not(.col-stage):not(.col-actions):not(.col-status) .cell-val {
    background: var(--accent-soft);
}


/* ═══════════════════════════════════════════════════════════════════════════
   HOME PAGE
   ═══════════════════════════════════════════════════════════════════════════ */

.ferry-home { max-width: 100%; min-width: 0; padding: 0 2px 12px; }

.home-welcome { margin-bottom: 10px; }
.home-greeting { font-size: 22px; font-weight: 800; margin: 0 0 4px; letter-spacing: -0.3px; }
.home-lede { font-size: 13.5px; color: var(--text-dim); margin: 0; line-height: 1.45; }
.home-lede strong { font-family: var(--mono); color: var(--text); }

/* The standalone animated SVG keeps its native ratio but is constrained by both the
   panel width and viewport height. Home therefore has no nested scroll region and the
   four quick actions remain visible below it on a normal desktop viewport. */
.home-flow-stage {
    display: flex;
    justify-content: center;
    min-width: 0;
    margin: 0 0 12px;
    padding: 0;
    overflow: visible;
    border: 0;
    background: transparent;
    box-shadow: none;
}
.home-flow-image {
    display: block;
    width: auto;
    max-width: 100%;
    height: auto;
    /* Enlarged ~60% from the original cap (min(48vh, 440px)) so the workflow diagram
       reads clearly on the Home page. Still responsive: max-width:100% keeps it inside
       the panel on narrow viewports, and the vh term keeps it from dominating short ones. */
    max-height: min(77vh, 704px);
}



/* Retain the established .home-cards/.home-card surface language, but arrange each
   shortcut as a compact icon-and-copy button. The button delegates to its sidebar item,
   which remains the sole owner of the HTMX route and control swap. */
.ferry-home .home-cards { gap: 10px; margin: 0; }
.ferry-home .home-card {
    display: grid;
    grid-template-columns: 32px minmax(0, 1fr);
    grid-template-rows: auto auto;
    column-gap: 10px;
    row-gap: 2px;
    min-width: 0;
    padding: 10px 12px;
}
.ferry-home .home-card-icon {
    grid-column: 1;
    grid-row: 1 / 3;
    width: 32px;
    height: 32px;
    align-self: center;
}
.ferry-home .home-card-title,
.ferry-home .home-card-sub { grid-column: 2; min-width: 0; }
.ferry-home .home-card-sub { line-height: 1.35; }

/* Activity is the Ops_Repo's own `git log`, read fresh on each branch-scoped load. */
.activity-list { list-style: none; margin: 0 0 8px; padding: 0; }
.activity-item {
    display: flex;
    align-items: baseline;
    gap: 10px;
    padding: 9px 4px;
    border-bottom: 1px solid var(--border);
    font-size: 12.5px;
}
.activity-item:last-child { border-bottom: none; }
.activity-hash { font-family: var(--mono); color: var(--text-faint); font-size: 11px; flex: 0 0 auto; }
.activity-message { flex: 1 1 auto; font-family: var(--mono); color: var(--text); min-width: 0;
    overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.activity-meta { flex: 0 0 auto; color: var(--text-faint); font-size: 11.5px; }

/* System page: the property-name column is a fixed, readable width so the two-column
   key/value tables line up consistently across the Instance/Repository/Inventory/Session
   sections. */
.col-syskey { width: 220px; }
