:root {
    /* Semantic severity/role colors — NAMES preserved exactly, values unchanged. */
    --blocking: #c62828;
    --warning: #ef6c00;
    --info: #1565c0;
    --ready: #2e7d32;
    --err: #6d4c41;
    --border: #dbe3e6;
    /* Stronger border for cards/inputs that need more separation than --border (Task 1). */
    --border-strong: #c3cfd3;
    --muted: #757575;

    /* Paired light-background tints for the semantic colors above (pills/badges/state chips). */
    --blocking-tint: #ffebee;
    --warning-tint: #fff3e0;
    --info-tint: #e3f2fd;
    --ready-tint: #e8f5e9;
    --err-tint: #efebe9;

    /* Non-semantic categorical colors (scenario tag, SID pill) + their tints. */
    --scenario: #4527a0;
    --scenario-tint: #ede7f6;
    --sid: #37474f;
    --sid-tint: #eceff1;

    /* Neutral grey pair used on the dark topbar/nav — distinct from --muted, which is
       calibrated for light backgrounds. --neutral-tint doubles as the lighter chrome border. */
    --neutral: #90a4ae;
    --neutral-tint: #cfd8dc;

    /* Sign-in accent: a teal that reads "crossing water", distinct from the semantic --info blue.
       Also used by the fleet monitor's "in progress" state (see comment below). */
    --accent: #0d9488;
    --accent-dark: #0b7d72;
    --accent-tint: #e6f6f4;
    /* Old names kept resolving for the .auth-* login CSS; retired in Wave C. */
    --auth-accent: var(--accent);
    --auth-accent-dark: var(--accent-dark);

    /* Surfaces + text — cool-shifted neutrals (Task 1, 2026-09-10 modernization pass). */
    --bg: #f4f7f8;
    --surface: #ffffff;
    --surface-alt: #eef2f4;
    /* One step darker than --surface-alt, for nested/raised surfaces (Task 1). */
    --surface-2: #e7edef;
    --text: #152229;
    --text-muted: #5a6a70;
    /* Faintest legible text tier — placeholders, disabled labels, tertiary metadata (Task 1). */
    --text-faint: #8b989d;
    --text-on-accent: #ffffff;

    /* Sidebar/nav chrome tokens (Task 1). --topbar-bg/-fg are kept as aliases: app.css's own
       .topbar/.topnav/.theme-toggle rules and NavMenu.razor.css/MainLayout.razor.css still read
       them directly until Task 2's layout rework migrates those call sites to --nav-*. */
    --nav-bg: #0e1a20;
    --nav-fg: #c7d3d7;
    --nav-fg-muted: #7d8f96;
    --nav-active: #12262d;
    --nav-line: #1c2e35;
    --topbar-bg: var(--nav-bg);
    --topbar-fg: var(--nav-fg);

    /* Scale */
    --space-1: 4px;
    --space-2: 8px;
    --space-3: 12px;
    --space-4: 16px;
    --space-5: 24px;
    --space-6: 32px;
    --space-7: 48px;
    --radius-sm: 4px;
    --radius-md: 8px;
    --radius-lg: 14px;
    --radius-full: 999px;
    --shadow-1: 0 1px 2px rgba(16, 32, 38, .06), 0 1px 3px rgba(16, 32, 38, .08);
    --shadow-2: 0 4px 12px rgba(16, 32, 38, .10), 0 2px 6px rgba(16, 32, 38, .06);
    --shadow-3: 0 12px 32px rgba(16, 32, 38, .16), 0 4px 10px rgba(16, 32, 38, .08);
    --fs-xs: .75rem;
    --fs-sm: .875rem;
    --fs-md: 1rem;
    --fs-lg: 1.25rem;
    --fs-xl: 1.5rem;
    --fs-2xl: 1.75rem;
    --fw-regular: 400;
    --fw-medium: 600;
    --fw-bold: 700;
    --focus-ring: 0 0 0 2px var(--bg), 0 0 0 4px var(--accent);
    /* Web fonts (Task 1) — linked via Google Fonts in App.razor's head; real fallback stacks so
       an offline/blocked load still renders legibly. */
    --font-ui: "Manrope", "Segoe UI", system-ui, sans-serif;
    --font-mono: "JetBrains Mono", Consolas, monospace;
}

/* ============================================================================
   Dark theme — Task A2. Two blocks, ONE token set: system-dark (no explicit
   choice, prefers-color-scheme) and explicit-dark (data-theme="dark" set by
   the toggle). `:not([data-theme="light"])` on the media-query block means an
   explicit LIGHT choice always beats system dark, matching the toggle's
   3-state contract (system -> light -> dark -> system).
   KEEP THESE TWO BLOCKS IDENTICAL — same token names, same values. A system-
   dark user and an explicitly-dark user must see the same theme. (Sync
   partner: the `:root[data-theme="dark"]` block below.)

   Values are chosen against the WCAG AA formula (4.5:1 normal text), not a
   naive channel-invert of the light values:
   - --blocking/--warning/--info/--ready/--err are lightened ("dark-safe"
     variants) so they read as TEXT on their own ~15%-opacity tint over
     --surface (pill.* — this is the binding constraint, ~0.25 relative
     luminance minimum against a near-black tint).
   - --text-on-accent flips from white to near-black for dark mode. The same
     lightened hues are also used as SOLID badge/verdict-card fill
     (.badge, .verdict-card) — white text on a light-enough-to-read-as-text
     hue fails AA (e.g. white/#ef5350 ~3.5:1), but black text on that same
     fill clears AA with room to spare. One token, two consumers, opposite
     luminance needs — the fix is the on-color, not the hue.
   - --muted is added even though the brief's suggested list only named
     --text-muted: `.muted`/table-head/fleet-card-label read --muted
     directly, and the light value (#757575) is ~3.5:1 on the dark surface —
     a real AA failure if left un-redefined, not a cosmetic miss.
   - --scenario/--sid + their tints are added (not in the brief's suggested
     list) so the scenario tag and SID pill don't stay stuck at their light
     values — leaving them un-themed would put a bright light-lavender/grey
     pill directly on the dark surface, the exact "look" this task exists to
     avoid, even though it isn't strictly an AA violation.
   - --accent/--accent-dark are lightened slightly (teal reads fine dark, but
     --accent-dark also serves as pill text via --auth-accent-dark on
     .state-pill.inprogress, which has the same tint-legibility floor as the
     semantic hues above).
   - --border/--neutral/--neutral-tint are decorative (borders only, 3:1 non-
     text requirement) and comfortably clear it unchanged, so --neutral* is
     intentionally left un-redefined here — it still resolves to its :root
     value, which is well clear of AA against the dark --nav-bg (#070f12 is
     darker than the light theme's --nav-bg #0e1a20, so contrast only
     improves; --topbar-bg aliases --nav-bg as of Task 1).
   ============================================================================ */
