/* ===== Azure Cockpit Theme — CSS Variables ===== */
:root {
    color-scheme: light;

    /* Typography */
    --app-font-display: "Plus Jakarta Sans", "Segoe UI", -apple-system, BlinkMacSystemFont, Roboto, sans-serif;
    --app-font-body: "Source Sans 3", "Segoe UI", -apple-system, BlinkMacSystemFont, Roboto, sans-serif;
    --app-font-mono: "Cascadia Mono", "SFMono-Regular", Consolas, "Liberation Mono", Menlo, monospace;
    /* Used by the brand lockup and by nothing else, so the blast radius of a brand decision is the brand.
       Meridian is a cartographer's word and engraved charts are where its typography actually lives; a
       high-contrast display serif claims that in one glance, and it is the one register no other Azure
       tool is in. It is a display face with a single weight and no business below about 20px — which is
       why the sidebar lockup was re-proportioned rather than just re-lettered. */
    --app-font-brand: "Instrument Serif", "Iowan Old Style", Georgia, "Times New Roman", serif;

    /* The brand's own two colours. Until now the lockup was painted from --app-accent-deep, so the brand
       was literally a shade of the UI; these let it read as Truenorth's while every button, link, chip and
       nav state stays exactly the blue it is.

       North is not a new colour: #ff8c00 is already --app-energy-orange and already the warm end of the
       signal ribbon. It just becomes the only accent the mark spends. Brand ink is deeper and less
       saturated than --app-accent-deep (#003e73), and that gap is the point — at a glance it reads as ink
       rather than as another blue in the ramp. */
    --brand-ink: #0b1f33;
    --brand-north: #ff8c00;

    /* Azure cockpit palette */
    --app-bg-page: #f6f8fb;
    --app-surface-canvas: #f6f8fb;
    --app-surface-0: #ffffff;
    --app-surface-1: #f8fbff;
    --app-surface-2: #eef4fb;
    --app-surface-3: #e6f0fb;
    --app-surface-hover: #eaf4ff;
    --app-surface-hero: linear-gradient(135deg, var(--app-surface-0), rgba(239, 246, 255, 0.94));
    --app-sidebar-bg: #f4f8ff;
    --app-sidebar-bg-elevated: #eef5ff;
    --app-text-primary: #172033;
    --app-text-secondary: #4f5f75;
    --app-text-tertiary: #6b778c;
    --app-text-inverse: #ffffff;
    --app-border-subtle: #d9e2ef;
    --app-border-strong: #b7c9de;
    --app-border-focus: #005a9e;
    --app-accent-primary: #0078d4;
    --app-accent-secondary: #005a9e;
    --app-accent-deep: #003e73;
    --app-signal-cyan: #00a6fb;
    --app-energy-orange: #ff8c00;
    --app-success: #107c10;
    --app-warning: #ff8c00;
    --app-danger: #d13438;
    --app-info: #0078d4;
    --app-code-bg: #eef6ff;
    --app-nav-hover-bg: #eaf4ff;
    --app-nav-active-bg: #dceeff;
    --app-nav-active-text: #003e73;
    --app-nav-active-border: rgba(0, 120, 212, 0.32);

    /* Signature signal ribbon */
    --app-signal-ribbon: linear-gradient(90deg, var(--app-accent-primary) 0%, var(--app-signal-cyan) 52%, var(--app-energy-orange) 100%);
    --app-signal-ribbon-vertical: linear-gradient(180deg, var(--app-accent-primary) 0%, var(--app-signal-cyan) 52%, var(--app-energy-orange) 100%);
    --app-signal-ribbon-soft: linear-gradient(90deg, rgba(0, 120, 212, 0.14), rgba(0, 166, 251, 0.12), rgba(255, 140, 0, 0.14));
    --app-signal-ribbon-surface: linear-gradient(135deg, rgba(0, 120, 212, 0.08), rgba(0, 166, 251, 0.06) 58%, rgba(255, 140, 0, 0.08));
    --app-skeleton-base: #e6eef8;
    --app-skeleton-highlight: rgba(255, 255, 255, 0.82);

    /* Layout */
    --app-sidebar-expanded-width: 17.5rem;
    --app-sidebar-collapsed-width: 4.75rem;
    --app-content-max-width: 1200px;
    --app-workspace-max-width: 1500px;

    /* Radius, elevation, focus, and motion */
    --app-radius-sm: 0.375rem;
    --app-radius-md: 0.5rem;
    --app-radius-lg: 0.75rem;
    --app-radius-xl: 1rem;
    --app-radius-pill: 999px;
    --app-shadow-sm: 0 1px 2px rgba(23, 32, 51, 0.08), 0 1px 4px rgba(23, 32, 51, 0.04);
    --app-shadow-md: 0 8px 24px rgba(23, 32, 51, 0.11);
    --app-shadow-lg: 0 18px 42px rgba(23, 32, 51, 0.14);
    --app-shadow-hero: 0 14px 36px rgba(0, 62, 115, 0.11);
    --app-shadow-nav-active: 0 8px 18px rgba(0, 120, 212, 0.16);
    --app-focus-ring: 0 0 0 0.1rem #ffffff, 0 0 0 0.28rem #005a9e;
    --app-motion-fast: 120ms;
    --app-motion-standard: 180ms;
    --app-motion-slow: 260ms;
    --app-motion-ease: cubic-bezier(0.2, 0, 0, 1);

    /* Row density. A 12-row folder and a 250-row backlog are not the same reading task, and Azure DevOps
       ships a control for exactly this. One variable drives every table and list; the shell carries
       data-density, so the choice is app-wide rather than something to re-make on each page. */
    --app-row-pad: 0.55rem;

    /* Sticky-offset utilities */
    --app-topbar-height: 3.5rem;
    --app-sticky-top: calc(var(--app-topbar-height) + 0.75rem);
    --app-assistant-z-index: 1030;
    /* Floating-action-stack (FAB) convention — documented reserved fixed-position slots so floating controls
       never collide: bottom-right = Assistant drawer, top = Toast host. (S12 feedback moved to a top-bar control
       beside the notification bell, so the bottom-left slot is currently free.) The --app-fab-* values remain the
       global positioning fallback for any future bottom-area FAB so a stale scoped stylesheet can't trap it in
       the header/nav flow. */
    --app-fab-z-index: 1030;
    --app-fab-inset: 1.25rem;
    /* Content inline padding — ties tab-bleed margins to Bootstrap's px-4 / px-3 */
    --app-content-inline-padding: 1.5rem;
    --app-content-inline-padding-sm: 1rem;

    /* ===== Work-item vocabulary — the ONE source for every colour a work item is drawn with =====
       Owned in code by Web/Presentation/WorkItemVisuals.cs, which is the only thing that names these
       tokens; no page may re-derive a work-item colour. Before this existed, seven private switch
       blocks across five pages each mapped type -> Bootstrap utility and had drifted: a Test Case was
       blue on the Dashboard, yellow in the Explorer, and cyan on its own detail page (which painted
       EVERY type bg-info, so a Bug and a Story were indistinguishable).

       TYPE colours are Azure DevOps Boards' own work-item colours — the resemblance is the point.
       They are drawn as a swatch beside a full-contrast label, never as the label's own colour, so
       the colour is redundant encoding (WCAG 1.4.1) and the swatch carries a hairline edge so it stays
       discernible against white at any hue (1.4.11). */
    --app-wi-story: #009ccc;
    --app-wi-usecase: #773b93;
    --app-wi-testcase: #004b50;
    --app-wi-bug: #cc293d;
    --app-wi-task: #f2cb1d;
    --app-wi-issue: #b4009e;
    --app-wi-unknown: var(--app-text-tertiary);

    /* STATUS colours are a SEPARATE axis and are used as TEXT, so each is verified >= 4.5:1 on
       --app-surface-0. Two app tokens are deliberately not reused here: --app-energy-orange (#ff8c00)
       measures 2.3:1 as text and --app-accent-primary 4.58:1 with no margin, so Modified takes a
       darkened amber and Saved takes the deeper blue. */
    --app-st-draft: #6b778c; /* 4.58:1 */
    --app-st-saved: #005a9e; /* 7.18:1 */
    --app-st-modified: #9a5b00; /* 5.47:1 */
    --app-st-pushed: #107c10; /* 5.42:1 */
    --app-st-imported: #0a6e79; /* 6.05:1 */
    --app-st-failed: #d13438; /* 4.93:1 */
    --app-st-unknown: var(--app-text-tertiary);

    /* Bootstrap variable mapping */
    --bs-body-bg: var(--app-bg-page);
    --bs-body-color: var(--app-text-primary);
    --bs-body-font-family: var(--app-font-body);
    --bs-primary: var(--app-accent-primary);
    /* Bootstrap 5.3 paints .bg-primary / .bg-primary-subtle / .text-bg-primary from the -rgb triplet,
       NOT from --bs-primary. Remapping --bs-primary alone (which is all this file used to do) left every
       solid and subtle primary surface on Bootstrap indigo #0d6efd while buttons and links were Azure
       #0078d4 — two blues, adjacent, in the same table row. */
    --bs-primary-rgb: 0, 120, 212;
    --bs-link-color: var(--app-accent-primary);
    --bs-link-hover-color: var(--app-accent-secondary);
    --bs-border-color: var(--app-border-subtle);
    --bs-border-radius: var(--app-radius-md);
    --bs-border-radius-lg: var(--app-radius-lg);
}

/* ===== Dark =========================================================================================
   Only colour TOKENS are redefined — nothing structural moves and no component rule is duplicated, which
   is the whole reason the palette was worth centralising.

   Everything here lives under @media screen. Paper is white: the print stylesheets were written when
   light was the only theme, and a reader who prints from a dark session must still get a light document.
   Excluding the block from print lets the :root values stand there, so this is one wrapper rather than a
   second palette that has to agree with the first.

   Dark is OPT-IN. It deliberately does not follow prefers-color-scheme: doing so once handed an
   unfinished theme to every user on a dark desktop who had never asked for it. */
@media screen {
    [data-bs-theme="dark"] {
        color-scheme: dark;

        --app-bg-page: #12161d;
        --app-surface-canvas: #12161d;
        --app-surface-0: #1a202a;
        --app-surface-1: #1f2630;
        --app-surface-2: #252d38;
        --app-surface-3: #2b3440;
        --app-surface-hover: #27313e;
        --app-surface-hero: linear-gradient(135deg, #1f2630, #1a212b);
        --app-sidebar-bg: #161c24;
        --app-sidebar-bg-elevated: #1b222c;

        /* The ink inverts; the amber only warms enough to hold its own against a dark ground. Keeping
           north recognisably north across both themes is most of what makes the mark one mark. */
        --brand-ink: #e4edf7;
        --brand-north: #ffa338;

        --app-text-primary: #e8edf4;
        --app-text-secondary: #b3bfcf;
        --app-text-tertiary: #93a1b2;
        --app-text-inverse: #12161d;

        --app-border-subtle: #303a47;
        --app-border-strong: #46525f;
        --app-border-focus: #7cc4f5;

        --app-accent-primary: #6cb8f0;
        --app-accent-secondary: #9ed2f8;
        --app-accent-deep: #c2e3fb;
        --app-signal-cyan: #45c1fb;
        --app-energy-orange: #f0a03c;
        --app-success: #5cc45f;
        --app-warning: #e2a03a;
        --app-danger: #f0787d;
        --app-info: #6cb8f0;

        --app-code-bg: #1e2732;
        --app-nav-hover-bg: #232c38;
        --app-nav-active-bg: #1d3348;
        --app-nav-active-text: #cfe6fa;
        --app-nav-active-border: rgba(108, 184, 240, 0.42);

        --app-skeleton-base: #232b36;
        --app-skeleton-highlight: rgba(255, 255, 255, 0.06);

        --app-shadow-sm: 0 1px 2px rgba(0, 0, 0, 0.5), 0 1px 4px rgba(0, 0, 0, 0.35);
        --app-shadow-md: 0 8px 24px rgba(0, 0, 0, 0.5);
        --app-shadow-lg: 0 18px 42px rgba(0, 0, 0, 0.55);
        --app-shadow-hero: 0 14px 36px rgba(0, 0, 0, 0.5);
        --app-shadow-nav-active: 0 8px 18px rgba(0, 0, 0, 0.45);
        --app-focus-ring: 0 0 0 0.1rem #12161d, 0 0 0 0.28rem #7cc4f5;

        /* Azure Boards' work-item colours, lifted onto a dark ground. A Bug stays red. */
        --app-wi-story: #3fc0e8;
        --app-wi-usecase: #b482d0;
        --app-wi-testcase: #4aa8a0;
        --app-wi-bug: #ef6a76;
        --app-wi-task: #e8c93f;
        --app-wi-issue: #e163cd;
        --app-wi-unknown: var(--app-text-tertiary);

        /* Status is drawn as TEXT, so these are re-picked for contrast rather than lightened by eye. */
        --app-st-draft: #93a1b2;
        --app-st-saved: #6cb8f0;
        --app-st-modified: #e2a03a;
        --app-st-pushed: #5cc45f;
        --app-st-imported: #4fc4c8;
        --app-st-failed: #f0787d;
        --app-st-unknown: var(--app-text-tertiary);

        --bs-body-bg: var(--app-bg-page);
        --bs-body-color: var(--app-text-primary);
        --bs-primary: var(--app-accent-primary);
        --bs-primary-rgb: 108, 184, 240;
        --bs-link-color: var(--app-accent-primary);
        --bs-link-hover-color: var(--app-accent-secondary);
        --bs-border-color: var(--app-border-subtle);
        --bs-secondary-color: var(--app-text-secondary);
        --bs-emphasis-color: var(--app-text-primary);
    }

    /* The light ground is two radial washes; on dark they read as smears, so the dark ground is flat. */
    [data-bs-theme="dark"] body,
    [data-bs-theme="dark"] .app-shell {
        background: var(--app-bg-page);
    }

    /* ── Directional corrections ──────────────────────────────────────────────────────────────────────
       Contrast is not symmetric and a palette cannot simply be swapped. The accent was lightened so it
       reads as TEXT on a dark ground; a filled control uses it the other way round — as a FILL under
       white text — which measures about 2.5:1. Filled things take dark ink in dark. */
    [data-bs-theme="dark"] .btn-primary,
    [data-bs-theme="dark"] .btn-primary:disabled,
    [data-bs-theme="dark"] .btn-primary.disabled {
        --bs-btn-color: #0b1622;
        --bs-btn-bg: var(--app-accent-primary);
        --bs-btn-border-color: var(--app-accent-primary);
        --bs-btn-hover-color: #0b1622;
        --bs-btn-hover-bg: var(--app-accent-secondary);
        --bs-btn-hover-border-color: var(--app-accent-secondary);
        --bs-btn-active-color: #0b1622;
        --bs-btn-active-bg: var(--app-accent-secondary);
        --bs-btn-disabled-color: #0b1622;
        --bs-btn-disabled-bg: var(--app-accent-primary);
        color: #0b1622;
        background-color: var(--app-accent-primary);
        border-color: var(--app-accent-primary);
    }

    [data-bs-theme="dark"] .btn-outline-primary {
        --bs-btn-color: var(--app-accent-secondary);
        --bs-btn-border-color: var(--app-accent-primary);
        --bs-btn-hover-color: #0b1622;
        --bs-btn-hover-bg: var(--app-accent-primary);
        color: var(--app-accent-secondary);
    }

    [data-bs-theme="dark"] .btn-outline-secondary {
        --bs-btn-color: var(--app-text-primary);
        --bs-btn-border-color: var(--app-border-strong);
        --bs-btn-hover-color: var(--app-text-primary);
        --bs-btn-hover-bg: var(--app-surface-2);
        --bs-btn-hover-border-color: var(--app-border-strong);
        --bs-btn-active-color: var(--app-text-primary);
        --bs-btn-active-bg: var(--app-surface-3);
        color: var(--app-text-primary);
    }

    [data-bs-theme="dark"] .btn-light {
        --bs-btn-color: var(--app-text-primary);
        --bs-btn-bg: var(--app-surface-2);
        --bs-btn-border-color: var(--app-border-strong);
        --bs-btn-hover-bg: var(--app-surface-3);
        color: var(--app-text-primary);
    }

    [data-bs-theme="dark"] .btn-outline-danger {
        --bs-btn-color: var(--app-danger);
        --bs-btn-border-color: var(--app-danger);
        --bs-btn-hover-color: #0b1622;
        --bs-btn-hover-bg: var(--app-danger);
        color: var(--app-danger);
    }

    [data-bs-theme="dark"] .btn-success,
    [data-bs-theme="dark"] .btn-warning,
    [data-bs-theme="dark"] .text-bg-primary,
    [data-bs-theme="dark"] .text-bg-info,
    [data-bs-theme="dark"] .text-bg-success,
    [data-bs-theme="dark"] .text-bg-warning,
    [data-bs-theme="dark"] .badge.bg-primary,
    [data-bs-theme="dark"] .badge.bg-info {
        color: #0b1622 !important;
    }

    /* Bootstrap's light utilities are a near-white fill in both themes — a white block on a dark page. */
    [data-bs-theme="dark"] .text-bg-light,
    [data-bs-theme="dark"] .bg-light {
        color: var(--app-text-primary) !important;
        background-color: var(--app-surface-2) !important;
    }

    /* Subtle fills are tuned for a white page; on dark they keep dark ink on a dark tint. */
    [data-bs-theme="dark"] .bg-primary-subtle,
    [data-bs-theme="dark"] .bg-success-subtle,
    [data-bs-theme="dark"] .bg-info-subtle,
    [data-bs-theme="dark"] .bg-warning-subtle,
    [data-bs-theme="dark"] .bg-danger-subtle,
    [data-bs-theme="dark"] .bg-secondary-subtle {
        background-color: var(--app-surface-2) !important;
        color: var(--app-text-primary) !important;
    }

    /* Bootstrap's -emphasis text colours are picked for their light -subtle partners. */
    [data-bs-theme="dark"] .text-primary-emphasis,
    [data-bs-theme="dark"] .text-success-emphasis,
    [data-bs-theme="dark"] .text-info-emphasis,
    [data-bs-theme="dark"] .text-warning-emphasis,
    [data-bs-theme="dark"] .text-danger-emphasis,
    [data-bs-theme="dark"] .text-secondary-emphasis,
    [data-bs-theme="dark"] .text-body-secondary,
    [data-bs-theme="dark"] .text-muted {
        color: var(--app-text-secondary) !important;
    }

    /* The assistant launcher is a gradient pill with white text; in dark the gradient is built from the
       lightened accents, so the label lands on a light fill. */
    /* The dashboard hero is a deep-blue gradient with white text: it already works on a dark page and
       only needs its own ground stated so nothing shows through it. */
    [data-bs-theme="dark"] .cmd-header {
        background-color: #0b1f33;
    }
}

/* ===== Base ===== */
html {
    min-height: 100%;
    background-color: var(--app-bg-page);
}

html, body {
    font-family: var(--app-font-body);
    background:
        radial-gradient(circle at top left, rgba(0, 120, 212, 0.08), transparent 28rem),
        radial-gradient(circle at top right, rgba(0, 166, 251, 0.06), transparent 24rem),
        var(--app-bg-page);
    color: var(--app-text-primary);
    text-rendering: optimizeLegibility;
    -webkit-font-smoothing: antialiased;
}

body {
    min-height: 100vh;
}

body,
button,
input,
select,
textarea,
.table {
    font-family: var(--app-font-body);
}

h1,
h2,
h3,
h4,
h5,
h6,
.navbar-brand,
.page-title,
.display-font,
.dashboard-summary-value {
    font-family: var(--app-font-display);
    letter-spacing: -0.015em;
}

code,
kbd,
pre,
samp,
.font-monospace,
.text-monospace {
    font-family: var(--app-font-mono);
}

code {
    padding: 0.08rem 0.28rem;
    border-radius: var(--app-radius-sm);
    background: var(--app-code-bg);
    color: var(--app-accent-deep);
}

pre {
    padding: 1rem;
    border: 1px solid var(--app-border-subtle);
    border-radius: var(--app-radius-lg);
    background: var(--app-code-bg);
    color: var(--app-text-primary);
}

a, .btn-link {
    color: var(--app-accent-primary);
}

a:hover, .btn-link:hover {
    color: var(--app-accent-secondary);
}

.btn-primary {
    background-color: var(--app-accent-primary);
    border-color: var(--app-accent-primary);
    box-shadow: 0 1px 2px rgba(0, 120, 212, 0.18);
}

.btn-primary:hover {
    background-color: var(--app-accent-secondary);
    border-color: var(--app-accent-secondary);
}

.btn,
.form-control,
.form-select,
.form-check-input,
.card,
.list-group-item,
.dropdown-menu {
    border-radius: var(--app-radius-md);
}

.btn,
.form-control,
.form-select,
.form-check-input,
a {
    transition:
        background-color var(--app-motion-standard) var(--app-motion-ease),
        border-color var(--app-motion-standard) var(--app-motion-ease),
        box-shadow var(--app-motion-standard) var(--app-motion-ease),
        color var(--app-motion-standard) var(--app-motion-ease);
}

.btn:focus,
.btn:active:focus,
.btn-link.nav-link:focus,
.form-control:focus,
.form-select:focus,
.form-check-input:focus {
    border-color: var(--app-border-focus);
    box-shadow: var(--app-focus-ring);
}

:where(a, button, [role="button"], input, select, textarea, summary, [tabindex]):focus-visible {
    outline: 2px solid var(--app-border-focus);
    outline-offset: 2px;
}

/* ===== Readability floors =============================================================================
   Bootstrap's own control colours are measured against pure white; every surface in this app is tinted,
   so several of them land just under 4.5:1 where they are actually used - 4.23 to 4.41. A contrast sweep
   over all 30 routes found each of these, and none of them is visible in the source.

   These were written once already and lost: they had been placed next to the dark-theme rules and went
   out with them when dark was withdrawn. They belong here, in the theme that ships. */

/* Outline buttons: the label sits on a tinted panel, so both variants take the deeper end of their hue.
   Set through Bootstrap's own button variable AND `color`, because .btn resolves --bs-btn-color at the
   same specificity and load order would otherwise decide the hover and active states. */
.btn-outline-primary {
    --bs-btn-color: var(--app-accent-secondary);
    color: var(--app-accent-secondary);
}

.btn-outline-secondary {
    --bs-btn-color: var(--app-text-secondary);
    --bs-btn-hover-color: var(--app-text-inverse);
    --bs-btn-active-color: var(--app-text-inverse);
    --bs-btn-disabled-color: var(--app-text-tertiary);
    color: var(--app-text-secondary);
}

/* A .btn with no variant still inherits Bootstrap's secondary ink. */
.btn:not([class*="btn-"]) {
    --bs-btn-color: var(--app-text-secondary);
}

/* Small text needs more contrast than the same colour gives at body size. */
a.small,
.small a,
small a,
.nav-tabs .nav-link,
.wizard-progress {
    color: var(--app-accent-secondary);
}

.wizard-progress {
    color: var(--app-text-secondary);
}

/* An alert paints its own tinted ground - amber for a warning - and the link colour that clears on white
   measures 4.09:1 there. The deeper accent keeps it reading as a link while clearing the alert's fill. */
.alert a:not(.btn) {
    color: var(--app-accent-secondary);
    text-decoration: underline;
}

/* Bootstrap's success fill under white text measures 4.02:1; the badge keeps the semantic and takes the
   darker green the app already uses for a pushed status. */
.badge.text-bg-success,
.badge.bg-success {
    background-color: var(--app-st-pushed) !important;
}

/* ===== Cards ===== */
/* Drive Bootstrap's own card variables rather than setting border-radius directly. Bootstrap rounds a
   card's first and last child from --bs-card-inner-border-radius, which it derives from
   --bs-card-border-radius; overriding the raw property left that derivation on Bootstrap's default, so an
   opaque .card-header painted square corners over the card's rounded ones. Every header, footer and
   flush table in the app had the same clipped top edge. */
.card {
    --bs-card-border-radius: var(--app-radius-lg);
    --bs-card-inner-border-radius: calc(var(--app-radius-lg) - 1px);
    --bs-card-border-color: var(--app-border-subtle);
    --bs-card-bg: var(--app-surface-0);
    box-shadow: var(--app-shadow-sm);
    transition:
        border-color var(--app-motion-standard) var(--app-motion-ease),
        box-shadow var(--app-motion-standard) var(--app-motion-ease),
        transform var(--app-motion-standard) var(--app-motion-ease);
}

/* A card header is the one place a panel says what it is, so it carries a tint of the accent rather than
   plain grey - the "colour bars" the ribbon used to provide, spent where they group content instead of
   on every surface. The top edge takes the card's inner radius so it can never square off the corner. */
.card-header {
    background: var(--app-surface-2);
    border-bottom: 1px solid var(--app-border-subtle);
    font-family: var(--app-font-display);
    font-weight: 600;
    color: var(--app-text-primary);
}

.card > .card-header:first-child {
    border-radius: var(--bs-card-inner-border-radius) var(--bs-card-inner-border-radius) 0 0;
}

.card > .card-footer:last-child {
    border-radius: 0 0 var(--bs-card-inner-border-radius) var(--bs-card-inner-border-radius);
}

/* A table that IS the card's body should reach the card's edges. Left in the body's padding, its header
   band floats with a gutter either side and the card reads as two misaligned rectangles - which is what
   the offset borders were. Bleeding it out is also how Azure DevOps draws a list card.

   The bleed is taken off the BODY's padding, not put on the table as negative margins. Negative margins
   assume the padding is there to cancel: on a `.card-body.p-0` - which is how a flush table is usually
   written - Bootstrap's `!important` zeroes the padding but cannot reach the child's margins, so the same
   -1rem that used to cancel a gutter became a 1rem overhang past the card on every side. On a clipping
   card (`.cockpit-panel`) that overhang is not visible untidiness, it is cut-off text. Zeroing the padding
   gives the identical geometry on a padded body and is simply idempotent on `.p-0`. */
.card-body:has(> .table-responsive:only-child),
.card-body:has(> .table:only-child) {
    padding: 0;
}

/* A table flush to the card's bottom edge rounds with it. */
.card > .card-body:last-child > .table-responsive:only-child,
.card > .card-body:last-child > .table:only-child,
.card > .table-responsive:last-child {
    border-radius: 0 0 var(--bs-card-inner-border-radius) var(--bs-card-inner-border-radius);
}

/* Only a bare table needs clipping to pick that radius up. A `.table-responsive` is already a scroll
   container and so clips to its own rounded box - and `overflow: hidden` here outranked Bootstrap's
   `.table-responsive { overflow-x: auto }`, which is the element's entire purpose. A wide table then had
   no scrollbar and no way to reach the columns past the card's edge. */
.card > .card-body:last-child > .table:only-child {
    overflow: hidden;
}

/* The last row's bottom border doubles with the card's own edge. */
.card > .card-body:last-child > .table-responsive:only-child .table > :last-child > tr:last-child > *,
.card > .card-body:last-child > .table:only-child > :last-child > tr:last-child > * {
    border-bottom-width: 0;
}

.card-footer {
    background-color: var(--app-surface-1);
    border-top-color: var(--app-border-subtle);
}

.list-group-item {
    border-color: var(--app-border-subtle);
}

/* ===== Tables ===== */
.table {
    color: var(--app-text-primary);
    --bs-table-border-color: var(--app-border-subtle);
}

.table thead th {
    font-family: var(--app-font-display);
    font-weight: 700;
    color: var(--app-text-secondary);
}

/* ===== Page Header ===== */
/* ===== Cockpit primitives and signal ribbon utilities ===== */
.cockpit-panel,
.cockpit-card {
    position: relative;
    overflow: hidden;
    border: 1px solid var(--app-border-subtle);
    border-radius: var(--app-radius-lg);
    background: var(--app-surface-0);
    box-shadow: var(--app-shadow-sm);
}

button.type-card {
    display: block;
    width: 100%;
    padding: 0;
    text-align: left;
    color: var(--app-text-primary);
    background: var(--app-surface-0);
}

button.type-card:focus-visible {
    outline: 2px solid var(--app-border-focus);
    outline-offset: 2px;
}

.connections-project-list {
    max-height: 18.75rem;
    overflow-y: auto;
}

.surface-elevated {
    background: var(--app-surface-0);
    border: 1px solid var(--app-border-subtle);
    box-shadow: var(--app-shadow-md);
}

/* ===== State rails ===================================================================================
   These replace the signal ribbon. The blue-cyan-orange gradient was the app's one distinctive mark and
   it was stamped on 65 surfaces through .signal-ribbon* plus every .cockpit-panel and .cockpit-card via
   ::before - which is to say it marked everything, and therefore marked nothing.

   A coloured edge now means one thing: THIS ONE NEEDS YOU. The rail is drawn as an inset shadow rather
   than a ::before so it composes with a table row, a card or a list item without any of them needing
   position:relative, and it never participates in layout.

   The gradient itself is kept as --app-signal-ribbon and still paints the brandmark and the auth
   screens, where it is an identity mark rather than a decoration on someone else's content. */
.state-rail-danger {
    box-shadow: inset 3px 0 0 var(--app-st-failed);
}

.state-rail-warning {
    box-shadow: inset 3px 0 0 var(--app-st-modified);
}

.state-rail-success {
    box-shadow: inset 3px 0 0 var(--app-st-pushed);
}

/* ===== Row density =================================================================================== */
[data-density="compact"] {
    --app-row-pad: 0.25rem;
}

/* Block padding only: the horizontal rhythm is what keeps columns apart and is not the reader's choice. */
.table > :not(caption) > * > * {
    padding-block: var(--app-row-pad);
}

.list-group-item {
    padding-block: var(--app-row-pad);
}

.app-density-toggle {
    display: inline-flex;
    align-items: center;
    justify-content: center;
    width: 1.875rem;
    height: 1.875rem;
    border: 1px solid transparent;
    border-radius: var(--app-radius-md);
    background: transparent;
    color: var(--app-text-secondary);
    cursor: pointer;
}

.app-density-toggle:hover {
    background: var(--app-nav-hover-bg);
    color: var(--app-accent-primary);
}

/* ===== Command-bar breadcrumb =========================================================================
   The top bar's left half. It replaced a static "<product> / Product cockpit" caption that
   rendered identically on all 66 routes. Ancestors are links; the current page is text. The trail is the
   one thing in the bar allowed to grow, so it takes the free space; the leaf is the only shrinkable item
   (it names the thing you are looking at, so it is also the one that must survive), and below 48rem the
   ancestors are hidden outright rather than clipped. */
.app-crumb {
    display: flex;
    align-items: center;
    min-width: 0;
    flex: 1;
    padding-inline-start: 0.125rem;
}

/* The global controls are a group, set apart from the location by a hairline rather than by distance -
   the bar was mostly empty space between them, which is what made it read as unfinished. */
.app-topbar-actions {
    padding-inline-start: 0.875rem;
    border-inline-start: 1px solid var(--app-border-subtle);
}

.app-crumb__home {
    font-family: var(--app-font-display);
    font-weight: 700;
    font-size: 0.875rem;
    color: var(--app-text-primary);
}

/* The home step is an icon, so it needs a target of its own rather than the text padding around it. */
.app-crumb__item:first-child .app-crumb__link {
    display: inline-grid;
    place-items: center;
    width: 1.75rem;
    height: 1.75rem;
    border-radius: var(--app-radius-md);
    color: var(--app-text-secondary);
}

.app-crumb__item:first-child .app-crumb__link:hover {
    background: var(--app-nav-hover-bg);
    color: var(--app-accent-primary);
    text-decoration: none;
}

.app-crumb__trail {
    display: flex;
    align-items: center;
    gap: 0.4375rem;
    flex-wrap: nowrap;
    min-width: 0;
    margin: 0;
    padding: 0;
    list-style: none;
    overflow: hidden;
}

.app-crumb__item,
.app-crumb__sep {
    flex: none;
    font-size: 0.8125rem;
    line-height: 1.3;
}

/* The separator is a character, so it is measured like one. It sat at 4.25:1 once the bar took the
   frame's tint instead of white. */
.app-crumb__sep {
    color: var(--app-text-secondary);
}

.app-crumb__item--current {
    min-width: 0;
    flex: 0 1 auto;
    overflow: hidden;
}

.app-crumb__link {
    color: var(--app-text-secondary);
    text-decoration: none;
    border-radius: var(--app-radius-sm);
    padding: 0.0625rem 0.125rem;
}

.app-crumb__link:hover {
    color: var(--app-accent-primary);
    text-decoration: underline;
}

.app-crumb__text {
    display: block;
    font-weight: 700;
    color: var(--app-text-primary);
    overflow: hidden;
    text-overflow: ellipsis;
    white-space: nowrap;
}

.app-crumb__item--current .app-crumb__text {
    max-width: 34ch;
}

@media (max-width: 48rem) {
    /* On a narrow window only the leaf earns the space. */
    .app-crumb__trail > .app-crumb__item:not(.app-crumb__item--current),
    .app-crumb__trail > .app-crumb__sep:not(:last-of-type) {
        display: none;
    }

    /* The display preferences stand down with them. Theme and density are set once and then left alone,
       so on a phone the bar keeps the controls a session actually turns over - inbox, alerts, profile -
       and the reader still gets the OS colour scheme by default. */
    .app-density-toggle {
        display: none;
    }
}

/* ===== Page shell ====================================================================================
   The one page header, replacing eight. A title row and a rule - not a hero: the command bar carries
   location, so the page opens with its subject.

   It keeps ONE accent bar. The ribbon was retired because it marked every surface; a single short bar
   under the page's own name marks exactly one thing per page, which is what an accent is for. */
.page-shell {
    display: grid;
    grid-template-columns: 1fr auto;
    align-items: center;
    gap: 0.5rem 1rem;
    padding-bottom: 0.85rem;
    margin-bottom: 1.25rem;
}

/* A plain rule. The gradient now runs full width along the bottom of the header BAND, where it separates
   the app's chrome from its content; a second gradient line ninety pixels below it only competed with the
   first. One ribbon, at the boundary that matters. */
.page-shell::after {
    content: "";
    grid-column: 1 / -1;
    grid-row: 3;
    height: 1px;
    margin-bottom: -0.85rem;
    background: var(--app-border-strong);
}

.page-shell__identity {
    display: flex;
    align-items: center;
    gap: 0.5rem;
    min-width: 0;
    grid-row: 1;
}

.page-shell__icon {
    font-size: 1.125rem;
    color: var(--app-accent-primary);
    flex: none;
}

.page-shell__title {
    font-family: var(--app-font-display);
    font-size: 1.25rem;
    font-weight: 700;
    letter-spacing: -0.015em;
    color: var(--app-text-primary);
    margin: 0;
    min-width: 0;
    overflow: hidden;
    text-overflow: ellipsis;
}

.page-shell__count {
    font-family: var(--app-font-mono);
    font-size: 0.75rem;
    font-variant-numeric: tabular-nums;
    color: var(--app-text-secondary);
    border: 1px solid var(--app-border-subtle);
    border-radius: var(--app-radius-pill);
    padding: 0.0625rem 0.5rem;
    flex: none;
}

/* The identity strip: id, type, status and link side by side.

   This slot spaced its markers with nothing but the whitespace text node Razor happens to emit between
   sibling components — 2.6px measured, which is why the borders read as touching in the editor no matter
   how much padding the chips themselves were given. Three rounds of feedback were spent on the chips
   when the container was what had no gap. It is a flex row like every other marker cluster in the app. */
.page-shell__status {
    display: inline-flex;
    align-items: center;
    flex-wrap: wrap;
    gap: 0.5rem;
    font-size: 0.8125rem;
    color: var(--app-text-tertiary);
    flex: none;
}

.page-shell__commands {
    display: flex;
    align-items: center;
    gap: 0.5rem;
    flex-wrap: wrap;
    grid-row: 1;
}

/* The subtitle sits INSIDE the header so the rule falls under the whole block. It used to be a sibling
   pulled up by a negative margin, which put it on top of the border and made the header look unfinished. */
.page-shell__subtitle {
    grid-column: 1 / -1;
    grid-row: 2;
    color: var(--app-text-secondary);
    font-size: 0.9375rem;
    max-width: 80ch;
    margin: 0;
}

/* ===== Work-item chips — type, status, link state =====================================================
   Three axes that never compete because they never share a shape: TYPE is a colour swatch beside a
   full-contrast label, STATUS is an outlined pill whose colour is its text, LINK STATE is a monochrome
   glyph. Rendered only by WorkItemTypeChip / WorkItemStatusChip / WorkItemLinkChip; the colour arrives
   as an inline custom property from WorkItemVisuals so the token stays the single source. */
/* The four row markers - identifier, type, status, link state - share one metric so a row reads as one
   line of equal-weight objects. Each was set independently before: the type chip left 1px above and below
   its text, and the link chip's content was TALLER than its own box, so the markers touched. */
.wi-chip,
.wi-state,
.wi-link {
    min-height: 1.5rem;
    padding-block: 0.25rem;
    line-height: 1.25;
    vertical-align: middle;
}

.wi-chip {
    display: inline-flex;
    align-items: center;
    gap: 0.4rem;
    font-size: 0.8125rem;
    font-weight: 600;
    color: var(--app-text-primary);
    white-space: nowrap;
}

.wi-chip__swatch {
    width: 0.5625rem;
    height: 0.5625rem;
    flex: none;
    border-radius: 2px;
    background: var(--wi-chip-color, var(--app-wi-unknown));
    /* A hairline edge keeps a pale swatch (Task #f2cb1d) discernible on white without darkening the
       Azure Boards hue itself. */
    outline: 1px solid rgba(23, 32, 51, 0.22);
    outline-offset: -1px;
}

.wi-state {
    display: inline-flex;
    align-items: center;
    gap: 0.3125rem;
    padding: 0.1875rem 0.5rem;
    font-size: 0.75rem;
    font-weight: 600;
    line-height: 1.35;
    white-space: nowrap;
    vertical-align: middle;
    color: var(--wi-state-color, var(--app-st-unknown));
    background: transparent;
    border: 1px solid currentColor;
    border-radius: var(--app-radius-pill);
}

.wi-state__dot {
    width: 0.375rem;
    height: 0.375rem;
    flex: none;
    border-radius: 50%;
    background: currentColor;
}

.wi-link {
    display: inline-flex;
    align-items: center;
    gap: 0.3125rem;
    font-size: 0.78125rem;
    white-space: nowrap;
    color: var(--app-text-secondary);
}

.wi-link--local {
    color: var(--app-text-tertiary);
}

/* ===== Shared loading and empty-state primitives ===== */
.app-loading-state,
.app-empty-state {
    position: relative;
    overflow: hidden;
    border-radius: var(--app-radius-lg);
}

.app-loading-state {
    display: grid;
    gap: 0.75rem;
    padding: 1rem;
    border: 1px solid var(--app-border-subtle);
    background:
        linear-gradient(180deg, var(--app-surface-0), var(--app-surface-1)),
        var(--app-surface-1);
    box-shadow: var(--app-shadow-sm);
    color: var(--app-text-secondary);
}

.app-loading-state--compact {
    gap: 0.625rem;
    padding: 0.75rem;
}

.app-loading-state__header {
    display: flex;
    align-items: center;
    gap: 0.625rem;
    color: var(--app-text-primary);
    font-size: 0.875rem;
}

.app-loading-state__indicator {
    position: relative;
    display: inline-flex;
    flex: 0 0 auto;
    width: 0.75rem;
    height: 0.75rem;
    border-radius: 50%;
    background: var(--app-signal-ribbon);
    box-shadow: 0 0 0 0.2rem rgba(0, 120, 212, 0.1);
}

.app-loading-state__indicator::after {
    content: "";
    position: absolute;
    inset: -0.3rem;
    border: 1px solid rgba(0, 120, 212, 0.22);
    border-radius: inherit;
    animation: app-loading-pulse 1.45s var(--app-motion-ease) infinite;
}

.app-loading-skeleton-group {
    display: grid;
    gap: 0.5rem;
}

.app-loading-skeleton {
    position: relative;
    width: 100%;
    height: 0.75rem;
    overflow: hidden;
    border-radius: var(--app-radius-pill);
    background:
        linear-gradient(90deg, rgba(0, 120, 212, 0.08), rgba(0, 166, 251, 0.08)),
        var(--app-skeleton-base);
}

.app-loading-skeleton::after {
    content: "";
    position: absolute;
    inset: 0;
    transform: translateX(-100%);
    background:
        linear-gradient(90deg, transparent, var(--app-skeleton-highlight), transparent),
        var(--app-signal-ribbon-soft);
    animation: app-skeleton-shimmer 1.55s var(--app-motion-ease) infinite;
}

.app-loading-skeleton--title {
    width: min(16rem, 82%);
    height: 1rem;
}

.app-loading-skeleton--line {
    width: 100%;
}

.app-loading-skeleton--line-short {
    width: 62%;
}

.app-loading-skeleton--block {
    height: 4rem;
    border-radius: var(--app-radius-md);
}

.app-empty-state {
    display: flex;
    align-items: flex-start;
    gap: 0.875rem;
    padding: 1rem;
    border: 1px dashed var(--app-border-strong);
    background:
        var(--app-signal-ribbon-surface),
        var(--app-surface-1);
    color: var(--app-text-secondary);
}

.app-empty-state--compact {
    gap: 0.75rem;
    padding: 0.75rem 0.85rem;
}

.app-empty-state--center {
    flex-direction: column;
    align-items: center;
    text-align: center;
}

.app-empty-state__icon {
    display: inline-flex;
    align-items: center;
    justify-content: center;
    flex: 0 0 auto;
    width: 2.5rem;
    height: 2.5rem;
    border: 1px solid rgba(0, 120, 212, 0.16);
    border-radius: var(--app-radius-lg);
    background: var(--app-surface-2);
    color: var(--app-accent-deep);
    box-shadow: var(--app-shadow-sm);
}

.app-empty-state__title {
    margin: 0 0 0.2rem;
    color: var(--app-text-primary);
    font-size: 0.95rem;
    font-weight: 700;
}

.app-empty-state__copy {
    color: var(--app-text-secondary);
    font-size: 0.875rem;
    line-height: 1.45;
}

@keyframes app-loading-pulse {
    0%,
    100% {
        opacity: 0.38;
        transform: scale(0.86);
    }

    50% {
        opacity: 0.9;
        transform: scale(1.08);
    }
}

@keyframes app-skeleton-shimmer {
    to {
        transform: translateX(100%);
    }
}

@media (prefers-reduced-motion: reduce) {
    .app-loading-state__indicator::after,
    .app-loading-skeleton::after {
        animation: none !important;
    }

    .app-loading-skeleton::after {
        opacity: 0;
        transform: none;
    }
}

.app-fade-in-up {
    animation: app-fade-in-up var(--app-motion-slow) var(--app-motion-ease) both;
}

@keyframes app-fade-in-up {
    from {
        opacity: 0;
        transform: translateY(0.5rem);
    }

    to {
        opacity: 1;
        transform: translateY(0);
    }
}

/* ===== Auth entry cockpit ===== */
.auth-entry {
    display: flex;
    align-items: center;
    justify-content: center;
    min-height: calc(100vh - var(--app-topbar-height) - 2rem);
    padding: clamp(1rem, 3vw, 3rem) 0;
}

.auth-entry__shell {
    position: relative;
    display: grid;
    grid-template-columns: minmax(0, 1.05fr) minmax(22rem, 0.95fr);
    width: min(100%, 68rem);
    overflow: hidden;
    border: 1px solid var(--app-border-subtle);
    border-radius: var(--app-radius-xl);
    background: var(--app-surface-0);
    box-shadow: var(--app-shadow-hero);
}

.auth-entry__shell::before {
    content: "";
    position: absolute;
    inset: 0 0 auto 0;
    z-index: 2;
    height: 3px;
    background: var(--app-signal-ribbon);
    pointer-events: none;
}

.auth-brand-panel {
    position: relative;
    display: flex;
    flex-direction: column;
    justify-content: space-between;
    gap: 2rem;
    min-height: 34rem;
    padding: clamp(1.5rem, 4vw, 3rem);
    overflow: hidden;
    border-right: 1px solid var(--app-border-subtle);
    background:
        radial-gradient(circle at 14% 18%, rgba(0, 166, 251, 0.18), transparent 14rem),
        radial-gradient(circle at 94% 88%, rgba(255, 140, 0, 0.12), transparent 12rem),
        var(--app-signal-ribbon-surface),
        var(--app-surface-2);
}

.auth-brand-panel::after {
    content: "";
    position: absolute;
    right: -5rem;
    bottom: -7rem;
    width: 18rem;
    height: 18rem;
    border: 1px solid rgba(0, 120, 212, 0.18);
    border-radius: 50%;
    background: radial-gradient(circle, var(--app-surface-2), rgba(0, 120, 212, 0.05) 58%, transparent 60%);
    pointer-events: none;
}

.auth-brand-panel > * {
    position: relative;
    z-index: 1;
}

/* The pill that used to sit here held a bi-stars icon and the bare product name. It is a BrandLockup now
   and brings its own geometry, so what is left for the panel to say is how large the mark runs on the
   page that is the app's largest brand moment. */
.auth-brand-panel .brand-lockup {
    --brand-mark-size: 3rem;
}

.auth-brand-title {
    max-width: 32rem;
    margin: 1.25rem 0 0.75rem;
    color: var(--app-text-primary);
    font-family: var(--app-font-display);
    font-size: clamp(2rem, 4vw, 3.25rem);
    font-weight: 700;
    line-height: 1.05;
    letter-spacing: -0.04em;
}

.auth-brand-copy {
    max-width: 34rem;
    margin: 0;
    color: var(--app-text-secondary);
    font-size: 1.05rem;
    line-height: 1.6;
}

.auth-signal-list {
    display: grid;
    gap: 0.75rem;
    margin: 0;
}

.auth-signal-item {
    display: flex;
    align-items: flex-start;
    gap: 0.75rem;
    padding: 0.85rem;
    border: 1px solid rgba(0, 120, 212, 0.14);
    border-radius: var(--app-radius-lg);
    background: var(--app-surface-2);
    box-shadow: var(--app-shadow-sm);
}

.auth-signal-icon {
    display: inline-flex;
    align-items: center;
    justify-content: center;
    flex: 0 0 auto;
    width: 2.25rem;
    height: 2.25rem;
    border-radius: var(--app-radius-lg);
    background: var(--app-signal-ribbon-soft);
    color: var(--app-accent-deep);
    font-size: 1.1rem;
}

.auth-signal-title {
    display: block;
    color: var(--app-text-primary);
    font-weight: 700;
}

.auth-signal-copy {
    display: block;
    margin-top: 0.15rem;
    color: var(--app-text-secondary);
    font-size: 0.925rem;
    line-height: 1.4;
}

.auth-form-panel {
    display: flex;
    flex-direction: column;
    justify-content: center;
    padding: clamp(1.5rem, 4vw, 3rem);
    background:
        linear-gradient(180deg, var(--app-surface-0), var(--app-surface-1)),
        var(--app-surface-0);
}

.auth-form-header {
    margin-bottom: 1.5rem;
}

.auth-form-eyebrow {
    display: inline-flex;
    align-items: center;
    gap: 0.4rem;
    margin-bottom: 0.75rem;
    color: var(--app-accent-secondary);
    font-size: 0.8125rem;
    font-weight: 700;
    letter-spacing: 0.04em;
    text-transform: uppercase;
}

.auth-form-title {
    margin: 0 0 0.5rem;
    color: var(--app-text-primary);
    font-family: var(--app-font-display);
    font-size: clamp(1.65rem, 3vw, 2.2rem);
    font-weight: 700;
    letter-spacing: -0.03em;
}

.auth-form-subtitle {
    margin: 0;
    color: var(--app-text-secondary);
    line-height: 1.55;
}

.auth-form-panel .form-label {
    color: var(--app-text-primary);
    font-weight: 700;
}

.auth-form-panel .form-control {
    min-height: 2.75rem;
    border-color: var(--app-border-subtle);
    background-color: var(--app-surface-0);
}

.auth-form-panel .btn-primary {
    min-height: 2.75rem;
    font-weight: 700;
}

.auth-dev-alert,
.auth-status-alert {
    border-radius: var(--app-radius-lg);
}

.auth-dev-alert {
    border-color: rgba(0, 120, 212, 0.22);
    background:
        var(--app-signal-ribbon-surface),
        #eef7ff;
    color: var(--app-accent-deep);
}

.auth-route-link {
    margin-top: 1.25rem;
    padding-top: 1rem;
    border-top: 1px solid var(--app-border-subtle);
    color: var(--app-text-secondary);
    text-align: center;
}

.auth-route-link a {
    font-weight: 700;
    text-decoration: none;
}

.auth-route-link a:hover {
    text-decoration: underline;
}

.auth-success-panel {
    padding: 1rem;
    border: 1px solid rgba(16, 124, 16, 0.28);
    border-radius: var(--app-radius-lg);
    background: linear-gradient(135deg, var(--app-surface-0), #f4fff4);
}

@media (max-width: 991.98px) {
    .auth-entry__shell {
        grid-template-columns: 1fr;
    }

    .auth-brand-panel {
        min-height: auto;
        border-right: 0;
        border-bottom: 1px solid var(--app-border-subtle);
    }

    .auth-signal-list {
        grid-template-columns: repeat(3, minmax(0, 1fr));
    }
}

@media (max-width: 575.98px) {
    .auth-entry {
        min-height: auto;
        padding: 0.75rem 0;
    }

    .auth-entry__shell {
        border-radius: var(--app-radius-lg);
    }

    .auth-brand-panel,
    .auth-form-panel {
        padding: 1.25rem;
    }

    .auth-brand-title {
        font-size: 2rem;
    }

    .auth-signal-list {
        grid-template-columns: 1fr;
    }
}

/* ===== Focus ===== */
h1:focus {
    outline: none;
}

/* ===== Form Validation ===== */
.valid.modified:not([type=checkbox]) {
    outline: 1px solid var(--app-success);
}

.invalid {
    outline: 1px solid var(--app-danger);
}

.validation-message {
    color: var(--app-danger);
}

/* ===== Error Boundary ===== */
.blazor-error-boundary {
    background: url(data:image/svg+xml;base64,PHN2ZyB3aWR0aD0iNTYiIGhlaWdodD0iNDkiIHhtbG5zPSJodHRwOi8vd3d3LnczLm9yZy8yMDAwL3N2ZyIgeG1sbnM6eGxpbms9Imh0dHA6Ly93d3cudzMub3JnLzE5OTkveGxpbmsiIG92ZXJmbG93PSJoaWRkZW4iPjxkZWZzPjxjbGlwUGF0aCBpZD0iY2xpcDAiPjxyZWN0IHg9IjIzNSIgeT0iNTEiIHdpZHRoPSI1NiIgaGVpZ2h0PSI0OSIvPjwvY2xpcFBhdGg+PC9kZWZzPjxnIGNsaXAtcGF0aD0idXJsKCNjbGlwMCkiIHRyYW5zZm9ybT0idHJhbnNsYXRlKC0yMzUgLTUxKSI+PHBhdGggZD0iTTI2My41MDYgNTFDMjY0LjcxNyA1MSAyNjUuODEzIDUxLjQ4MzcgMjY2LjYwNiA1Mi4yNjU4TDI2Ny4wNTIgNTIuNzk4NyAyNjcuNTM5IDUzLjYyODMgMjkwLjE4NSA5Mi4xODMxIDI5MC41NDUgOTIuNzk1IDI5MC42NTYgOTIuOTk2QzI5MC44NzcgOTMuNTEzIDI5MSA5NC4wODE1IDI5MSA5NC42NzgyIDI5MSA5Ny4wNjUxIDI4OS4wMzggOTkgMjg2LjYxNyA5OUwyNDAuMzgzIDk5QzIzNy45NjMgOTkgMjM2IDk3LjA2NTEgMjM2IDk0LjY3ODIgMjM2IDk0LjM3OTkgMjM2LjAzMSA5NC4wODg2IDIzNi4wODkgOTMuODA3MkwyMzYuMzM4IDkzLjAxNjIgMjM2Ljg1OCA5Mi4xMzE0IDI1OS40NzMgNTMuNjI5NCAyNTkuOTYxIDUyLjc5ODUgMjYwLjQwNyA1Mi4yNjU4QzI2MS4yIDUxLjQ4MzcgMjYyLjI5NiA1MSAyNjMuNTA2IDUxWk0yNjMuNTg2IDY2LjAxODNDMjYwLjczNyA2Ni4wMTgzIDI1OS4zMTMgNjcuMTI0NSAyNTkuMzEzIDY5LjMzNyAyNTkuMzEzIDY5LjYxMDIgMjU5LjMzMiA2OS44NjA4IDI1OS4zNzEgNzAuMDg4N0wyNjEuNzk1IDg0LjAxNjEgMjY1LjM4IDg0LjAxNjEgMjY3LjgyMSA2OS43NDc1QzI2Ny44NiA2OS43MzA5IDI2Ny44NzkgNjkuNTg3NyAyNjcuODc5IDY5LjMxNzkgMjY3Ljg3OSA2Ny4xMTgyIDI2Ni40NDggNjYuMDE4MyAyNjMuNTg2IDY2LjAxODNaTTI2My41NzYgODYuMDU0N0MyNjEuMDQ5IDg2LjA1NDcgMjU5Ljc4NiA4Ny4zMDA1IDI1OS43ODYgODkuNzkyMSAyNTkuNzg2IDkyLjI4MzcgMjYxLjA0OSA5My41Mjk1IDI2My41NzYgOTMuNTI5NSAyNjYuMTE2IDkzLjUyOTUgMjY3LjM4NyA5Mi4yODM3IDI2Ny4zODcgODkuNzkyMSAyNjcuMzg3IDg3LjMwMDUgMjY2LjExNiA4Ni4wNTQ3IDI2My41NzYgODYuMDU0N1oiIGZpbGw9IiNGRkU1MDAiIGZpbGwtcnVsZT0iZXZlbm9kZCIvPjwvZz48L3N2Zz4=) no-repeat 1rem/1.8rem, #b32121;
    padding: 1rem 1rem 1rem 3.7rem;
    color: white;
}

    .blazor-error-boundary::after {
        content: "An error has occurred."
    }

/* ===== Misc ===== */
.darker-border-checkbox.form-check-input {
    border-color: var(--app-text-tertiary);
}

.form-floating > .form-control-plaintext::placeholder, .form-floating > .form-control::placeholder {
    color: var(--bs-secondary-color);
    text-align: end;
}

.form-floating > .form-control-plaintext:focus::placeholder, .form-floating > .form-control:focus::placeholder {
    text-align: start;
}

/* ===== Content HTML rendering in cards ===== */
.content-html h3 {
    font-size: 1rem;
    font-weight: 600;
    margin-top: 1rem;
    margin-bottom: 0.5rem;
}

.content-html h3:first-child {
    margin-top: 0;
}

.content-html ul, .content-html ol {
    margin-bottom: 0.5rem;
    padding-left: 1.5rem;
}

.content-html p {
    margin-bottom: 0.5rem;
}

/* ===== Long-form assistance ===== */
.wizard-command-bar,
.sticky-command-bar {
    position: sticky;
    bottom: 0;
    z-index: 20;
    padding: 0.75rem;
    margin-inline: -0.75rem;
    background:
        linear-gradient(180deg, var(--app-surface-0), var(--app-surface-1)),
        var(--app-surface-0);
    border-top: 1px solid var(--app-border-subtle);
    box-shadow: 0 -8px 22px rgba(23, 32, 51, 0.08);
    backdrop-filter: blur(10px);
}

.text-pre-wrap {
    white-space: pre-wrap;
}

.action-feedback-stack {
    position: fixed;
    right: 1rem;
    bottom: 1rem;
    z-index: 1080;
    width: min(28rem, calc(100vw - 2rem));
    max-height: calc(100vh - 2rem);
    overflow-y: auto;
    pointer-events: none;
}

.action-feedback-stack .alert {
    pointer-events: auto;
}

.import-command-scope {
    padding-bottom: 4.5rem;
}

.import-command-bar {
    border-radius: 0.5rem 0.5rem 0 0;
}

.dashboard-summary-card,
.dashboard-command-card,
.dashboard-search-card,
.dashboard-insight-card,
.dashboard-continue-learning-card,
.learning-progress-card,
.learning-step-card,
.learning-topic-card,
.learning-assistant-card {
    border-radius: var(--app-radius-lg);
}

/* The second stop is --app-surface-1, not the literal it used to be. var(--app-surface-1)
   composited over --app-surface-0 in the light theme IS #f8fbff, which is exactly what --app-surface-1
   holds - so this was the token's own value typed out by hand with an alpha to make it look like a
   wash. Light is unchanged to the pixel.

   In dark it was a defect no light-theme review could see: a 180deg ramp ending at 96% WHITE, so the
   taller the card the further it washed out. The recent-work list is the tallest card on the page and
   faded from #1a202a at its header to near-silver at its last row. Hovering a row then painted the
   opaque --app-surface-hover over the wash and the row went DARK - the giveaway the owner reported. */
.dashboard-summary-card,
.dashboard-continue-learning-card,
.dashboard-search-card,
.dashboard-recent-card,
.dashboard-insight-card {
    background:
        linear-gradient(180deg, var(--app-surface-0), var(--app-surface-1)),
        var(--app-surface-0);
}

.dashboard-summary-card .card-body {
    display: grid;
    gap: 0.55rem;
    min-height: 10rem;
    padding: 1.15rem 1.15rem 1rem 1.35rem;
}

.dashboard-command-card {
    color: var(--app-text-primary);
    transition:
        transform var(--app-motion-fast) var(--app-motion-ease),
        box-shadow var(--app-motion-fast) var(--app-motion-ease),
        border-color var(--app-motion-fast) var(--app-motion-ease);
}

.dashboard-command-card .card-body {
    display: flex;
    flex-direction: column;
}

.dashboard-command-card:hover {
    color: var(--app-text-primary);
    border-color: var(--app-accent-primary);
    box-shadow: var(--app-shadow-md);
    transform: translateY(-2px);
}

.dashboard-summary-icon,
.learning-topic-icon {
    display: inline-flex;
    align-items: center;
    justify-content: center;
    width: 2.25rem;
    height: 2.25rem;
    border-radius: var(--app-radius-lg);
    background: var(--app-signal-ribbon-soft);
    color: var(--app-accent-deep);
    font-size: 1.2rem;
}

.dashboard-continue-learning-card .card-body,
.dashboard-search-card .card-body {
    padding: 1.25rem;
}


.dashboard-summary-value {
    font-size: 2rem;
    font-weight: 700;
    line-height: 1.1;
    color: var(--app-text-primary);
}

.dashboard-stat-strip {
    display: grid;
    grid-template-columns: repeat(auto-fit, minmax(11rem, 1fr));
    gap: 0.75rem;
}

.dashboard-stat {
    display: flex;
    align-items: center;
    gap: 0.6rem;
    padding: 0.65rem 0.9rem;
    border: 1px solid var(--app-border-subtle);
    border-radius: var(--app-radius-lg);
    background: var(--app-surface-0);
    color: var(--app-text-primary);
    text-decoration: none;
    transition:
        border-color var(--app-motion-fast) var(--app-motion-ease),
        background-color var(--app-motion-fast) var(--app-motion-ease);
}

.dashboard-stat:hover {
    color: var(--app-text-primary);
    border-color: var(--app-accent-primary);
    background: var(--app-surface-hover);
}

.dashboard-stat__icon {
    font-size: 1.3rem;
    line-height: 1;
}

.dashboard-stat__value {
    font-family: var(--app-font-display);
    font-size: 1.4rem;
    font-weight: 700;
    line-height: 1;
    letter-spacing: -0.015em;
    margin-left: 0.1rem;
}

.dashboard-stat__label {
    font-size: 0.82rem;
    color: var(--app-text-secondary);
}

.dashboard-stat__filter {
    margin-left: auto;
    font-size: 0.85rem;
    color: var(--app-text-tertiary);
}

.dashboard-tabs {
    gap: 0.25rem;
    border-bottom-color: var(--app-border-subtle);
}

.dashboard-tabs .nav-link {
    color: var(--app-text-secondary);
}

.dashboard-tabs .nav-link:hover {
    color: var(--app-text-primary);
}

.dashboard-tabs .nav-link.active {
    color: var(--app-text-primary);
    font-weight: 600;
}

/* S3 v2: the "find & filter" zone groups the counter chips with the search box above the tabs. The chips are
   narrowed (content-sized, packed left) so they read as compact filters, not full-width cards. */
.dashboard-find .dashboard-stat-strip {
    margin-bottom: 0;
    grid-template-columns: repeat(auto-fit, minmax(7.5rem, max-content));
    justify-content: start;
    gap: 0.5rem;
}

.dashboard-find .dashboard-stat {
    padding: 0.4rem 0.7rem;
}

.dashboard-find .dashboard-stat__icon {
    font-size: 1.1rem;
}

.dashboard-find .dashboard-stat__value {
    font-size: 1.15rem;
}

/* S3 v2: the compact Quick-actions bar — icon buttons with a short label and a descriptive tooltip. */
.dashboard-shortcut-bar {
    display: flex;
    flex-wrap: wrap;
    gap: 0.5rem;
}

.dashboard-shortcut-btn {
    display: inline-flex;
    align-items: center;
    gap: 0.45rem;
    padding: 0.4rem 0.75rem;
    border: 1px solid var(--app-border-subtle);
    border-radius: var(--app-radius-lg);
    background: var(--app-surface-0);
    color: var(--app-text-primary);
    text-decoration: none;
    font-size: 0.85rem;
    font-weight: 600;
    transition:
        border-color var(--app-motion-fast) var(--app-motion-ease),
        background var(--app-motion-fast) var(--app-motion-ease);
}

.dashboard-shortcut-btn:hover,
.dashboard-shortcut-btn:focus-visible {
    border-color: var(--app-text-secondary);
    background: var(--app-surface-hover);
    color: var(--app-text-primary);
}

.dashboard-shortcut-btn .bi {
    font-size: 1rem;
}

.dashboard-search-card .form-label {
    color: var(--app-text-primary);
    font-weight: 700;
}

.dashboard-search-results {
    border-top: 1px solid var(--app-border-subtle);
    padding-top: 1rem;
}

.dashboard-result-link:first-child {
    border-top: 1px solid var(--app-border-subtle);
}

.dashboard-empty-state {
    min-height: 11rem;
}

.dashboard-recent-card .card-header,
.dashboard-insight-card .card-body {
    position: relative;
}

.dashboard-recent-card .list-group-item,
.dashboard-search-results .list-group-item {
    padding-block: 0.95rem;
}

.dashboard-insight-card {
    transition:
        border-color var(--app-motion-fast) var(--app-motion-ease),
        box-shadow var(--app-motion-fast) var(--app-motion-ease),
        transform var(--app-motion-fast) var(--app-motion-ease);
}

.dashboard-insight-card:hover {
    border-color: rgba(0, 120, 212, 0.26);
    box-shadow: var(--app-shadow-md);
    transform: translateY(-1px);
}

.learning-topic-icon {
    color: var(--app-accent-primary);
}

.learning-progress-ring {
    display: inline-flex;
    align-items: center;
    justify-content: center;
    flex: 0 0 auto;
    width: 4rem;
    height: 4rem;
    border: 0.35rem solid rgba(0, 120, 212, 0.22);
    border-radius: 50%;
    background: var(--app-surface-0);
    color: var(--app-accent-secondary);
    box-shadow: inset 0 0 0 1px rgba(0, 166, 251, 0.12);
    font-weight: 700;
}

.learning-progress-ring-sm {
    width: 3rem;
    height: 3rem;
    border-width: 0.25rem;
    font-size: 0.85rem;
}

.learning-progress-native {
    display: block;
    width: 100%;
    max-width: 24rem;
    height: 0.65rem;
    accent-color: var(--app-accent-primary);
}

.learning-next-topic {
    padding: 0.75rem;
    border: 1px solid var(--app-border-subtle);
    border-radius: var(--app-radius-lg);
    background: var(--app-surface-1);
}

.learning-topic-card {
    transition:
        border-color var(--app-motion-fast) var(--app-motion-ease),
        box-shadow var(--app-motion-fast) var(--app-motion-ease);
}

.learning-topic-complete {
    border-color: rgba(16, 124, 16, 0.35);
    background: linear-gradient(135deg, var(--app-surface-0), #f4fff4);
}

/* ===== Assistant panel =====
   The shell used to carry a floating pill in the bottom-right corner, which is exactly where a data table
   keeps its actions column — it covered Delete and "Mark as Local Copy" on whatever row sat under it, and
   a different row as the reader scrolled. The trigger moved into the command bar (AssistantLauncher); what
   is left here is the panel, which now hangs from the bar that opens it rather than from a corner button.

   A panel the reader has just asked for is allowed to cover content — it is a response to their click and
   it closes. A control that sits there permanently is not. */
.assistant-shell {
    position: fixed;
    right: 1.25rem;
    top: calc(var(--app-topbar-height) + 0.5rem);
    bottom: 1.25rem;
    z-index: var(--app-assistant-z-index);
    display: flex;
    flex-direction: column;
    align-items: flex-end;
    gap: 0.75rem;
    pointer-events: none;
}

.assistant-drawer {
    pointer-events: auto;
}

.assistant-drawer {
    width: min(27rem, calc(100vw - 2rem));
    max-height: min(42rem, calc(100vh - 7rem));
    overflow: hidden;
    border: 1px solid transparent;
    border-radius: var(--app-radius-xl);
    background:
        linear-gradient(var(--app-surface-0), var(--app-surface-0)) padding-box,
        var(--app-signal-ribbon) border-box;
    box-shadow: var(--app-shadow-lg);
}

.assistant-drawer-header {
    position: relative;
    display: flex;
    align-items: flex-start;
    justify-content: space-between;
    gap: 1rem;
    overflow: hidden;
    padding: 1rem 1rem 0.95rem;
    border-bottom: 1px solid var(--app-border-subtle);
    background:
        radial-gradient(circle at 15% 15%, rgba(0, 166, 251, 0.16), transparent 9rem),
        var(--app-signal-ribbon-surface),
        var(--app-surface-1);
}

.assistant-title-wrap,
.assistant-close-button {
    position: relative;
    z-index: 1;
}

.assistant-drawer-eyebrow {
    display: inline-flex;
    align-items: center;
    gap: 0.35rem;
    margin-bottom: 0.35rem;
    color: var(--app-accent-deep);
    font-size: 0.72rem;
    font-weight: 800;
    letter-spacing: 0.08em;
    text-transform: uppercase;
}

.assistant-drawer-title {
    margin: 0;
    color: var(--app-text-primary);
    font-family: var(--app-font-display);
    font-size: 1.12rem;
    font-weight: 750;
    letter-spacing: -0.02em;
}

.assistant-drawer-subtitle {
    margin-top: 0.25rem;
    color: var(--app-text-secondary);
    font-size: 0.84rem;
}

.assistant-close-button {
    border-color: rgba(0, 120, 212, 0.22);
    background: var(--app-surface-2);
}

.assistant-drawer-body {
    display: grid;
    gap: 0.85rem;
    max-height: calc(min(42rem, 100vh - 7rem) - 5rem);
    overflow-y: auto;
    padding: 1rem;
}

.assistant-disclosure {
    border: 1px solid var(--app-border-subtle);
    border-radius: var(--app-radius-lg);
    background: var(--app-surface-1);
    overflow: hidden;
}

.assistant-disclosure-summary {
    display: flex;
    align-items: center;
    justify-content: space-between;
    gap: 0.5rem;
    padding: 0.6rem 0.85rem;
    cursor: pointer;
    list-style: none;
    color: var(--app-text-secondary);
    font-size: 0.82rem;
    font-weight: 650;
}

.assistant-disclosure-summary::-webkit-details-marker {
    display: none;
}

.assistant-disclosure-summary-text {
    display: inline-flex;
    align-items: center;
    gap: 0.4rem;
}

.assistant-disclosure-summary-text i {
    color: var(--app-accent-primary);
}

.assistant-disclosure-chevron {
    font-size: 0.8rem;
    transition: transform var(--app-motion-fast) var(--app-motion-ease);
}

.assistant-disclosure[open] .assistant-disclosure-chevron {
    transform: rotate(180deg);
}

.assistant-disclosure-body {
    padding: 0 0.85rem 0.75rem;
    color: var(--app-text-secondary);
    font-size: 0.84rem;
}

.assistant-status-panel {
    position: relative;
    display: flex;
    align-items: flex-start;
    gap: 0.75rem;
    overflow: hidden;
    padding: 0.85rem;
    border: 1px solid rgba(0, 120, 212, 0.16);
    border-radius: var(--app-radius-lg);
    background:
        var(--app-signal-ribbon-surface),
        var(--app-surface-1);
    color: var(--app-text-secondary);
    font-size: 0.86rem;
}

.assistant-status-panel strong {
    display: block;
    color: var(--app-text-primary);
}

.assistant-status-icon {
    display: inline-flex;
    align-items: center;
    justify-content: center;
    flex: 0 0 auto;
    width: 2.25rem;
    height: 2.25rem;
    border-radius: var(--app-radius-lg);
    background: var(--app-surface-2);
    color: var(--app-accent-deep);
    box-shadow: var(--app-shadow-sm);
}

.assistant-section-card,
.assistant-response-panel {
    border: 1px solid var(--app-border-subtle);
    border-radius: var(--app-radius-lg);
    background: var(--app-surface-0);
    box-shadow: var(--app-shadow-sm);
}

.assistant-section-card {
    padding: 0.85rem;
}

.assistant-section-title {
    display: flex;
    align-items: center;
    gap: 0.45rem;
    margin: 0 0 0.5rem;
    color: var(--app-text-primary);
    font-family: var(--app-font-display);
    font-size: 0.95rem;
    font-weight: 750;
}

.assistant-section-title i {
    color: var(--app-accent-primary);
}

.assistant-safety-list {
    padding-left: 1.15rem;
}

.assistant-transcript-panel {
    display: flex;
    flex-direction: column;
    gap: 0.75rem;
}

.assistant-transcript-header {
    display: flex;
    align-items: center;
    justify-content: space-between;
    gap: 0.5rem;
}

.assistant-transcript-list {
    list-style: none;
    margin: 0;
    padding: 0;
    display: flex;
    flex-direction: column;
    gap: 0.85rem;
}

.assistant-turn {
    display: flex;
    flex-direction: column;
    gap: 0.5rem;
}

.assistant-turn-question,
.assistant-turn-answer {
    padding: 0.6rem 0.75rem;
    border: 1px solid var(--app-border-subtle);
    border-radius: var(--app-radius-lg);
}

.assistant-turn-question {
    background: var(--app-surface-1);
}

.assistant-turn-answer {
    background: var(--app-surface-0);
}

.assistant-turn-role {
    display: inline-flex;
    align-items: center;
    font-size: 0.72rem;
    font-weight: 800;
    letter-spacing: 0.04em;
    text-transform: uppercase;
    color: var(--app-text-secondary);
}

.assistant-turn-text {
    margin-top: 0.25rem;
    color: var(--app-text-primary);
    font-size: 0.88rem;
    white-space: pre-wrap;
    overflow-wrap: anywhere;
}

.assistant-safety-list li::marker {
    color: var(--app-accent-primary);
}

.assistant-chat-panel .form-label {
    color: var(--app-text-secondary);
    font-weight: 700;
}

.assistant-message-input {
    min-height: 6rem;
    resize: vertical;
}

.assistant-question-actions {
    flex-wrap: wrap;
}

.assistant-loading-preview,
.assistant-empty-state,
.assistant-error-panel {
    margin: 0;
}

.assistant-response-panel {
    position: relative;
    display: grid;
    gap: 0.75rem;
    overflow: hidden;
    padding: 0.85rem;
}

.assistant-response-header {
    display: grid;
    gap: 0.4rem;
}

.assistant-response-meta {
    display: flex;
    align-items: center;
    flex-wrap: wrap;
    gap: 0.4rem;
    color: var(--app-text-secondary);
    font-size: 0.78rem;
}

.assistant-response {
    padding: 0.75rem;
    border: 1px solid var(--app-border-subtle);
    border-radius: var(--app-radius-lg);
    background: var(--app-surface-1);
}

.assistant-response :is(h4, h5) {
    margin: 0.75rem 0 0.35rem;
    color: var(--app-text-primary);
    font-family: var(--app-font-display);
    font-size: 0.96rem;
}

.assistant-response :is(pre, code) {
    border-radius: var(--app-radius-sm);
    background: var(--app-code-bg);
}

.assistant-reference-grid {
    display: grid;
    gap: 0.75rem;
}

.assistant-reference-card {
    padding: 0.75rem;
    border: 1px solid var(--app-border-subtle);
    border-radius: var(--app-radius-lg);
    background: var(--app-surface-1);
}

.assistant-reference-title {
    margin: 0 0 0.45rem;
    color: var(--app-text-primary);
    font-size: 0.85rem;
    font-weight: 750;
}

.assistant-reference-list {
    margin-bottom: 0;
    padding-left: 1rem;
    color: var(--app-text-secondary);
}

@media (max-width: 991.98px) {

    .wizard-command-bar,
    .sticky-command-bar {
        position: static;
        margin-inline: 0;
        border: 1px solid var(--app-border-subtle);
        border-radius: 0.5rem;
        box-shadow: var(--app-shadow-sm);
    }

    .import-command-scope {
        padding-bottom: 1rem;
    }

    .import-command-bar {
        border-radius: 0.5rem;
    }
}

@media (max-width: 575.98px) {
    .action-feedback-stack {
        right: 0.75rem;
        bottom: 0.75rem;
        left: 0.75rem;
        width: auto;
        max-height: 45vh;
    }

    .assistant-shell {
        right: 0.75rem;
        bottom: 0.75rem;
        left: 0.75rem;
    }

    .assistant-drawer {
        width: 100%;
    }

}

/* S12 feedback launcher is now a top-bar control beside the notification bell (the trigger + dropdown live in
   FeedbackLauncher.razor.css, mirroring NotificationBell). It is no longer a bottom-left FAB, so it needs no
   global fixed-position fallback here. */

/* ===== Explorer workspace — CSS grid layout ===== */
.explorer-rail-card,
.explorer-table-card {
    border-radius: var(--app-radius-xl);
}

/* #8: compact filter toolbar replaces the old full-width "Filter workspace" card. */
.explorer-toolbar {
    display: flex;
    flex-wrap: wrap;
    align-items: center;
    gap: 0.6rem;
    padding: 0.75rem 0.9rem;
    border: 1px solid var(--app-border-subtle);
    border-radius: var(--app-radius-lg);
    background: var(--app-surface-0);
}

.explorer-toolbar__search {
    flex: 1 1 16rem;
    min-width: 12rem;
}

.explorer-toolbar__select {
    flex: 0 1 11rem;
    min-width: 8rem;
    width: auto;
}

.explorer-toolbar__reset {
    flex: 0 0 auto;
    margin-left: auto;
}

/* The five selects live behind this. They occupied a second full row before the reader saw a single work
   item, and most visits filter by nothing. */
.explorer-toolbar__filters {
    flex: 0 0 auto;
    display: inline-flex;
    align-items: center;
    gap: 0.35rem;
}

/* The count is the whole reason collapsing is safe: it says how much is being hidden. */
.explorer-filter-count {
    display: inline-flex;
    align-items: center;
    justify-content: center;
    min-width: 1.25rem;
    height: 1.25rem;
    padding-inline: 0.3rem;
    border-radius: var(--app-radius-pill);
    background: var(--app-accent-primary);
    color: #ffffff;
    font-size: 0.7rem;
    font-weight: 700;
    font-variant-numeric: tabular-nums;
}

/* The disclosure takes the whole row when open, so the selects keep the shape they always had. */
.explorer-toolbar__fields {
    flex: 1 1 100%;
    display: flex;
    flex-wrap: wrap;
    align-items: center;
    gap: 0.6rem;
}

/* What is APPLIED is never hidden. Each chip names its field, because "Bug" alone does not say whether
   the reader filtered by type or searched for the word. */
.explorer-applied {
    display: flex;
    flex-wrap: wrap;
    align-items: center;
    gap: 0.4rem;
}

.explorer-applied__chip {
    display: inline-flex;
    align-items: center;
    gap: 0.35rem;
    padding: 0.25rem 0.55rem;
    border: 1px solid var(--app-border-subtle);
    border-radius: var(--app-radius-pill);
    background: var(--app-surface-2);
    color: var(--app-text-secondary);
    font-size: 0.78rem;
    cursor: pointer;
    transition:
        border-color var(--app-motion-fast) var(--app-motion-ease),
        color var(--app-motion-fast) var(--app-motion-ease);
}

.explorer-applied__chip:hover,
.explorer-applied__chip:focus-visible {
    border-color: var(--app-danger);
    color: var(--app-danger);
}

/* The chips carry three kinds of narrowing now — a toolbar select, a manual folder, an organization group.
   They behave identically (each removes itself), so the kind is carried by the icon alone rather than by a
   second row or a second colour. It is decoration over the field name, so it inherits the tertiary ink and
   goes with the chip on hover. */
.explorer-applied__icon {
    font-size: 0.72rem;
    color: var(--app-text-tertiary);
}

.explorer-applied__chip:hover .explorer-applied__icon,
.explorer-applied__chip:focus-visible .explorer-applied__icon {
    color: inherit;
}

.explorer-applied__field {
    font-size: 0.66rem;
    letter-spacing: 0.06em;
    text-transform: uppercase;
    color: var(--app-text-tertiary);
}

.explorer-applied__value {
    font-weight: 600;
    color: var(--app-text-primary);
}

/* S1 (WI UX refinements): the per-row action buttons share one spaced flex group so they never crowd/overlap. */
/* A facet whose count is zero still says something — "none in what you are looking at" — and removing it
   would make the strip change shape as filters move, with the chips jumping under the pointer. So it is
   muted rather than dropped: the ink follows the state, the target stays put, and it remains clickable,
   which is what ExplorerFacetCountRenderTests pins. */
.sb-chip--empty {
    opacity: 0.55;
}

.sb-chip--empty:hover,
.sb-chip--empty:focus-visible,
.sb-chip--empty.active {
    opacity: 1;
}

/* One row shape for every row. flex-wrap was letting a labelled "Mark as Local Copy" break onto a second
   line, so rows linked to Azure DevOps stood taller than local-only ones and the extra height read as extra
   importance. The actions are icon buttons now and they stay on one line. */
.explorer-row-actions {
    display: flex;
    flex-wrap: nowrap;
    align-items: center;
    justify-content: flex-end;
    gap: 0.375rem;
}

/* A destructive control should not be the loudest thing in a resting row - six red buttons at rest are risk
   surface that buys nothing, doubly so where a floating control can cover them.

   visibility, not display or opacity: it keeps the cell's width stable so revealing the button cannot shift
   the row under the pointer, and unlike opacity:0 it leaves nothing invisible still able to take a click.
   It also removes the button from the tab order while hidden, which is why the reveal keys off :focus-within
   on the ROW - the reader reaches the row through its checkbox or title link first, and by the time this
   control is next in the tab order it is already visible. */
.explorer-row-delete {
    visibility: hidden;
}

tr:hover .explorer-row-delete,
tr:focus-within .explorer-row-delete {
    visibility: visible;
}

/* Where there is no hover there is no reveal, so on touch it simply stays visible. */
@media (hover: none) {
    .explorer-row-delete {
        visibility: visible;
    }
}

/* The folder cell was narrow enough to truncate its own value ("Uncateg...") while restating it in full
   underneath. The restatement is gone; this stops the select from clipping what it is showing. */
.explorer-folder-select {
    min-width: 9rem;
}

/* Relative timestamps are short; the column no longer needs to wrap a full instant onto two lines. */
.explorer-updated-cell {
    white-space: nowrap;
}

/* #8: the Explorer work-item table spans full width; the "Group & folders" drawer chrome (backdrop, off-canvas
   panel, pinned column, header) is now the shared .side-* rules below — the drawer migrated to the shared
   PinnableSideDrawer component (Slice 3 of the shared-drawer refactor). */

/* #8: sortable table headers + paging footer. */
.explorer-sort-btn {
    display: inline-flex;
    align-items: center;
    gap: 0.3rem;
    padding: 0;
    border: 0;
    background: none;
    color: inherit;
    font: inherit;
    font-weight: 600;
    cursor: pointer;
}

.explorer-sort-btn:hover {
    color: var(--app-accent-primary);
}

.explorer-sort-btn .bi {
    font-size: 0.8em;
    color: var(--app-text-tertiary);
}

.explorer-paging {
    border-top: 1px solid var(--app-border-subtle);
}

/* #9: read-only work item tree + preview. */
.explorer-tree-layout {
    display: grid;
    grid-template-columns: minmax(0, 1.6fr) minmax(0, 1fr);
    gap: 1rem;
    align-items: start;
}

.explorer-tree-preview {
    position: sticky;
    top: var(--app-sticky-top, 4.25rem);
}

.explorer-tree-search {
    flex: 1 1 12rem;
    min-width: 9rem;
}

.explorer-tree,
.explorer-tree-group {
    list-style: none;
    margin: 0;
    padding: 0;
}

.explorer-tree-group {
    padding-left: 1.1rem;
}

.explorer-tree-node {
    display: flex;
    align-items: center;
    gap: 0.4rem;
    width: 100%;
    padding: 0.25rem 0.4rem;
    border-radius: var(--app-radius-md);
}

.explorer-tree-node--root,
.explorer-tree-node__label {
    border: 0;
    background: none;
    color: var(--app-text-primary);
    font: inherit;
    text-align: left;
    cursor: pointer;
}

.explorer-tree-node__label {
    display: flex;
    align-items: center;
    gap: 0.4rem;
    width: 100%;
    padding: 0;
}

.explorer-tree-node--root:hover,
.explorer-tree-node__label:hover {
    color: var(--app-accent-primary);
}

.explorer-tree-node--leaf {
    cursor: grab;
    color: var(--app-text-secondary);
}

.explorer-tree-node--leaf:hover {
    background: var(--app-surface-hover);
}

.explorer-tree-node--leaf.active {
    background: var(--app-nav-active-bg);
    border: 1px solid var(--app-nav-active-border);
}

.explorer-tree-droppable:hover {
    background: var(--app-surface-hover);
    outline: 1px dashed var(--app-accent-primary);
}

.explorer-tree-twisty {
    width: 1rem;
    text-align: center;
    color: var(--app-text-tertiary);
}

.explorer-tree-grip {
    color: var(--app-text-tertiary);
}

.explorer-tree-leaf-title {
    min-width: 0;
}

.explorer-tree-empty {
    padding: 0.2rem 0.6rem;
}

@media (max-width: 992px) {
    .explorer-tree-layout {
        grid-template-columns: 1fr;
    }

    .explorer-tree-preview {
        position: static;
    }
}

.explorer-rail-card .card-header,
.explorer-table-header {
    background:
        linear-gradient(180deg, var(--app-surface-0), var(--app-surface-1)),
        var(--app-surface-1);
}

.explorer-rail-card .card-body {
    padding: 0.9rem;
}

/* ===== Shared off-canvas side drawer =================================================================
   Neutral .side-* chrome for the shared PinnableSideDrawer component
   (RFC design-shared-pinnable-side-drawer-2026-07-14): overlay + light-dismiss backdrop, Escape/focus a11y,
   pin-to-column with a responsive <lg revert. Used by the Audit Explorer "Recent audit activity" drawer and
   the WI Explorer "Group & folders" drawer; the earlier per-page .explorer-drawer* / bespoke audit rules were
   unified here. ==================================================================================== */
.side-host {
    min-width: 0;
}

.side-host__main {
    min-width: 0;
}

.side-drawer {
    position: fixed;
    top: 0;
    right: 0;
    z-index: 1045;
    width: min(23rem, 92vw);
    height: 100vh;
    padding: 1rem;
    overflow-y: auto;
    overscroll-behavior: contain;
    background: var(--app-surface-0);
    border-left: 1px solid var(--app-border-subtle);
    box-shadow: 0 8px 32px rgba(23, 43, 77, 0.22);
    transform: translateX(100%);
    visibility: hidden;
    transition: transform var(--app-motion-standard) var(--app-motion-ease);
}

.side-drawer--open {
    transform: translateX(0);
    visibility: visible;
}

.side-drawer__header {
    display: flex;
    align-items: center;
    justify-content: space-between;
    gap: 0.75rem;
    margin-bottom: 1rem;
}

.side-drawer-backdrop {
    position: fixed;
    inset: 0;
    z-index: 1040;
    background: rgba(23, 43, 77, 0.42);
}

@media (prefers-reduced-motion: reduce) {
    .side-drawer {
        transition: none;
    }
}

/* Pinned: dock the drawer as an in-flow sticky column with the list flexing to fill (Slice 2). */
.side-host--pinned {
    display: flex;
    align-items: flex-start;
    gap: 1rem;
}

.side-host--pinned .side-host__main {
    flex: 1 1 auto;
    min-width: 0;
    order: 1;
}

.side-drawer--pinned {
    position: sticky;
    top: var(--app-sticky-top, 4.25rem);
    right: auto;
    z-index: auto;
    order: 2;
    flex: 0 0 22rem;
    width: 22rem;
    height: auto;
    max-height: calc(100vh - var(--app-sticky-top, 4.25rem) - 1rem);
    transform: none;
    visibility: visible;
    border: 1px solid var(--app-border-subtle);
    border-radius: var(--app-radius-lg);
    box-shadow: var(--app-shadow-sm);
}

/* Below lg the pinned column is not viable: revert the host to block flow (list full width) and render a
   restored pin as a hidden off-canvas drawer, so a persisted pin never covers small-screen content (RFC 3.5).
   The --open overlay still opens the feed at every width. */
@media (max-width: 991.98px) {
    .side-host--pinned {
        display: block;
    }

    .side-drawer--pinned {
        position: fixed;
        top: 0;
        right: 0;
        z-index: 1045;
        order: 0;
        flex: none;
        width: min(23rem, 92vw);
        height: 100vh;
        max-height: none;
        transform: translateX(100%);
        visibility: hidden;
        border: 0;
        border-left: 1px solid var(--app-border-subtle);
        border-radius: 0;
        box-shadow: 0 8px 32px rgba(23, 43, 77, 0.22);
    }

    .side-drawer--pinned.side-drawer--open {
        transform: translateX(0);
        visibility: visible;
    }
}

.explorer-rail-card .btn-group-vertical {
    gap: 0.35rem;
}

.explorer-rail-card .btn-group-vertical > .btn {
    border-radius: var(--app-radius-md);
}

/* ===== Explorer folder panel — contrast fixes ===== */
/* WCAG AA: white (#fff) on #0078d4 = 4.65:1 — passes AA */
.explorer-folder-panel .list-group-flush {
    display: grid;
    gap: 0.35rem;
}

.explorer-folder-panel .list-group-flush > .list-group-item {
    border: 1px solid var(--app-border-subtle);
    border-radius: var(--app-radius-md);
    background-color: var(--app-surface-0);
}

.explorer-folder-panel .list-group-item.active,
.explorer-folder-panel .list-group-item-action.active {
    background-color: var(--app-accent-primary);
    border-color: var(--app-accent-primary);
    color: #ffffff;
}

.explorer-folder-panel .list-group-item.active .btn-link,
.explorer-folder-panel .list-group-item-action.active .btn-link {
    color: #ffffff;
}

.explorer-folder-count-badge {
    background-color: var(--app-text-tertiary);
    color: #ffffff;
}

.explorer-folder-panel .list-group-item.active .explorer-folder-count-badge,
.explorer-folder-panel .list-group-item-action.active .explorer-folder-count-badge {
    background-color: rgba(255, 255, 255, 0.18);
    color: #ffffff;
}

.explorer-folder-panel .list-group-item.active .btn-outline-danger {
    color: var(--app-surface-0);
    border-color: rgba(255, 255, 255, 0.55);
}

.explorer-folder-panel .list-group-item.active .btn-outline-danger:hover {
    background-color: rgba(255, 255, 255, 0.18);
}

/* Create folder input group — ensure white surface, not blue bleed */
.explorer-folder-panel .input-group .form-control {
    background-color: var(--app-surface-0);
    color: var(--app-text-primary);
}

.explorer-selection-alert {
    border-color: rgba(0, 120, 212, 0.18);
    background:
        var(--app-signal-ribbon-surface),
        var(--app-surface-1);
}

/* ===== Explorer results table — sticky header & row density ===== */
.explorer-results-table-wrap {
    max-height: calc(100vh - var(--app-sticky-top, 4.25rem) - 9rem);
    overflow-y: auto;
    overscroll-behavior: contain;
}

.explorer-table-card {
    min-width: 0;
}

.explorer-results-table-wrap .table thead th {
    position: sticky;
    top: 0;
    z-index: 2;
    background-color: var(--app-surface-1);
    border-bottom: 2px solid var(--app-border-strong);
    font-size: 0.8125rem;
    text-transform: uppercase;
    letter-spacing: 0.04em;
    color: var(--app-text-secondary);
    white-space: nowrap;
}

.explorer-results-table-wrap .table tbody tr:hover {
    background-color: var(--app-nav-hover-bg);
}

.explorer-title-cell {
    max-width: 22rem;
}

/* The derived one-line summary under a title. Clamped to one line: it is a glance, not the content -
   the full text is on the element's title attribute, and the item itself is one click away. */
.explorer-summary-cell {
    display: -webkit-box;
    -webkit-line-clamp: 1;
    -webkit-box-orient: vertical;
    overflow: hidden;
    word-break: break-word;
}

.explorer-title-cell a {
    display: -webkit-box;
    -webkit-line-clamp: 2;
    -webkit-box-orient: vertical;
    overflow: hidden;
    word-break: break-word;
}

.explorer-empty-surface {
    min-height: 16rem;
    justify-content: center;
}

/* ===== Explorer inline WI preview (expand-in-place peek) ===== */
/* Dedicated leading column for the expand control — narrow, centered (E3: discoverability). */
.explorer-expander-col {
    width: 2.75rem;
    text-align: center;
}

.explorer-preview-toggle {
    color: var(--app-text-secondary);
    line-height: 1;
}

/* A clearly-sized chevron (right when collapsed, down when expanded). */
.explorer-preview-toggle .bi {
    font-size: 1.15rem;
}

.explorer-preview-toggle:hover,
.explorer-preview-toggle[aria-expanded="true"] {
    color: var(--app-accent-primary);
}

/* The preview cell carries its own surface + separator, so it reads as a child panel of the row above it.
   Compound selector so the left indent beats Bootstrap's `.table > :not(caption) > * > *` cell padding. */
.explorer-results-table .explorer-preview-cell {
    padding: 0.75rem 1rem 1rem 2.25rem;
    background-color: var(--app-surface-1);
    border-bottom: 2px solid var(--app-border-strong);
}

.wi-preview {
    max-width: 60rem;
}

/* ===== Work Item detail — sticky tab navigation ===== */
.wi-detail-tabs-wrapper {
    position: sticky;
    top: var(--app-sticky-top, 4.25rem);
    z-index: 15;
    background: var(--app-surface-0);
    border-bottom: 1px solid var(--app-border-subtle);
    padding-top: 0.5rem;
    /* bleed past the content inline padding to span full article width */
    margin-inline: calc(-1 * var(--app-content-inline-padding));
    padding-inline: var(--app-content-inline-padding);
    box-shadow: 0 8px 18px rgba(23, 32, 51, 0.08);
}

.wi-detail-tabs-wrapper .nav-tabs {
    border-bottom: none;
    flex-wrap: nowrap;
    overflow-x: auto;
    -ms-overflow-style: none;
    scrollbar-width: none;
}

.wi-detail-tabs-wrapper .nav-tabs::-webkit-scrollbar {
    display: none;
}

.wi-detail-tab-panel {
    padding-top: 1.25rem;
}

/* ===== Quill / RichTextEditor — app theme ===== */
.ql-toolbar.ql-snow {
    background-color: var(--app-surface-1);
    border-color: var(--app-border-subtle);
    border-radius: 0.375rem 0.375rem 0 0;
}

.ql-container.ql-snow {
    background-color: var(--app-surface-0);
    border-color: var(--app-border-subtle);
    border-radius: 0 0 0.375rem 0.375rem;
    color: var(--app-text-primary);
    font-family: var(--app-font-body);
    font-size: 0.9375rem;
}

.ql-container.ql-snow .ql-editor {
    min-height: 8rem;
    line-height: 1.6;
}

.rich-text-editor-host--comfortable .ql-container.ql-snow .ql-editor {
    min-height: 10rem;
}

.ql-container.ql-snow .ql-editor.ql-blank::before {
    color: var(--app-text-tertiary);
    font-style: normal;
}

/* Quill toolbar buttons — inherit app focus ring */
.ql-toolbar.ql-snow .ql-formats button:focus-visible,
.ql-toolbar.ql-snow .ql-picker:focus-visible {
    outline: 2px solid var(--app-border-focus);
    outline-offset: 2px;
    border-radius: 2px;
}

/* Quill editor card — remove extra margin added by .mb-3 wrapper */
.card-body.p-0 > .mb-3 {
    margin-bottom: 0;
}

.card-body.p-0 > .mb-3 > div[id^="rte-"] {
    border-radius: 0 0 calc(0.375rem - 1px) calc(0.375rem - 1px);
}

/* ===== Responsive adjustments for explorer layout ===== */
@media (max-width: 767.98px) {
}

@media (max-width: 991.98px) {
    .explorer-rail {
        position: static;
        max-height: none;
    }

    .wi-detail-tabs-wrapper {
        margin-inline: calc(-1 * var(--app-content-inline-padding-sm));
        padding-inline: var(--app-content-inline-padding-sm);
    }
}

@media (prefers-reduced-motion: reduce) {
    *,
    *::before,
    *::after {
        animation-duration: 0.01ms !important;
        animation-iteration-count: 1 !important;
        scroll-behavior: auto !important;
        transition-duration: 0.01ms !important;
        transition-delay: 0ms !important;
    }

    .app-fade-in-up {
        animation: none !important;
    }
}

/* RFC-014 — the Tools (feature palette) drawer is a touch wider than the shared .side-drawer default (23rem),
   capped so it stays usable on narrow viewports. Targets the DrawerId the ExplorerDetail Tools pane sets on the
   shared PinnableSideDrawer (Slice 5 of the shared-drawer refactor); the id beats the .side-drawer class width. */
#wi-tools-drawer {
    width: min(34rem, 92vw);
}

/* System-safety S1 — global z-index fallback for the confirmation modal. The confirm is the most blocking
   overlay in the app, so it must sit strictly above all app chrome AND the toast host (z-index: 1090). Global
   fallback per the blazor rule for fixed UI; component detail lives in ConfirmHost.razor.css. */
.confirm-host .modal-backdrop {
    z-index: 1095;
}

.confirm-host .modal {
    z-index: 1096;
}

/* S11 Slice 5 — global positioning fallback for the notification toast host (per the blazor rule for
   fixed floating UI): keep the stack pinned bottom-right above app chrome even if the component's scoped
   CSS is stale or missing, so toasts never render inside the normal layout flow. Visual detail lives in
   ToastHost.razor.css. */
.notif-toast-host {
    position: fixed;
    right: 1rem;
    bottom: 1rem;
    z-index: 1090;
    width: min(22rem, calc(100vw - 2rem));
}

/* =====================================================================
   SIGNAL BOARD — vendored verbatim from
   docs/design-assets/signal-board/signal-board.css (SHA-256 3E8869BF, see RFC).
   Adoption RFC: docs/design-signal-board-visual-refresh-2026-07-10.md
   (Claude<->GPT cross-model consensus REACHED). Purely additive — edits
   belong in the vendored source, not here. Slice A.
   ===================================================================== */
/* =====================================================================
   SIGNAL BOARD — 1b rollout
   Append to: src/.../Web/wwwroot/app.css
   Pure additive. Uses existing Azure Cockpit tokens (--app-*). No new
   dependencies, no JS. Safe to ship incrementally, page by page.
   ===================================================================== */

/* ---- KPI tile: the headline metric card (Dashboard, QA, Explorer summaries) ---- */
.sb-kpi {
    position: relative;
    overflow: hidden;
    display: block;
    padding: 1.05rem 1.15rem;
    border: 1px solid var(--app-border-subtle);
    border-radius: var(--app-radius-lg);
    background:
        linear-gradient(180deg, var(--app-surface-0), var(--app-surface-1)),
        var(--app-surface-0);
    box-shadow: var(--app-shadow-sm);
    color: var(--app-text-primary);
    text-decoration: none;
    transition:
        border-color var(--app-motion-fast) var(--app-motion-ease),
        box-shadow var(--app-motion-fast) var(--app-motion-ease),
        transform var(--app-motion-fast) var(--app-motion-ease);
}

.sb-kpi:hover,
.sb-kpi:focus-visible {
    color: var(--app-text-primary);
    border-color: rgba(0, 120, 212, 0.3);
    box-shadow: var(--app-shadow-md);
    transform: translateY(-2px);
}

.sb-kpi__top {
    display: flex;
    align-items: center;
    justify-content: space-between;
    gap: 0.5rem;
}

.sb-kpi__icon {
    display: inline-flex;
    align-items: center;
    justify-content: center;
    width: 2.2rem;
    height: 2.2rem;
    border-radius: var(--app-radius-md);
    font-size: 1.1rem;
}

.sb-kpi__value {
    margin-top: 0.7rem;
    font-family: var(--app-font-display);
    font-size: 2.1rem;
    font-weight: 700;
    line-height: 1;
    letter-spacing: -0.02em;
}

.sb-kpi__value small {
    font-size: 1rem;
    color: var(--app-text-tertiary);
}

.sb-kpi__label {
    margin-top: 0.25rem;
    color: var(--app-text-secondary);
    font-size: 0.85rem;
}

/* Sparkline — a row of <i> bars, height set inline (0–100%). Decorative only. */
.sb-kpi__spark {
    display: flex;
    align-items: flex-end;
    gap: 2px;
    height: 1.5rem;
    margin-top: 0.55rem;
}

.sb-kpi__spark i {
    flex: 1;
    border-radius: 2px;
    opacity: 0.5;
    background: var(--sb-accent, var(--app-accent-primary));
}

@media (prefers-reduced-motion: reduce) {
    .sb-kpi { transition: none; }
    .sb-kpi:hover, .sb-kpi:focus-visible { transform: none; }
}

/* ---- Segmented control: replaces .nav-tabs for in-page view switches ----
   Markup stays a set of <button>s; keep your existing click handlers
   (e.g. Dashboard SetActiveTab). Just add .active to the current one. */
.sb-seg {
    display: inline-flex;
    gap: 0.2rem;
    padding: 0.25rem;
    background: var(--app-surface-2);
    border: 1px solid var(--app-border-subtle);
    border-radius: var(--app-radius-pill);
}

.sb-seg button {
    border: 0;
    background: transparent;
    border-radius: var(--app-radius-pill);
    padding: 0.35rem 0.95rem;
    font-family: var(--app-font-display);
    font-size: 0.85rem;
    font-weight: 600;
    color: var(--app-text-secondary);
    cursor: pointer;
    transition:
        background-color var(--app-motion-fast) var(--app-motion-ease),
        color var(--app-motion-fast) var(--app-motion-ease);
}

.sb-seg button:hover { color: var(--app-text-primary); }

.sb-seg button.active {
    background: var(--app-surface-0);
    color: var(--app-accent-deep);
    box-shadow: var(--app-shadow-sm);
}

.sb-seg button:focus-visible {
    outline: 2px solid var(--app-border-focus);
    outline-offset: 2px;
}

/* ---- Activity timeline: recent-activity rails (Dashboard, item detail, QA history) ---- */
.sb-timeline {
    list-style: none;
    margin: 0;
    padding: 0;
    position: relative;
}

.sb-timeline::before {
    content: "";
    position: absolute;
    top: 0.4rem;
    bottom: 0.4rem;
    left: 0.65rem;
    width: 2px;
    background: var(--app-border-subtle);
}

.sb-tl-item {
    position: relative;
    padding: 0.55rem 0 0.55rem 2rem;
}

.sb-tl-item::before {
    content: "";
    position: absolute;
    left: 0.35rem;
    top: 0.85rem;
    width: 0.65rem;
    height: 0.65rem;
    border-radius: 50%;
    background: var(--sb-accent, var(--app-accent-primary));
    border: 2px solid var(--app-surface-0);
    box-shadow: 0 0 0 1px var(--app-border-subtle);
}

/* =====================================================================
   SIGNAL BOARD — compaction (post-UAT): compact quick-filter chip.
   A slim horizontal counterpart to .sb-kpi for the counter/filter role
   (PO-directed; NOT part of the vendored signal-board.css handoff).
   Adoption RFC: docs/design-signal-board-compaction-2026-07-10.md.
   ===================================================================== */
.sb-chip-row {
    display: flex;
    flex-wrap: wrap;
    gap: 0.5rem;
}

.sb-chip {
    display: inline-flex;
    align-items: center;
    gap: 0.5rem;
    padding: 0.3rem 0.8rem 0.3rem 0.45rem;
    border: 1px solid var(--app-border-subtle);
    border-radius: var(--app-radius-pill);
    background: var(--app-surface-0);
    box-shadow: var(--app-shadow-sm);
    color: var(--app-text-primary);
    text-decoration: none;
    transition:
        border-color var(--app-motion-fast) var(--app-motion-ease),
        box-shadow var(--app-motion-fast) var(--app-motion-ease);
}

.sb-chip:hover,
.sb-chip:focus-visible {
    color: var(--app-text-primary);
    border-color: rgba(0, 120, 212, 0.3);
    box-shadow: var(--app-shadow-md);
}

.sb-chip:focus-visible {
    outline: 2px solid var(--app-border-focus);
    outline-offset: 2px;
}

/* W3 (Workspaces IA): the Explorer filter chips are <button> (in-page filter actions, not links). Reset the
   button chrome so they match the anchor/div chips, and show the pointer only on the interactive ones (the
   display-only Total chip is a <div> and keeps the default cursor). */
button.sb-chip {
    font: inherit;
    cursor: pointer;
}

/* Active filter chip: accent the border from the chip's own --sb-accent so the applied filter reads at a glance. */
.sb-chip.active {
    border-color: var(--sb-accent, var(--app-border-focus));
    box-shadow: var(--app-shadow-md);
}

/* Icon colour + tint are set inline per chip (matching the accent map), so this is layout only. */
.sb-chip__icon {
    display: inline-flex;
    align-items: center;
    justify-content: center;
    width: 1.6rem;
    height: 1.6rem;
    border-radius: var(--app-radius-md);
    font-size: 0.9rem;
}

.sb-chip__value {
    font-family: var(--app-font-display);
    font-size: 1.05rem;
    font-weight: 700;
    line-height: 1;
    letter-spacing: -0.01em;
}

.sb-chip__label {
    color: var(--app-text-secondary);
    font-size: 0.8rem;
}

/* ===== Dashboard "Command header" (2b) — design-command-header-dashboard-2026-07-10.md =====
   Pure additive. Uses existing Azure Cockpit --app-* tokens only. No new deps, no JS. */
/* background-color under the gradient: it is the colour the white text is actually guaranteed against,
   and without it a gradient that does not paint leaves white on white. */
.cmd-header { position: relative; overflow: hidden; border-radius: var(--app-radius-xl); padding: 1.25rem 1.5rem; color: #fff;
    background-color: #013a67;
    background: radial-gradient(circle at 92% -30%, rgba(255,140,0,0.5), transparent 40%),
                radial-gradient(circle at 70% 130%, rgba(0,166,251,0.5), transparent 55%),
                linear-gradient(120deg, #013a67 0%, #0060b0 48%, #0079d6 100%);
    background-color: #013a67;
    box-shadow: var(--app-shadow-hero); }
.cmd-header__kicker { font-family: var(--app-font-display); font-size: 0.72rem; font-weight: 700; letter-spacing: 0.12em; text-transform: uppercase; color: rgba(255,255,255,0.92); }
.cmd-header__title { font-family: var(--app-font-display); font-weight: 700; font-size: 1.6rem; letter-spacing: -0.02em; margin-top: 2px; }
.cmd-search { display: flex; align-items: center; gap: 0.6rem; padding: 0.6rem 0.9rem; border-radius: var(--app-radius-lg); background: rgba(255,255,255,0.95); color: var(--app-text-tertiary); }
.cmd-search input { flex: 1; border: 0; outline: 0; background: transparent; font-size: 0.95rem; color: var(--app-text-primary); }
.btn-on-hero { background: rgba(255,255,255,0.16); border: 1px solid rgba(255,255,255,0.3); color: #fff; font-weight: 600; }
.btn-on-hero:hover { background: rgba(255,255,255,0.26); color: #fff; }
.pulse-strip { display: flex; align-items: center; gap: 1.5rem; flex-wrap: wrap; }
.pulse-item { display: flex; align-items: center; gap: 0.55rem; text-decoration: none; }
.pulse-dot { width: 0.6rem; height: 0.6rem; border-radius: 50%; flex: 0 0 auto; background: var(--p-accent, var(--app-accent-primary)); box-shadow: 0 0 0 3px color-mix(in srgb, var(--p-accent, var(--app-accent-primary)) 16%, transparent); }
.pulse-num { font-family: var(--app-font-display); font-weight: 700; font-size: 1.3rem; letter-spacing: -0.02em; line-height: 1; }
.pulse-label { font-size: 0.74rem; text-transform: uppercase; letter-spacing: 0.05em; }
.pulse-sep { width: 1px; align-self: stretch; background: rgba(255,255,255,0.28); }
.cmd-header .pulse-num { color: #fff; }
.cmd-header .pulse-label { color: rgba(255,255,255,0.75); }
.rw-list { display: flex; flex-direction: column; }
.rw-item { display: flex; gap: 0.85rem; align-items: center; padding: 0.8rem 1rem; border-top: 1px solid var(--app-border-subtle); text-decoration: none; color: var(--app-text-primary); position: relative; transition: background-color var(--app-motion-fast) var(--app-motion-ease); }
.rw-item:first-child { border-top: 0; }
.rw-item:hover { background: var(--app-surface-hover); color: var(--app-text-primary); }
.rw-tick { position: absolute; left: 0; top: 0.65rem; bottom: 0.65rem; width: 3px; border-radius: 999px; background: var(--rw, var(--app-accent-primary)); }
/* The tint mixes against --app-surface-0, the card's own ground, NOT a hardcoded #fff. It was #fff, and
   in light that is the same colour, so the defect was invisible for as long as anyone only looked at
   light: on the dark theme the chip came out 86% white and this list alone wore a silver square while
   every other list stayed dark. A tint is only a tint relative to what it sits on. */
.rw-icon { width: 2.1rem; height: 2.1rem; flex: 0 0 auto; border-radius: var(--app-radius-md); display: inline-flex; align-items: center; justify-content: center; font-size: 1.05rem; background: color-mix(in srgb, var(--rw, var(--app-accent-primary)) 14%, var(--app-surface-0)); color: var(--rw, var(--app-accent-primary)); }
.rw-body { flex: 1 1 auto; min-width: 0; }
/* Two lines, not one: a User Story title opens with "As a user I want to…", so one line showed the
   words every story shares and cut the words that tell them apart. The row also names the whole
   statement on hover (Dashboard.razor, title=). */
.rw-title { font-weight: 600; line-height: 1.3; overflow: hidden; text-overflow: ellipsis; display: -webkit-box; -webkit-line-clamp: 2; -webkit-box-orient: vertical; }
.rw-meta { font-size: 0.8rem; color: var(--app-text-tertiary); margin-top: 0.15rem; }
.rw-meta b { color: var(--app-text-secondary); font-weight: 600; }
.rw-right { flex: 0 0 auto; }
.goto-grid { display: grid; grid-template-columns: 1fr 1fr; gap: 0.5rem; }
@media (prefers-reduced-motion: reduce) { .rw-item { transition: none; } }
@media (max-width: 575.98px) { .pulse-strip { gap: 1rem; } .cmd-header { padding: 1rem 1.1rem; } }

/* Work Queue (design-dashboard-redefinition-2026-09-11.md S5.2): lanes of NAMED items. Replaces the
   Needs-attention rules, which styled counts. Adapted from them so the card keeps the cockpit vocabulary. */
.queue-lane { border-top: 1px solid var(--app-border-subtle); padding: 0.5rem 0 0.6rem; }
.queue-lane:first-child { border-top: 0; }
.queue-lane__heading { font-family: var(--app-font-display); font-size: 0.68rem; font-weight: 700;
    letter-spacing: 0.08em; text-transform: uppercase; color: var(--app-text-tertiary);
    margin: 0 0 0.15rem; padding: 0 1rem; }
.queue-row { display: flex; align-items: baseline; flex-wrap: wrap; gap: 0.5rem; padding: 0.4rem 1rem;
    text-decoration: none; color: var(--app-text-primary); }
a.queue-row:hover { background: var(--app-surface-hover); color: var(--app-text-primary); }
.queue-row__title { font-weight: 600; min-width: 0; }
.queue-row__meta { font-size: 0.8rem; flex: 1 1 auto; min-width: 0; }
.queue-lane__overflow { display: inline-block; padding: 0.15rem 1rem 0; }

/* The hygiene footer is deliberately subordinate to the lanes above it: tidying is a sweep, not a queue. */
.queue-tidy { display: flex; flex-wrap: wrap; gap: 0.5rem; align-items: baseline;
    border-top: 1px solid var(--app-border-subtle); padding: 0.5rem 1rem; }

/* My work insights (S6): the relocated tiles, below the working area rather than above it. */
.dashboard-my-insights-card .stacked-bar__svg { overflow: visible; }
.dashboard-activity-window { width: auto; min-width: 6.5rem; }

/* What survives of the retired Next-best-action card: the learning recommendation, as one rail line. */
.dashboard-learning-line { color: var(--app-text-secondary); padding: 0.35rem 0.1rem; }

/* ── The console ─────────────────────────────────────────────────────────────────────────────────
   A dashboard that scrolls has stopped being one: you are meant to read it, not travel through it.
   So the PAGE never scrolls. The three instruments sit in one viewport-height grid, and the two that
   hold lists scroll INSIDE their own frame, the way a gauge has a bounded face.

   The height is the viewport minus the app chrome above it: the topbar (--app-topbar-height) plus
   .app-content's 1.35rem top and 2rem bottom padding. min-height is the escape hatch — on a short
   window the instruments stop shrinking and the page scrolls rather than crushing them. */
/* The page is one viewport-height flex column: the deck and the deck rail take what they need, the
   console takes the rest. Computing the console's height from constants instead meant guessing the
   deck's height, and the guess pushed the rail below the fold — the exact failure this replaces.
   The only subtraction left is the app chrome: the topbar plus .app-content's 1.35rem/2rem padding. */
.dash-page {
    display: flex;
    flex-direction: column;
    height: calc(100dvh - var(--app-topbar-height) - 3.35rem);
    min-height: 34rem;
}

/* The deck and the rail are content-sized; only the console flexes. Without this the deck is a
   shrinkable flex item and the console eats into it, clipping the search row off the bottom. */
.dash-page > .cmd-header,
.dash-page > .dash-deckrail { flex: 0 0 auto; }

.dash-console {
    display: grid;
    grid-template-columns: minmax(0, 1.25fr) minmax(0, 1fr) minmax(0, 0.95fr);
    gap: 0.85rem;
    flex: 1 1 auto;
    min-height: 0;
    margin-top: 0.85rem;
}

.dash-column { display: flex; flex-direction: column; gap: 0.85rem; min-height: 0; min-width: 0; }

/* Every instrument is a bounded frame: a header that stays put and a body that scrolls within it. */
.dash-column > section { display: flex; flex-direction: column; min-height: 0; flex: 1 1 auto; }
.dash-column > section > .card { display: flex; flex-direction: column; min-height: 0; height: 100%; }
.dash-column > section > .card > .card-header { flex: 0 0 auto; }
/* scrollbar-gutter keeps the track reserved whether or not it is needed, so a bounded list reads as
   SCROLLABLE rather than as one that was cut off — and the rows do not shift when it appears. Chosen
   over a fade mask because a mask has to match the panel's background, and this app has two themes. */
.dash-column > section > .card > .card-body {
    flex: 1 1 auto;
    min-height: 0;
    overflow-y: auto;
    overscroll-behavior: contain;
    scrollbar-gutter: stable;
    scrollbar-width: thin;
}

/* The rail is the signals instrument alone (the inbox card left for the top-bar envelope on 2026-09-12:
   two instruments did not fit the rail at a laptop height). Charts are never put in a scrolling frame:
   a bar chart that scrolls under its own header is not a chart, it is a list. The card fills the column
   so the rail has no hole; its body sits at its natural height inside it, unscrolled. The readouts
   stack at the full rail width, one under the other. */
.dash-column--rail > #dashboard-my-insights > .card > .card-body { flex: 1 1 auto; overflow-y: visible; }
.dashboard-insight + .dashboard-insight { margin-top: 1.1rem; }
.dashboard-insight-caption { margin: 0.1rem 0 0.35rem; line-height: 1.35; }

/* A scrollable frame should say so before you reach its edge. */
.dash-column > section > .card > .card-body::-webkit-scrollbar { width: 0.5rem; }
.dash-column > section > .card > .card-body::-webkit-scrollbar-thumb {
    background: var(--app-border-strong); border-radius: 1rem; }

/* The deck rail: navigation and the learning line on one line, so neither costs an instrument. */
.dash-deckrail { display: flex; align-items: center; justify-content: space-between; flex-wrap: wrap;
    gap: 0.75rem; margin-top: 0.85rem; min-height: 2.4rem; }
.dash-deckrail #dashboard-shortcuts { margin: 0; }
.dash-deckrail #dashboard-shortcuts h2 { display: none; } /* the icons and labels already say it */
.dash-deckrail .goto-grid { display: flex; flex-wrap: wrap; gap: 0.4rem; }
.dash-deckrail .dashboard-shortcut-btn { padding: 0.3rem 0.7rem; }
.dash-deckrail #dashboard-learning-next { margin: 0; }

/* Panel headers hold a title and a control or two; they must stay on one line rather than wrapping
   a two-word link onto its own row and stealing height from the list below. */
.dash-column > section > .card > .card-header { flex-wrap: nowrap; white-space: nowrap; gap: 0.5rem; }
.dash-column > section > .card > .card-header > .cockpit-panel__title { min-width: 0; overflow: hidden; text-overflow: ellipsis; }
.dash-column .dashboard-recent-count { width: auto; min-width: 3.9rem; }

/* Signals is the densest instrument in the narrowest column: give its readouts room to breathe and
   let the type chips wrap under the bar rather than fighting it for the same line. */
.dash-column--rail .dashboard-my-insights-card .card-body { padding: 0.75rem 0.9rem; }
.dash-column--rail .stacked-bar { margin-top: 0.15rem; }
.dash-column--rail .stacked-bar__legend { row-gap: 0.1rem; }

/* First run: neither the queue nor the signals have anything to say, so the console drops to one
   column rather than leaving holes where instruments should be — the ragged-column problem this
   layout exists to remove. The recent panel stands alone and carries the invitation. */
.dash-console--solo { grid-template-columns: minmax(0, 1fr); }

/* Instrument titles are h2s for the outline; visually they stay the bold card title they were. */
.cockpit-panel__title { font-size: 1rem; font-weight: 700; margin: 0; line-height: inherit; }

/* The deck is one band, not two rows: on a console every millimetre it takes the instruments lose. */
.cmd-header--deck { padding-block: 0.85rem; }
.cmd-header--deck .cmd-header__identity { display: flex; flex-direction: column; gap: 0.05rem; }
.cmd-header--deck .cmd-header__actions { margin-top: 0.7rem; }

@media (max-width: 1399.98px) {
    /* Two instruments abreast, and the page scrolls again. Below this width there is no honest way to
       hold three bounded lists AND keep their rows readable, so the console stops promising one screen
       rather than delivering three unreadable slivers of it. */
    .dash-console { grid-template-columns: minmax(0, 1.2fr) minmax(0, 1fr); }
    .dash-column--rail { grid-column: 1 / -1; flex-direction: row; }
    .dash-column--rail > section { flex: 1 1 0; }
    .dash-page { height: auto; min-height: 0; }
    /* The rail is now a full-width band, so the readouts sit abreast again — here the width IS the
       viewport's, which is the one case a viewport breakpoint can honestly speak to. */
    .dash-column--rail .dashboard-my-insights-card .card-body { display: flex; gap: 1.5rem; }
    .dash-column--rail .dashboard-insight { flex: 1 1 0; min-width: 0; }
    .dashboard-insight + .dashboard-insight { margin-top: 0; }
}

@media (max-width: 767.98px) {
    /* A phone has no viewport to fill: unstack, let the page scroll, and let each panel be its own
       height. Fixed frames on a small screen produce three tiny windows onto three long lists. */
    .dash-console { grid-template-columns: minmax(0, 1fr); }
    .dash-column--rail { flex-direction: column; }
    .dash-column--rail .dashboard-my-insights-card .card-body { flex-direction: column; }
    .dashboard-insight + .dashboard-insight { margin-top: 1.1rem; }
    .dash-column > section > .card > .card-body { overflow-y: visible; }
    .dash-deckrail { justify-content: flex-start; }
}

/* A short window is the same problem as a phone: stop shrinking, start scrolling. */
@media (max-height: 44rem) and (min-width: 1400px) {
    .dash-page { height: auto; min-height: 0; }
}

/* dashboard-insights-fix Slice 2: chart legibility — a sparkline axis frame, so the chart is readable on its
   own (the PO: the score line was an abstract curve). The donut legend rules that sat here went with the
   DonutChart component: both consumers now render a compact stacked bar. */

/* The sparkline host is layout-neutral (display:contents) when there is no axis, so every existing bare-sparkline
   usage renders unchanged; the framed variant stacks the trend over a compact axis caption. */
.sparkline-host { display: contents; }
.sparkline-frame { display: flex; flex-direction: column; gap: 0.15rem; min-width: 0; }
.sparkline-frame .sparkline { flex: 0 0 auto; }
.sparkline-frame__axis { display: flex; justify-content: space-between; gap: 0.5rem;
    font-size: 0.68rem; color: var(--app-text-tertiary); font-variant-numeric: tabular-nums; }
.sparkline-frame__range { font-weight: 700; color: var(--app-text-secondary); }

/* Insights composition legend (I-3): each legend entry is a WorkItemTypeChip — the SAME swatch colour the
   donut arc is drawn with, because both now read WorkItemVisuals. Before the consolidation the legend
   badge and its own arc could disagree (the badge came from GetTypeBadgeClass, the arc from
   GetTypeAccent). The anchor supplies only the affordance; the chip supplies the vocabulary. */
.insights-type-badge {
    display: inline-flex;
    align-items: center;
    gap: 0.35rem;
    padding: 0.15rem 0.5rem;
    border: 1px solid var(--app-border-subtle);
    border-radius: var(--app-radius-pill);
    background: var(--app-surface-0);
}

.insights-type-badge:hover {
    border-color: var(--app-accent-primary);
    background: var(--app-surface-hover);
}

.insights-type-count {
    font-family: var(--app-font-mono);
    font-size: 0.75rem;
    font-variant-numeric: tabular-nums;
    color: var(--app-text-secondary);
}

/* dashboard-insights-fix Slice 3 (I-6): the QA quality counters as one dense inline strip, not four big tiles. */
.qa-stat-strip { display: flex; flex-wrap: wrap; align-items: center; gap: 0.35rem 1.3rem; padding: 0.35rem 0.1rem;
    font-size: 0.9rem; color: var(--app-text-secondary); }
.qa-stat { display: inline-flex; align-items: center; gap: 0.35rem; }
.qa-stat__value { font-family: var(--app-font-display); font-weight: 700; font-size: 1.05rem;
    color: var(--app-text-primary); font-variant-numeric: tabular-nums; }

/* ── Sidebar navigation refresh (N1 §5 D2): active-section cue ─────────────────────────────
   The header of the section containing the active page is highlighted — even when that section
   is collapsed. GLOBAL (not in the scoped NavMenu.razor.css) on purpose: the `.active` class is
   set on the anchor rendered by the child NavLink component, which does NOT carry NavMenu's
   CSS-isolation attribute — a scoped `:has(.nav-link.active)` selector could not match it. A
   global rule under the stable `.app-sidebar` wrapper (rendered by MainLayout) sees the composed
   DOM. Design: docs/design-sidebar-nav-refresh-2026-07-11.md (consensus REACHED). */
.app-sidebar .nav-section:has(.nav-link.active) > .nav-section-summary {
    background: var(--app-nav-active-bg);
}

.app-sidebar .nav-section:has(.nav-link.active) > .nav-section-summary .nav-section-title,
.app-sidebar .nav-section:has(.nav-link.active) > .nav-section-summary .nav-section-icon,
.app-sidebar .nav-section:has(.nav-link.active) > .nav-section-summary .nav-section-chevron {
    color: var(--app-accent-deep);
}

/* ── Communication consolidation (§3.2): personal / admin divider ──────────────────────────
   The admin surfaces (Feedback inbox, Announcements) sit in a .nav-comms-admin-group wrapper.
   The top divider renders ONLY when an admin item actually renders (`:has(.nav-link)`), so a
   personal-only user gets no dangling divider. GLOBAL for the same reason as the active-section
   cue above: the child NavLink anchor does not carry NavMenu's CSS-isolation attribute.
   Design: docs/design-communication-announcements-consolidation-2026-07-18.md (consensus REACHED) §3.2. */
.app-sidebar .nav-comms-admin-group:has(.nav-link) {
    margin-top: 0.375rem;
    padding-top: 0.375rem;
    border-top: 1px solid var(--app-border-subtle);
}

/* ── QA trend charts (PO ruling 2026-08-28) ──────────────────────────────────────────────────────────
   Shared by four components, so the tokens live here rather than in any one component's scoped sheet.

   The chart palette is deliberate: the previous charts borrowed Bootstrap's semantic variables, and two of
   the four band fills failed their contrast check against the white panel — band B (--bs-info) measured
   1.96:1 and band C (--bs-warning) 1.63:1 against a 3:1 floor, so half the stack was effectively invisible.
   These steps are validated for the light surface, and every one of them is always paired with a label or a
   legend entry: a status colour never carries meaning on its own.

   Charts repair 2026-09-14 (D1): the tokens sit on :root so the band badges, the report bars, the explorer
   sparkline and the print page paint the same colour as the trend charts — QaPalette is the one map. */
:root {
    --qa-band-a: #0ca30c;
    --qa-band-b: #2a78d6;
    --qa-band-c: #fab219;
    --qa-band-d: #d03b3b;
    --qa-sev-critical: #d03b3b;
    --qa-sev-major: #ec835a;
    --qa-sev-minor: #fab219;
    --qa-sev-info: #6b778c;
    --qa-good: #0ca30c;
    --qa-critical: #d03b3b;
    --qa-neutral: #6b778c;
    --qa-accent: var(--app-accent-primary);
    --qa-grid: var(--app-border-subtle);
    --qa-axis: var(--app-border-strong);
    --qa-ink: var(--app-text-primary);
    --qa-ink-muted: var(--app-text-tertiary);
    --qa-ink-on-fill: #ffffff;
    --qa-ink-on-light: #3a2c05;
    --qa-surface: var(--app-surface-0);
}

/* Band and severity badges on the same tokens as the marks. The token paints a tint and a border; the text
   stays in the ordinary ink, because white on the status green (3.4:1) or the status orange (2.6:1) is not
   readable badge text. A badge always carries its letter or word — the colour is never the only signal. */
.qa-badge-band-a,
.qa-badge-band-b,
.qa-badge-band-c,
.qa-badge-band-d,
.qa-badge-sev-critical,
.qa-badge-sev-major,
.qa-badge-sev-minor,
.qa-badge-sev-info,
.qa-badge-neutral {
    --qa-badge-token: var(--qa-neutral);
    color: var(--qa-ink);
    background: color-mix(in srgb, var(--qa-badge-token) 16%, transparent);
    border: 1px solid var(--qa-badge-token);
    font-weight: 600;
}

.qa-badge-band-a { --qa-badge-token: var(--qa-band-a); }
.qa-badge-band-b { --qa-badge-token: var(--qa-band-b); }
.qa-badge-band-c { --qa-badge-token: var(--qa-band-c); }
.qa-badge-band-d { --qa-badge-token: var(--qa-band-d); }
.qa-badge-sev-critical { --qa-badge-token: var(--qa-sev-critical); }
.qa-badge-sev-major { --qa-badge-token: var(--qa-sev-major); }
.qa-badge-sev-minor { --qa-badge-token: var(--qa-sev-minor); }
.qa-badge-sev-info { --qa-badge-token: var(--qa-sev-info); }

/* The segmented bar (D2): a legend row under every part-to-whole bar carries the count and share for each
   part, so nothing is read from a hover alone. */
.qa-segmented-bar svg { display: block; width: 100%; height: auto; overflow: visible; }
.qa-plot-inset-label--bar { font-size: 11px; font-weight: 600; font-variant-numeric: tabular-nums; }

/* The score histogram (D4): accent columns are the data, the band tints are context, the aggregate is a rule. */
.qa-histogram-bin { fill: var(--qa-accent); }
.qa-histogram-aggregate { stroke: var(--qa-ink); stroke-width: 1.5; }

.qa-plot-caption {
    margin: 0.5rem 0 0;
    font-size: 0.82rem;
    color: var(--app-text-tertiary);
}

.qa-plot svg,
.qa-small-multiple svg,
.qa-slope svg {
    display: block;
    width: 100%;
    height: auto;
    overflow: visible;
}

.qa-plot-grid { stroke: var(--qa-grid); stroke-width: 1; }
.qa-plot-axis { stroke: var(--qa-axis); stroke-width: 1; }

.qa-plot-tick {
    font-size: 11px;
    fill: var(--qa-ink-muted);
    font-variant-numeric: tabular-nums;
}

.qa-plot-band-label { font-size: 11px; font-weight: 600; fill: var(--qa-ink-muted); }
.qa-plot-label { font-size: 12px; fill: var(--qa-ink); }

.qa-plot-value {
    font-size: 12px;
    font-weight: 600;
    fill: var(--qa-ink);
    font-variant-numeric: tabular-nums;
}

.qa-plot-inset-label { font-size: 11px; font-weight: 600; font-variant-numeric: tabular-nums; }

/* A value printed over a mark keeps a surface-coloured halo so it stays legible on any fill. */
.qa-plot-value--halo {
    paint-order: stroke;
    stroke: var(--qa-surface);
    stroke-width: 3px;
    stroke-linejoin: round;
}

.qa-plot-line {
    fill: none;
    stroke: var(--qa-accent);
    stroke-width: 2;
    stroke-linejoin: round;
    stroke-linecap: round;
}

.qa-plot-line--tile { stroke-width: 1.75; }
.qa-plot-dot { fill: var(--qa-surface); stroke: var(--qa-accent); stroke-width: 2; }
.qa-plot-dot--last { fill: var(--qa-accent); stroke: var(--qa-surface); }

.qa-plot-legend {
    display: flex;
    flex-wrap: wrap;
    gap: 0.35rem 1rem;
    margin-top: 0.6rem;
    font-size: 0.85rem;
    color: var(--qa-ink-muted);
}

.qa-plot-legend-item { display: inline-flex; align-items: center; gap: 0.35rem; }
.qa-plot-swatch { width: 0.7rem; height: 0.7rem; border-radius: 2px; display: inline-block; }

/* Small multiples — one tile per series, so identity is a heading rather than a colour. */
.qa-small-multiples {
    display: grid;
    grid-template-columns: repeat(auto-fit, minmax(13rem, 1fr));
    gap: 0.75rem 1rem;
}

.qa-small-multiple {
    margin: 0;
    padding: 0.5rem 0.65rem 0.35rem;
    border: 1px solid var(--app-border-subtle);
    border-radius: 0.5rem;
    background: var(--app-surface-0);
}

.qa-small-multiple--falling { border-color: var(--qa-critical); }
.qa-small-multiple--falling .qa-plot-line { stroke: var(--qa-critical); }
.qa-small-multiple--falling .qa-plot-dot--last { fill: var(--qa-critical); }

.qa-small-multiple-head {
    display: flex;
    align-items: baseline;
    justify-content: space-between;
    gap: 0.5rem;
}

.qa-small-multiple-name { font-size: 0.85rem; color: var(--app-text-secondary); }

.qa-small-multiple-value {
    font-size: 1.05rem;
    font-weight: 600;
    color: var(--app-text-primary);
    font-variant-numeric: tabular-nums;
}

.qa-small-multiples-scale {
    margin: 0.6rem 0 0;
    font-size: 0.82rem;
    color: var(--app-text-tertiary);
}

/* Slope chart — first run to latest, ordered by change. */
.qa-slope-heads {
    display: flex;
    justify-content: space-between;
    max-width: 46rem;
    margin: 0 auto 0.25rem;
    padding: 0 2rem;
    font-size: 0.82rem;
    color: var(--app-text-tertiary);
}

/* The QA trends page: the headline row above the charts. */
.qa-trends-kpis {
    display: grid;
    grid-template-columns: repeat(auto-fit, minmax(12rem, 1fr));
    gap: 1rem;
}

.qa-kpi-label {
    margin: 0 0 0.25rem;
    font-size: 0.75rem;
    letter-spacing: 0.08em;
    text-transform: uppercase;
    color: var(--app-text-tertiary);
}

.qa-kpi-value {
    font-size: 2.1rem;
    font-weight: 600;
    line-height: 1;
    color: var(--app-text-primary);
}

.qa-kpi-delta { font-size: 0.9rem; margin-left: 0.4rem; font-variant-numeric: tabular-nums; }
.qa-kpi-delta--up { color: var(--app-st-pushed); }
.qa-kpi-delta--down { color: var(--app-st-failed); }
.qa-kpi-foot { margin: 0.35rem 0 0; font-size: 0.82rem; color: var(--app-text-tertiary); }

/* Work item detail: the identity id beside the title, and the metadata line under the command bar.
   These kept their wi-detail-hero__* names through the PageShell conversion because two structure
   tests pin them (the language control lives in the always-visible header; the commands own their own
   full-width bar) - the names still describe where they are, so renaming them would only churn. */
/* ===== Work-item identifiers =========================================================================
   An id is not metadata. It is the reference the reader quotes into Azure DevOps, searches the list for,
   and puts in a message to a colleague - so it is set as a first-class, selectable reference: monospace,
   full-contrast, on a tinted chip that makes it findable at a glance. It used to be 12px tertiary text,
   which is the treatment for something you are not meant to read. */
.wi-id,
.wi-detail-id,
.wi-link__id {
    /* inline-block, not inline: an inline element's block padding PAINTS but does not enter the line box,
       so the chip's border overlapped the line above and below it and the ids read as touching. */
    display: inline-block;
    vertical-align: middle;
    font-family: var(--app-font-mono);
    font-size: 0.8125rem;
    font-weight: 600;
    font-variant-numeric: tabular-nums;
    letter-spacing: 0.01em;
    color: var(--app-accent-deep);
    background: var(--app-surface-2);
    border: 1px solid var(--app-border-subtle);
    border-radius: var(--app-radius-sm);
    padding: 0.1875rem 0.5rem;
    line-height: 1.35;
    /* The id shares a cell with a title that wraps, so it needs its own vertical rhythm. */
    margin-block: 0.125rem;
    white-space: nowrap;
    user-select: all; /* one click selects the whole reference for copying */
}

/* On the detail page the id sits beside the title and carries the page's identity, so it is a shade
   stronger again. */
.wi-detail-id {
    font-size: 0.875rem;
}

/* Inside the link chip the id already has the cloud glyph beside it, so it drops the chip's own border
   and keeps the weight. */
.wi-link__id {
    background: transparent;
    border-color: transparent;
    padding-inline: 0;
    color: var(--app-text-primary);
}

.wizard-progress {
    font-variant-numeric: tabular-nums;
}

/* ===== Command palette =====
   Sixty-six routes across eight nav sections, and the sidebar scrolls at a 900px viewport. The palette is
   the one surface that makes every other screen cheaper to reach without changing any of them. */
.palette-backdrop {
    position: fixed;
    inset: 0;
    z-index: 1080;
    background: rgba(16, 23, 32, 0.42);
    backdrop-filter: blur(2px);
}

.palette {
    position: fixed;
    z-index: 1081;
    top: 12vh;
    left: 50%;
    transform: translateX(-50%);
    width: min(40rem, calc(100vw - 2rem));
    max-height: 70vh;
    display: flex;
    flex-direction: column;
    overflow: hidden;
    background: var(--app-surface-0);
    border: 1px solid var(--app-border-subtle);
    border-radius: var(--app-radius-lg);
    box-shadow: var(--app-shadow-lg);
}

.palette__field {
    display: flex;
    align-items: center;
    gap: 0.6rem;
    padding: 0.85rem 1rem;
    border-bottom: 1px solid var(--app-border-subtle);
}

.palette__icon {
    color: var(--app-text-tertiary);
    flex: 0 0 auto;
}

/* Deliberately not a .form-control: the field IS the dialog's top edge, so a second border inside a border
   reads as a box in a box. */
.palette__input {
    flex: 1 1 auto;
    min-width: 0;
    border: 0;
    background: none;
    color: var(--app-text-primary);
    font-size: 1rem;
}

.palette__input:focus {
    outline: none;
}

.palette__input::placeholder {
    color: var(--app-text-tertiary);
}

.palette__hint,
.palette__footer kbd {
    font-family: var(--app-font-mono);
    font-size: 0.68rem;
    padding: 0.1rem 0.35rem;
    border: 1px solid var(--app-border-subtle);
    border-radius: var(--app-radius-sm);
    background: var(--app-surface-2);
    color: var(--app-text-tertiary);
}

.palette__results {
    margin: 0;
    padding: 0.35rem;
    list-style: none;
    overflow-y: auto;
    flex: 1 1 auto;
}

.palette__result {
    display: flex;
    align-items: center;
    gap: 0.7rem;
    padding: 0.5rem 0.65rem;
    border-radius: var(--app-radius-md);
    cursor: pointer;
}

/* One highlight, driven by the keyboard selection rather than by :hover, so moving with the arrow keys and
   moving with the pointer cannot light two rows at once. */
.palette__result--selected {
    background: var(--app-nav-hover-bg);
}

.palette__result-icon {
    color: var(--app-accent-primary);
    flex: 0 0 auto;
}

.palette__result-text {
    display: flex;
    flex-direction: column;
    min-width: 0;
    line-height: 1.25;
}

.palette__result-label {
    color: var(--app-text-primary);
    font-weight: 600;
    overflow: hidden;
    text-overflow: ellipsis;
    white-space: nowrap;
}

.palette__result-detail {
    font-family: var(--app-font-mono);
    font-size: 0.72rem;
    color: var(--app-text-secondary);
}

.palette__empty {
    padding: 1.25rem 0.65rem;
    text-align: center;
    color: var(--app-text-secondary);
}

.palette__footer {
    margin: 0;
    padding: 0.5rem 1rem;
    border-top: 1px solid var(--app-border-subtle);
    font-size: 0.74rem;
    color: var(--app-text-tertiary);
}

.palette__degraded {
    color: var(--app-warning, var(--app-text-secondary));
}