@media (prefers-color-scheme: dark) {
    :root:not([data-theme="light"]) {
        --blocking: #ef9a9a;
        --warning: #ffa726;
        --info: #42a5f5;
        --ready: #66bb6a;
        --err: #b09a90;
        --border: #233741;
        --border-strong: #31505c;
        --muted: #9fb0b8;

        --blocking-tint: rgba(229, 115, 115, .15);
        --warning-tint: rgba(255, 167, 38, .15);
        --info-tint: rgba(66, 165, 245, .15);
        --ready-tint: rgba(102, 187, 106, .15);
        --err-tint: rgba(161, 136, 127, .15);

        --scenario: #b39ddb;
        --scenario-tint: rgba(179, 157, 219, .15);
        --sid: #b0bec5;
        --sid-tint: rgba(176, 190, 197, .15);

        /* Dark neutral: light --neutral/--neutral-tint (#90a4ae/#cfd8dc, for nav greys) would render a
           light box in dark mode when used as a StatusBadge tint — mirror --sid (AA-verified) here. */
        --neutral: #b0bec5;
        --neutral-tint: rgba(176, 190, 197, .15);

        --accent: #2dd4bf;
        --accent-dark: #14b8a6;
        --accent-tint: rgba(45, 212, 191, .15);

        --bg: #0b1418;
        --surface: #111e24;
        --surface-alt: #17272f;
        --surface-2: #1e313a;
        --text: #e7eef1;
        --text-muted: #93a4ab;
        --text-faint: #66787f;
        --text-on-accent: #04120f;

        --nav-bg: #070f12;
        --nav-fg: #b7c6cb;
        --nav-fg-muted: #67787f;
        --nav-active: #0f2027;
        --nav-line: #152229;
        --topbar-bg: var(--nav-bg);
        --topbar-fg: var(--nav-fg);

        --shadow-1: 0 1px 2px rgba(0, 0, 0, .4);
        --shadow-2: 0 4px 14px rgba(0, 0, 0, .5);
        --shadow-3: 0 14px 36px rgba(0, 0, 0, .6);
    }
}

/* Explicit-dark twin of the media-query block above — MUST stay in sync (same
   token names, same values). This is what the toggle sets via
   data-theme="dark", independent of the OS setting. */
:root[data-theme="dark"] {
    --blocking: #ef9a9a;
    --warning: #ffa726;
    --info: #42a5f5;
    --ready: #66bb6a;
    --err: #b09a90;
    --border: #233741;
    --border-strong: #31505c;
    --muted: #9fb0b8;

    --blocking-tint: rgba(229, 115, 115, .15);
    --warning-tint: rgba(255, 167, 38, .15);
    --info-tint: rgba(66, 165, 245, .15);
    --ready-tint: rgba(102, 187, 106, .15);
    --err-tint: rgba(161, 136, 127, .15);

    --scenario: #b39ddb;
    --scenario-tint: rgba(179, 157, 219, .15);
    --sid: #b0bec5;
    --sid-tint: rgba(176, 190, 197, .15);

    /* Dark neutral: light --neutral/--neutral-tint (#90a4ae/#cfd8dc, for nav greys) would render a
       light box in dark mode when used as a StatusBadge tint — mirror --sid (AA-verified) here. */
    --neutral: #b0bec5;
    --neutral-tint: rgba(176, 190, 197, .15);

    --accent: #2dd4bf;
    --accent-dark: #14b8a6;
    --accent-tint: rgba(45, 212, 191, .15);

    --bg: #0b1418;
    --surface: #111e24;
    --surface-alt: #17272f;
    --surface-2: #1e313a;
    --text: #e7eef1;
    --text-muted: #93a4ab;
    --text-faint: #66787f;
    --text-on-accent: #04120f;

    --nav-bg: #070f12;
    --nav-fg: #b7c6cb;
    --nav-fg-muted: #67787f;
    --nav-active: #0f2027;
    --nav-line: #152229;
    --topbar-bg: var(--nav-bg);
    --topbar-fg: var(--nav-fg);

    --shadow-1: 0 1px 2px rgba(0, 0, 0, .4);
    --shadow-2: 0 4px 14px rgba(0, 0, 0, .5);
    --shadow-3: 0 14px 36px rgba(0, 0, 0, .6);
}

* { box-sizing: border-box; }

/* Standard offscreen-clip pattern (Task C1) — content stays in the accessibility tree and
   readable by screen readers, but isn't visually rendered or reflowed into layout. Used by
   the #ferryman-live polite live region in MainLayout. */
.visually-hidden {
    position: absolute;
    width: 1px;
    height: 1px;
    padding: 0;
    margin: -1px;
    overflow: hidden;
    clip: rect(0, 0, 0, 0);
    white-space: nowrap;
    border: 0;
}

body {
    margin: 0;
    font-family: var(--font-ui);
    letter-spacing: -.006em;
    -webkit-font-smoothing: antialiased;
    color: var(--text);
    background: var(--bg);
}

h1:focus { outline: none; }
/* Same reason as h1: the device page focuses its Recovery section only to bring a #recovery link back to it
   (DeviceDetailView.ShouldFocusRecovery). It is not a control, and a ring round the whole panel reads as one. */
.recovery-section:focus { outline: none; }

/* ============================================================================
   Global controls (Task 2, 2026-09-10 modernization pass) — the button/input/
   field/label vocabulary every later component and page task adopts. Values
   ported verbatim from the mockup's .btn / .inp / .field / .lbl / .hint rules, onto
   this app's existing tokens (--focus-ring, not the mockup's --ring, which is
   an unstyled name here). R-P ruling: no standalone `.pill` — StatusBadge
   (Task 3) stays the one pill idiom, so it is deliberately not added here.

   Focus rings: the mockup's .inp/.field use a plain :focus with an
   accent-tint box-shadow. The app already has one focus-ring convention
   (see .confirm-input:focus-visible, .theme-toggle:focus-visible above) and
   this task's constraint is that every interactive control's :focus-visible
   uses box-shadow:var(--focus-ring) — so that ring is kept on :focus-visible
   (keyboard/programmatic focus), and the accent border-color-only change is
   kept on plain :focus as the mouse-click affordance, rather than competing
   rings on the same control.
   ============================================================================ */
.btn {
    display: inline-flex;
    align-items: center;
    gap: 8px;
    font-family: inherit;
    font-size: var(--fs-sm);
    font-weight: 700;
    padding: 9px 15px;
    border-radius: 9px;
    border: 1px solid transparent;
    cursor: pointer;
    transition: transform .08s, background .13s, box-shadow .13s, border-color .13s;
    line-height: 1;
    white-space: nowrap;
}
.btn:active { transform: translateY(1px); }
.btn:focus-visible { outline: none; box-shadow: var(--focus-ring); }
.btn svg { width: 16px; height: 16px; }
/* accent glow derived from --accent so it tracks the theme (was a baked-in light-teal literal). */
.btn-primary { background: var(--accent); color: var(--text-on-accent); box-shadow: 0 1px 2px color-mix(in srgb, var(--accent) 30%, transparent); }
.btn-primary:hover { background: var(--accent-dark); box-shadow: 0 4px 12px color-mix(in srgb, var(--accent) 32%, transparent); }
.btn-secondary { background: var(--surface); color: var(--text); border-color: var(--border-strong); }
.btn-secondary:hover { background: var(--surface-alt); border-color: var(--text-faint); }
.btn-ghost { background: none; color: var(--text-muted); }
.btn-ghost:hover { background: var(--surface-alt); color: var(--text); }
.btn-danger { background: var(--blocking); color: #fff; }
.btn-danger:hover { filter: brightness(.93); }
.btn-sm { padding: 6px 11px; font-size: var(--fs-xs); }
/* Disabled buttons must LOOK disabled — previously a disabled .btn kept full color + pointer
   cursor and read as clickable (e.g. the wizard's Next before a valid Project ID). */
.btn:disabled, .btn[disabled] { opacity: .5; cursor: not-allowed; box-shadow: none; }
.btn:disabled:hover { filter: none; }
.btn:disabled:active { transform: none; }

/* ---- list pager (Task 7 pages tag .pager/.pgbtn; CSS lives here globally) ---- */
.pager { display: flex; align-items: center; gap: 6px; font-size: var(--fs-sm); color: var(--text-muted); font-weight: 600; }
/* inline-flex (not inline-grid): these pager buttons also carry .btn and can hold an icon + a
   "Prev"/"Next" label — grid stacked the glyph over the label; flex keeps them on one line. */
.pgbtn { min-width: 32px; height: 32px; display: inline-flex; align-items: center; justify-content: center; gap: 6px; padding: 0 10px; border: 1px solid var(--border-strong); background: var(--surface); border-radius: 8px; cursor: pointer; color: var(--text); font-family: inherit; font-size: var(--fs-sm); font-weight: 600; line-height: 1; white-space: nowrap; transition: background .13s, border-color .13s; }
.pgbtn svg { width: 15px; height: 15px; }
.pgbtn:hover:not(:disabled) { background: var(--surface-alt); border-color: var(--text-faint); }
.pgbtn:disabled { opacity: .45; cursor: not-allowed; }
.pgbtn:focus-visible { outline: none; box-shadow: var(--focus-ring); }

/* ---- form controls (styled, not browser-default) ---- */
.lbl { display: block; font-size: var(--fs-sm); font-weight: 700; margin-bottom: 7px; }
.hint { font-size: var(--fs-xs); color: var(--text-muted); margin-top: 6px; }
.inp {
    width: 100%;
    height: 42px;
    border: 1px solid var(--border-strong);
    border-radius: 10px;
    background: var(--surface);
    color: var(--text);
    padding: 0 14px;
    font-family: inherit;
    font-size: var(--fs-md);
    transition: border-color .13s, box-shadow .13s;
}
.inp:focus { outline: none; border-color: var(--accent); }
.inp:focus-visible { outline: none; box-shadow: var(--focus-ring); }

.field {
    display: flex;
    align-items: center;
    gap: 8px;
    background: var(--surface);
    border: 1px solid var(--border-strong);
    border-radius: 9px;
    padding: 0 12px;
    height: 38px;
    transition: border-color .13s, box-shadow .13s;
}
.field:focus-within { border-color: var(--accent); }
.field svg { width: 16px; height: 16px; color: var(--text-faint); }
.field input, .field select {
    border: 0;
    background: none;
    outline: none;
    font-family: inherit;
    font-size: var(--fs-sm);
    color: var(--text);
    height: 100%;
    min-width: 120px;
}
.field select { cursor: pointer; padding-right: 4px; }
.field input:focus-visible, .field select:focus-visible { box-shadow: var(--focus-ring); }

/* ============================================================================
   App shell — left-sidebar console (Task 5, 2026-09-10 modernization pass).
   Replaces the former .topbar/.topnav/.theme-toggle-pill chrome. The sidebar is
   authored in MainLayout.razor, but the nav items are <NavLink> (which renders an
   <a>), and a component's rendered element does NOT inherit MainLayout's CSS-
   isolation scope — so .nav-* styling, and the mobile reflow that has to reach
   .nav-item, MUST live here in global CSS. The one bit that genuinely needs
   ::deep (the ThemeToggle island's foot-row look) stays in MainLayout.razor.css.
   ============================================================================ */
.app { display: grid; grid-template-columns: 248px 1fr; min-height: 100vh; }

.sidebar {
    background: var(--nav-bg);
    color: var(--nav-fg);
    display: flex;
    flex-direction: column;
    padding: var(--space-4) var(--space-3);
    gap: var(--space-2);
    position: sticky;
    top: 0;
    height: 100vh;
}

.brand {
    display: flex;
    align-items: center;
    gap: 10px;
    padding: 6px 8px 14px;
    border-bottom: 1px solid var(--nav-line);
    margin-bottom: 6px;
    text-decoration: none;
}
.brand-text { display: flex; flex-direction: column; gap: 2px; min-width: 0; }
/* The mark is dark-on-light artwork and the sidebar is dark, so it sits on its own
   light disc rather than disappearing into the chrome (same reason as the old topbar). */
.brand-mark {
    width: 32px;
    height: 32px;
    border-radius: 9px;
    background: #f7f9fa;
    display: grid;
    place-items: center;
    flex: 0 0 auto;
}
.brand-mark img { width: 26px; height: 26px; display: block; }
.brand-name { font-weight: 800; font-size: 1.05rem; letter-spacing: -.02em; color: #fff; line-height: 1.1; }
.brand-sub { font-size: .62rem; letter-spacing: .14em; text-transform: uppercase; color: var(--nav-fg-muted); font-weight: 600; }

/* The scrolling nav column; the foot below it is pinned to the bottom of the 100vh sidebar. */
.nav-scroll { flex: 1 1 auto; min-height: 0; overflow-y: auto; display: flex; flex-direction: column; gap: var(--space-2); }
.nav-group { margin-top: var(--space-3); display: flex; flex-direction: column; gap: 2px; }
.nav-group:first-child { margin-top: 0; }
/* Section headers are muted, non-actionable labels, so --nav-fg-muted is acceptable here
   (unlike the actionable nav items / foot rows, which use --nav-fg for AA). */
.nav-label { font-size: .62rem; letter-spacing: .14em; text-transform: uppercase; color: var(--nav-fg-muted); font-weight: 700; padding: 0 10px 6px; }

.nav-item {
    display: flex;
    align-items: center;
    gap: 11px;
    width: 100%;
    text-align: left;
    background: none;
    border: 0;
    color: var(--nav-fg);
    text-decoration: none;
    padding: 9px 10px;
    border-radius: 9px;
    font-size: .875rem;
    font-weight: 600;
    cursor: pointer;
    transition: background .13s, color .13s;
}
.nav-item:hover { background: var(--nav-active); color: #fff; }
/* NavLink adds `.active` (+ aria-current="page") on the matched route. */
.nav-item.active { background: var(--nav-active); color: #fff; }
.nav-item svg { width: 17px; height: 17px; flex: 0 0 auto; color: var(--nav-fg-muted); }
.nav-item:hover svg, .nav-item.active svg { color: var(--accent); }
.nav-item:focus-visible { outline: none; box-shadow: var(--focus-ring); }

.sidebar-foot { padding-top: var(--space-3); border-top: 1px solid var(--nav-line); display: flex; flex-direction: column; gap: 4px; }
.org-chip { display: flex; align-items: center; gap: 9px; padding: 8px 10px; border-radius: 9px; background: var(--nav-active); }
.org-dot { width: 8px; height: 8px; border-radius: 50%; background: var(--accent); flex: 0 0 auto; }
.org-meta { flex: 1; min-width: 0; }
.org-name { font-weight: 700; font-size: .82rem; color: #fff; overflow-wrap: anywhere; }
.org-role { font-size: .66rem; color: var(--nav-fg-muted); letter-spacing: .04em; }

/* Foot rows — operator id, Sign out, Sign in. Real <button>/<a> reset to a flat row.
   Actionable, so --nav-fg (not --nav-fg-muted) for AA in both themes. */
.foot-row {
    display: flex;
    align-items: center;
    gap: 8px;
    width: 100%;
    padding: 7px 10px;
    color: var(--nav-fg);
    font-family: inherit;
    font-size: .8rem;
    font-weight: 600;
    border: 0;
    background: none;
    border-radius: 8px;
    cursor: pointer;
    text-align: left;
    text-decoration: none;
    line-height: 1.2;
}
.foot-row:hover { background: var(--nav-active); color: #fff; }
.foot-row svg { width: 16px; height: 16px; flex: 0 0 auto; }
.foot-row:focus-visible { outline: none; box-shadow: var(--focus-ring); }
/* The operator-id row is informational, not a control — it doesn't hover/click. */
.foot-id { cursor: default; overflow-wrap: anywhere; }
.foot-id:hover { background: none; color: var(--nav-fg); }
.foot-form { margin: 0; }

/* Platform-admin badge inside the org chip. --nav-fg text on the --nav-active chip clears AA in
   both themes; the --nav-fg-muted outline is decorative (3:1 non-text). */
.op-badge {
    display: inline-flex;
    align-items: center;
    padding: 0.1rem var(--space-2);
    border: 1px solid var(--nav-fg-muted);
    border-radius: 999px;
    color: var(--nav-fg);
    font-size: var(--fs-xs);
    font-weight: var(--fw-medium);
    text-transform: uppercase;
    letter-spacing: 0.04em;
    flex: 0 0 auto;
}

/* ---- Page-header band (Task 5). Authored INSIDE each page (@Body), so its CSS lives here in
   global app.css — NOT MainLayout.razor.css, whose isolation scope can't reach slotted @Body
   content. Pages adopt this exact structure (pages are migrated in later tasks):

     <div class="pagehead">
       <div>
         <div class="crumb">Group › Page</div>
         <h1>Title</h1>
         <div class="page-sub">subtitle</div>
       </div>
       <div class="head-actions">…buttons…</div>
     </div>
*/
.pagehead {
    display: flex;
    align-items: flex-end;
    justify-content: space-between;
    gap: var(--space-4);
    margin-bottom: var(--space-5);
    flex-wrap: wrap;
}
.pagehead h1 { font-size: var(--fs-2xl); font-weight: 800; letter-spacing: -.02em; }
.crumb { font-size: var(--fs-xs); color: var(--text-muted); font-weight: 600; letter-spacing: .02em; margin-bottom: 6px; display: flex; gap: 7px; align-items: center; }
.page-sub { color: var(--text-muted); font-size: var(--fs-sm); margin-top: 5px; }
.head-actions { display: flex; gap: 10px; flex-wrap: wrap; }

/* Sidebar → top strip below 900px. Everything wraps (no horizontal body scroll); section labels
   drop out and the foot reflows inline so Sign out / Theme stay reachable. Lives here (not the
   scoped file) because it has to restyle .nav-item, which NavLink renders unscoped. */
@media (max-width: 900px) {
    .app { grid-template-columns: 1fr; }
    .sidebar { position: static; height: auto; flex-direction: row; flex-wrap: wrap; align-items: center; gap: var(--space-2) var(--space-3); }
    .brand { border-bottom: 0; margin-bottom: 0; padding: 6px 8px; }
    .nav-scroll { flex: 1 1 auto; flex-direction: row; flex-wrap: wrap; align-items: center; gap: 4px 6px; overflow: visible; }
    .nav-group { flex-direction: row; flex-wrap: wrap; align-items: center; margin-top: 0; gap: 4px; }
    .nav-label { display: none; }
    .nav-item { width: auto; }
    .sidebar-foot { padding-top: 0; border-top: 0; flex-direction: row; flex-wrap: wrap; align-items: center; width: 100%; gap: 4px 8px; }
    .foot-row { width: auto; }
    .org-chip { padding: 6px 10px; }
    .content { padding: var(--space-4); }
}

/* Operator write actions + the login form. */
.op-actions { margin-top: 1.5rem; padding: 1rem 1.25rem; border: 1px solid var(--border); border-radius: 6px; background: var(--bg); max-width: 680px; }
.op-actions h2 { margin-top: 0; font-size: 1.05rem; }
.op-actions label { display: block; margin: 0.5rem 0; max-width: 640px; }
/* Task 2 redirect (brief step 3): these were bare-default <input>/<textarea>/<button> —
   give .op-actions its controls' look from the global .inp/.btn-secondary vocabulary above
   so every op-actions block across ~20 pages renders styled today, ahead of the page tasks
   that will put explicit .inp/.btn-* classes directly on this markup (at which point this
   redirect becomes redundant and removable). `.login label/input/button` and the standalone
   `.login{}` rule that used to share these selectors are dropped outright, not redirected —
   `class="login"` has no markup consumer; Login.razor renders via `.auth-*` (see the auth-*
   block further down this file), so those three selectors were dead CSS. */
.op-actions input, .op-actions select {
    width: 100%;
    box-sizing: border-box;
    padding: 0 14px;
    height: 42px;
    border: 1px solid var(--border-strong);
    border-radius: 10px;
    background: var(--surface);
    color: var(--text);
    font-family: inherit;
    font-size: var(--fs-md);
    transition: border-color .13s, box-shadow .13s;
}
/* select was previously unstyled (the input rule above didn't include it), so every
   op-actions <select> across the form pages rendered as a tiny browser-default control. */
.op-actions select { cursor: pointer; }
/* A checkbox must NOT inherit the full-width 42px text-field box above. */
.op-actions input[type="checkbox"] { width: auto; height: auto; }
.op-actions input:focus, .op-actions select:focus { outline: none; border-color: var(--accent); }
.op-actions input:focus-visible, .op-actions select:focus-visible { box-shadow: var(--focus-ring); }
.op-actions button {
    margin-top: 0.6rem;
    display: inline-flex;
    align-items: center;
    gap: 8px;
    font-family: inherit;
    font-size: var(--fs-sm);
    font-weight: 700;
    padding: 9px 15px;
    border-radius: 9px;
    border: 1px solid var(--border-strong);
    background: var(--surface);
    color: var(--text);
    cursor: pointer;
    transition: transform .08s, background .13s, box-shadow .13s, border-color .13s;
}
.op-actions button:hover { background: var(--surface-alt); border-color: var(--text-faint); }
.op-actions button:active { transform: translateY(1px); }
.op-actions button:focus-visible { outline: none; box-shadow: var(--focus-ring); }
/* The bare `.op-actions button` redirect above out-specifies the global `.btn-*` variants
   (0,1,1 vs 0,1,0), so a page marking a primary/danger/ghost action inside an op-actions
   block needs these equal-or-higher-specificity overrides to actually get the accent look. */
.op-actions button.btn-primary { background: var(--accent); color: var(--text-on-accent); border-color: transparent; }
.op-actions button.btn-primary:hover { background: var(--accent-dark); border-color: transparent; }
.op-actions button.btn-danger { background: var(--blocking); color: #fff; border-color: transparent; }
.op-actions button.btn-danger:hover { filter: brightness(.93); background: var(--blocking); }
.op-actions button.btn-ghost { background: none; border-color: transparent; color: var(--text-muted); }
.op-actions button.btn-ghost:hover { background: var(--surface-alt); color: var(--text); }
/* Wizard step button row: a plain flex row (NOT a nested .op-actions card, which double-boxed). */
.wiz-actions { display: flex; gap: 10px; margin-top: 20px; flex-wrap: wrap; align-items: center; }
.wiz-actions .push { margin-right: auto; }

/* Main content column of the .app grid (Task 5). Pages author their own .pagehead band
   (see the markup-pattern comment above) directly inside this <main>. The old max-width/
   auto-centering is dropped: in a sidebar console the content fills its grid column. */
.content { min-width: 0; padding: var(--space-6) var(--space-6) var(--space-7); }

a { color: var(--info); }
.muted { color: var(--muted); }
.mono { font-family: var(--font-mono); font-size: 0.85em; }
.small { font-size: 0.8rem; }

.callout {
    padding: 0.75rem 1rem;
    border: 1px solid var(--border);
    border-left: 4px solid var(--info);
    background: var(--surface);
    border-radius: 4px;
}

.callout.error { border-left-color: var(--blocking); }

/* table.devices removed: the retired Fleet page was its last consumer and now renders via <DataTable>
   (its own .data-table scoped CSS). Dropped in the modernization consistency sweep. */

/* Task C8 dead-CSS sweep: .badge, .pill(.*), and .verdict-card were hand-rolled idioms superseded by
   the <StatusBadge> component (see StatusBadge.razor's own doc comment) — grepped zero remaining
   `class="badge|pill|verdict-card"` markup across the app before removal, including the /waves/new
   "auto · exact" mapping badges the old .pill.ready comment named (NewWave.razor now renders those
   via <StatusBadge Severity="ConfidenceSeverity(...)">). .verdict-row (DeviceDetail's still-live
   wrapper) is unaffected and stays below. */
.verdict-row { display: flex; gap: 1rem; align-items: flex-start; margin: 1rem 0 1.5rem; flex-wrap: wrap; }

.verdict-row .meta { font-size: 0.9rem; line-height: 1.5; }

h2.sev-blocking { color: var(--blocking); }
h2.sev-warning { color: var(--warning); }
h2.sev-info { color: var(--info); }

details.finding {
    background: var(--surface);
    border: 1px solid var(--border);
    border-left-width: 4px;
    border-radius: 4px;
    margin-bottom: 0.5rem;
    padding: 0;
}

details.finding.blocking { border-left-color: var(--blocking); }
details.finding.warning { border-left-color: var(--warning); }
details.finding.info { border-left-color: var(--info); }
details.finding.err { border-left-color: var(--err); }

details.finding summary {
    padding: 0.6rem 0.9rem;
    cursor: pointer;
    font-weight: 600;
}

details.finding .body { padding: 0 0.9rem 0.75rem; }
details.finding .remediation { background: var(--ready-tint); padding: 0.5rem 0.75rem; border-radius: 4px; }

/* Task C8 dead-CSS sweep: table.evidence had zero remaining `class="evidence"` markup — DeviceDetail's
   findings now render their evidence via <DataTable Variant="TableVariant.KeyValue"> instead of a
   hand-rolled table.evidence. */

.rule { font-size: 0.75rem; margin-top: 0.4rem; }

.danger-zone { border-color: var(--blocking); }
.danger-zone .ack { margin: 0.75rem 0; }
.danger-zone .ack p { margin: 0.15rem 0 0.75rem; }
.danger-zone > label { margin-top: 0.75rem; }

.callout.error details { margin-top: 0.4rem; }
.callout.error details summary { cursor: pointer; color: var(--blocking); }
.callout.error details pre { white-space: pre-wrap; word-break: break-word; }

#blazor-error-ui {
    background: lightyellow;
    bottom: 0;
    box-shadow: 0 -1px 2px rgba(0, 0, 0, 0.2);
    display: none;
    left: 0;
    padding: 0.6rem 1.25rem 0.7rem 1.25rem;
    position: fixed;
    width: 100%;
    z-index: 1000;
}

#blazor-error-ui .dismiss { cursor: pointer; position: absolute; right: 0.75rem; top: 0.5rem; }

/* ============================================================================
   Operator sign-in (LoginLayout + Login.razor) — dark, branded, focused.
   Task 6 (2026-09-10 modernization pass): fully tokenized onto the shared
   neutrals/accent system. The earlier era pinned every color here to a
   hardcoded light literal because only the card's *background* had been
   switched to var(--surface) — that half-tokenization was the actual bug
   (typed input text stayed a hardcoded dark hex, so it went invisible
   against the dark theme's dark --surface). The fix is to move every color
   in the block onto tokens together, so light/dark theme move as one unit
   instead of the card racing ahead of its own contents.
   ============================================================================ */
.login-shell {
    min-height: 100vh;
    display: grid;
    place-items: center;
    padding: 2rem 1rem;
    position: relative;
    overflow: hidden;
    /* Deliberately dark-on-dark in both themes — a single-look auth surface that
       doesn't flip with the toggle, same as the nav rail. Gradient stops stay
       literal (they're an intentional ferry-at-dusk backdrop, not a themed
       surface); only the foreground text now tracks --nav-fg, the token this
       app already uses for light text on this same dark-chrome family. */
    background: radial-gradient(125% 90% at 50% -12%, #35474f 0%, #1d272d 46%, #121a1e 100%);
    color: var(--nav-fg);
}

/* Layered translucent waves anchored to the bottom — the ferry crossing, kept faint. */
.login-waves {
    position: absolute;
    left: 0;
    right: 0;
    bottom: 0;
    width: 100%;
    height: 44vh;
    min-height: 220px;
    pointer-events: none;
}

.login-stage {
    position: relative;
    z-index: 1;
    width: 100%;
    display: grid;
    place-items: center;
}

.auth-card {
    width: 100%;
    max-width: 400px;
    background: var(--surface);
    border-radius: var(--radius-lg);
    padding: 2.25rem 2.25rem 1.6rem;
    box-shadow: var(--shadow-3);
}

.auth-brand { text-align: center; margin-bottom: 1.5rem; }

.auth-mark {
    height: 66px;
    width: 66px;
    border-radius: 50%;
    background: var(--surface-alt);
    padding: 3px;
    box-shadow: var(--shadow-1);
}

.auth-wordmark {
    margin-top: .65rem;
    font-size: 1.65rem;
    font-weight: 700;
    letter-spacing: .01em;
    color: var(--text);
}

.auth-tagline {
    margin-top: .15rem;
    font-size: .8rem;
    color: var(--text-muted);
}

.auth-heading {
    text-align: center;
    font-size: .78rem;
    font-weight: 600;
    text-transform: uppercase;
    letter-spacing: .14em;
    color: var(--text-muted);
    margin: 0 0 1.3rem;
}

.auth-form { display: flex; flex-direction: column; gap: .9rem; }
.auth-field { display: flex; flex-direction: column; gap: .35rem; }
.auth-field span { font-size: .8rem; font-weight: 600; color: var(--text-muted); }

/* Dimensions/radius match the shared .inp control (Task 2) rather than duplicating a
   near-identical-but-different input here. */
.auth-field input {
    width: 100%;
    height: 42px;
    padding: 0 14px;
    font-family: inherit;
    font-size: var(--fs-md);
    color: var(--text);
    background: var(--surface);
    border: 1px solid var(--border-strong);
    border-radius: 10px;
    transition: border-color .13s, box-shadow .13s;
}

.auth-field input:focus {
    outline: none;
    border-color: var(--accent);
    box-shadow: 0 0 0 3px var(--accent-tint);
}
.auth-field input:focus-visible { outline: none; box-shadow: var(--focus-ring); }

.auth-submit {
    margin-top: .45rem;
    padding: .75rem 1rem;
    font-size: .98rem;
    font-weight: 600;
    color: var(--text-on-accent);
    background: var(--accent);
    border: none;
    border-radius: 9px;
    cursor: pointer;
    transition: background .15s, transform .03s, box-shadow .13s;
    box-shadow: 0 1px 2px color-mix(in srgb, var(--accent) 30%, transparent);
}

.auth-submit:hover { background: var(--accent-dark); box-shadow: 0 4px 12px color-mix(in srgb, var(--accent) 32%, transparent); }
.auth-submit:active { transform: translateY(1px); }
.auth-submit:focus-visible { outline: none; box-shadow: var(--focus-ring); }

.auth-divider {
    display: flex;
    align-items: center;
    gap: .75rem;
    margin: 1.3rem 0 1rem;
    color: var(--text-muted);
    font-size: .78rem;
}
.auth-divider::before, .auth-divider::after {
    content: "";
    flex: 1;
    height: 1px;
    background: var(--border);
}

.auth-sso {
    display: flex;
    align-items: center;
    justify-content: center;
    gap: .55rem;
    padding: .7rem 1rem;
    font-size: .92rem;
    font-weight: 600;
    color: var(--text);
    background: var(--surface);
    border: 1px solid var(--border-strong);
    border-radius: 9px;
    text-decoration: none;
    transition: border-color .15s, background .15s, color .15s;
}
.auth-sso:hover { border-color: var(--auth-accent); background: var(--accent-tint); color: var(--auth-accent-dark); }
.auth-sso-mark {
    display: inline-grid;
    place-items: center;
    width: 20px;
    height: 20px;
    border-radius: 50%;
    /* Fixed brand mark, not a themed surface — #ffffff is chosen for THIS literal blue,
       not tracked to --text-on-accent (which flips to near-black for the teal accent in
       dark theme and would go invisible against this unrelated blue background). */
    background: #007dc1;
    color: #ffffff;
    font-size: .72rem;
    font-weight: 700;
}

.auth-alert {
    padding: .6rem .8rem;
    margin-bottom: 1rem;
    font-size: .85rem;
    color: var(--blocking);
    background: var(--blocking-tint);
    border: 1px solid var(--border);
    border-left: 4px solid var(--blocking);
    border-radius: 8px;
}

/* C7/Task 6: the sign-in error renders as <Callout> (shared component). The override that
   used to live here pinned its colors to match a light-only .auth-card; now that the card
   above is fully tokenized (var(--surface)), Callout's own tokenized styles already track
   the same theme the card does, so the pin would fight the card instead of matching it —
   removed rather than carried forward. */

.auth-foot {
    margin-top: 1.5rem;
    text-align: center;
    font-size: .72rem;
    letter-spacing: .02em;
    color: var(--text-muted);
}

@media (max-width: 440px) {
    .auth-card { padding: 1.8rem 1.4rem 1.4rem; }
}

/* ============================================================================
   Stat rollup cards (.stats/.stat) — global KPI tiles per the dashboard mockup:
   a semantic color rail (--sc, set per instance or via a modifier class below),
   an uppercase label with a leading dot, a big tabular-nums number, and a foot
   line. Introduced here for Fleet monitor's rollup but deliberately generic
   (not fleet-* named) so any page can reuse the same look later. Foot-line
   color uses --text-muted rather than the mockup's --text-faint — this text
   can carry an operator-relevant hint ("needs operator action"), so it stays
   above the faint tier's AA floor.
   ============================================================================ */
.stats {
    display: grid;
    grid-template-columns: repeat(4, 1fr);
    gap: var(--space-4);
    margin: 1rem 0 1.5rem;
}

.stat {
    background: var(--surface);
    border: 1px solid var(--border);
    border-radius: var(--radius-lg);
    padding: var(--space-4) var(--space-5);
    box-shadow: var(--shadow-1);
    position: relative;
    overflow: hidden;
}
.stat::before { content: ""; position: absolute; left: 0; top: 0; bottom: 0; width: 3px; background: var(--sc); }
.stat .st-label { font-size: var(--fs-xs); text-transform: uppercase; letter-spacing: .08em; color: var(--text-muted); font-weight: 700; display: flex; align-items: center; gap: 7px; }
.stat .st-num { font-size: 2.1rem; font-weight: 800; letter-spacing: -.03em; margin-top: 8px; font-variant-numeric: tabular-nums; }
.stat .st-foot { font-size: var(--fs-xs); color: var(--text-muted); margin-top: 2px; font-weight: 600; }
.stat .st-dot { width: 8px; height: 8px; border-radius: 50%; background: var(--sc); flex: none; }

@media (max-width: 900px) {
    .stats { grid-template-columns: repeat(2, 1fr); }
}

/* Fleet rollup (retired Fleet page; Devices wave view) — per-state color mapped onto --sc via the existing
   HealthClass()/`total` modifier classes already emitted by the markup. Reuses the same
   semantic palette as FleetSeverity/StatusBadge below: halted -> --blocking (an error-shaped
   stop), inprogress -> --warning (mockup-aligned; still distinct from --info so it doesn't
   read as "just informational"), done -> --ready, total -> --accent. */
.stat.halted { --sc: var(--blocking); }
.stat.inprogress { --sc: var(--warning); }
.stat.done { --sc: var(--ready); }
.stat.total { --sc: var(--accent); }

/* Task C8 dead-CSS sweep: .state-pill and .fleet-timeline(.ft-*) had zero remaining consumers —
   The retired Fleet page's halted-device drill-in now renders via the shared <StepTimeline> component
   (Components/Shared/StepTimeline.razor(.css), its own scoped tl-* classes) instead of this
   hand-rolled <ul class="fleet-timeline">/.ft-* markup, and its per-device state now renders via
   <StatusBadge> instead of <span class="state-pill">. tr.fleet-timeline-row td stays below —
   the Devices page still wraps the drill-in cell in <tr class="fleet-timeline-row"> (a distinct,
   still-live selector, not `.fleet-timeline`). */

tr.fleet-timeline-row td { background: var(--bg); padding: 0.75rem 1rem; }

/* ============================================================================
   Onboarding wizard stepper + Deploy step (Task 13, 2026-09-10 modernization
   pass). Ported from the mockup's .stepper / .step / .secret-box / .cmd / .copy-btn
   rules onto this app's token vocabulary. Onboard.razor renders these purely
   off its existing _step int (StepClass) - no new state, presentation only.

   Specificity note: the Deploy step's buttons sit inside the page's existing
   `<section class="op-actions">` wrapper, and `.op-actions button` (a class +
   type selector, more specific than a single class) already sets its own
   background/padding/margin for every plain <button> in that wrapper. That's
   harmless where the values happen to coincide (.btn-secondary), but it would
   visibly stomp a button whose look genuinely differs (the dark `.cmd` copy
   button). `.cmd .copy-btn` below is written as a two-class descendant
   selector specifically so it outranks `.op-actions button` regardless of
   which comes later in the file, without touching that shared rule (used by
   ~20 other pages) at all.
   ============================================================================ */
/* Single row, no wrap: each .step-line has flex:1, so under flex-wrap a connector greedily
   fills its row and pushes the next step to a new line (the stepper rendered vertically). */
.stepper { display: flex; align-items: center; margin: 1rem 0 1.5rem; max-width: 680px; }
.step { display: flex; align-items: center; gap: 10px; }
.step-dot {
    flex: none;
    width: 30px;
    height: 30px;
    border-radius: 50%;
    display: grid;
    place-items: center;
    font-weight: 800;
    font-size: .8rem;
    border: 2px solid var(--border-strong);
    color: var(--text-muted);
    background: var(--surface);
    transition: background .15s, border-color .15s, box-shadow .15s, color .15s;
}
.step.done .step-dot { background: var(--accent); border-color: var(--accent); color: var(--text-on-accent); }
.step.active .step-dot { border-color: var(--accent); color: var(--accent-dark); box-shadow: 0 0 0 4px var(--accent-tint); }
.step-name { font-size: var(--fs-sm); font-weight: 700; color: var(--text-muted); white-space: nowrap; }
.step.done .step-name, .step.active .step-name { color: var(--text); }
.step-line { flex: 1; height: 2px; min-width: 16px; background: var(--border); margin: 0 12px; border-radius: 2px; }
.step-line.filled { background: var(--accent); }
@media (max-width: 560px) {
    .step-name { display: none; }
    .step-line { margin: 0 6px; }
}

/* ---- Deploy step: shown-once secret ---- */
.secret-box {
    background: var(--surface-2);
    border: 1px dashed var(--border-strong);
    border-radius: var(--radius-md);
    padding: 14px 16px;
    display: flex;
    align-items: center;
    justify-content: space-between;
    gap: 12px;
    flex-wrap: wrap;
    max-width: 680px;
    margin-bottom: 1rem;
}
.secret-val { font-family: var(--font-mono); font-size: .9rem; font-weight: 600; word-break: break-all; }
/* Overrides `.op-actions button`'s margin-top/padding/font-size for just this button (see the
   specificity note above) so it renders at its intended .btn-sm size, vertically centered
   against the secret text rather than pushed down by the shared wrapper's spacing rule. */
.secret-box .btn-sm { margin-top: 0; padding: 6px 11px; font-size: var(--fs-xs); }

/* ---- Deploy step: install command ---- */
/* Deliberately a fixed dark "terminal" surface in both themes - literal colors, not tokens,
   same reasoning as a syntax-highlighted code block. Legibility (not theme-matching) is the
   only requirement here (Task 13 brief); contrast of #d6e6e3/#8be9d4/#79b8ff/#ffd479 on #0b1519
   all clear AA comfortably in either light or dark mode since the surface never changes. */
.cmd {
    background: #0b1519;
    color: #d6e6e3;
    border-radius: var(--radius-md);
    padding: 44px 18px 16px;
    font-family: var(--font-mono);
    font-size: .82rem;
    line-height: 1.7;
    overflow-x: auto;
    position: relative;
    border: 1px solid #16272e;
    max-width: 680px;
    margin-bottom: 1rem;
}
.cmd .tok-cmd { color: #8be9d4; }
.cmd .tok-flag { color: #79b8ff; }
.cmd .tok-val { color: #ffd479; }
.cmd .copy-btn {
    margin-top: 0;
    position: absolute;
    top: 10px;
    right: 10px;
    background: rgba(255, 255, 255, .1);
    border: 1px solid rgba(255, 255, 255, .18);
    color: #d6e6e3;
    border-radius: 7px;
    padding: 5px 10px;
    font-size: .72rem;
    font-weight: 700;
    font-family: inherit;
    cursor: pointer;
    display: inline-flex;
    gap: 6px;
    align-items: center;
    transition: background .13s;
}
.cmd .copy-btn:hover { background: rgba(255, 255, 255, .18); }
.cmd .copy-btn:focus-visible { outline: none; box-shadow: var(--focus-ring); }
