/* ==========================================================================
   Story 135.16 - the --bb-* design token layer.

   Every value derives from a --mud-palette-* variable, so a theme switch or a
   palette edit carries through with no second definition of THESE tokens and
   no dark-mode selector reaching them. MudThemeProvider still swaps the
   values INSIDE a single :root block once the circuit exists, and
   `.mud-theme-dark` is still MudBlazor's colour-role class rather than a
   dark-mode signal.

   Story 150.4 adds one narrow, deliberate exception elsewhere in this same
   file, outside this token layer: `<html>` now carries a `data-theme`
   attribute (App.razor sets it from the `bb-theme` cookie, read against the
   real per-request HttpContext, before any circuit exists), and App.razor's
   own inline critical-CSS block is a second definition of exactly two palette
   values (Background/TextPrimary, kept byte-equal to BacklogBouncerTheme.cs by
   an architecture test) for light and dark. It exists only because these
   --mud-palette-* custom properties are not populated yet at that point -
   MudThemeProvider has not rendered - so nothing in :root below is usable
   that early. Once the circuit connects, MudThemeProvider takes over exactly
   as before and this exception plays no further part; the --bb-* tokens
   themselves are still single-sourced from --mud-palette-*, never touched by
   the critical-CSS block.

   The --bb- prefix is already used component-scoped (MainLayout.razor.css,
   QualityAnalysis.razor.css); this is its first :root-level use and collides
   with none of them.
   ========================================================================== */

:root {
    /* Surface tiers. The app has exactly three: the page behind everything, the
       panel a region sits on, and the card that carries one object. */
    --bb-surface: var(--mud-palette-surface);
    --bb-surface-2: color-mix(in srgb, var(--mud-palette-surface) 94%, var(--mud-palette-primary) 6%);
    --bb-surface-3: color-mix(in srgb, var(--mud-palette-surface) 88%, var(--mud-palette-primary) 12%);

    /* Accent tints. Fills and hairlines only, never text.
       DESIGN.md ratifies these per theme and they differ ONLY in alpha
       (0.08/0.09 and 0.25/0.27). A per-theme alpha is the one thing derivation
       cannot carry, since nothing emits a numeric alpha that varies by theme,
       so the single-alpha form at DESIGN.md's light values is used. That is
       byte-identical to what the shell already renders in both themes today
       (MainLayout.razor.css, ScopeSheet.razor.css, ScopeSummary.razor.css), so
       this introduces no new seam. */
    --bb-accent-soft: rgba(var(--mud-palette-primary-rgb), 0.08);
    --bb-accent-line: rgba(var(--mud-palette-primary-rgb), 0.25);

    /* Semantic tints, same contract as the accent pair. --bb-good-line is the
       success-hued companion --bb-accent-line already establishes the naming for
       (Story 135.23): the Drafts diff draws its "now" cell as a good-soft fill
       with a hairline above it, and reusing --bb-good-soft for both would put a
       0.13 border on a 0.13 fill - invisible, i.e. a drawn element silently
       dropped. It lives here rather than in the isolated CSS because the token
       layer is where such a computation belongs, which is also what keeps
       Story 135.22's raw-rgba gate over Components/Workflow/ green. */
    --bb-good-soft: rgba(var(--mud-palette-success-rgb), 0.13);
    --bb-good-line: rgba(var(--mud-palette-success-rgb), 0.22);
    --bb-warn-soft: rgba(var(--mud-palette-warning-rgb), 0.15);
    --bb-bad-soft: rgba(var(--mud-palette-error-rgb), 0.13);
    --bb-info-soft: rgba(var(--mud-palette-info-rgb), 0.13);

    /* Error as TEXT. Story 135.29. --mud-palette-error is a fill/border/icon/dot
       hue that also has to serve as text, and it measures 4.39:1 in the dark
       palette (#EF5350 on #20262B, measured in-browser by review-135-28) - just
       under the AA floor. DESIGN.md names {colors.error-text-dark} #FF8A85 as the
       foreground companion, but unlike the amber case there is no palette slot
       carrying it: WarningDarken already held {colors.warning-text}, whereas
       adding ErrorDarken would ALSO move MudBlazor's hover fill for every
       Color.Error filled button, LIGHTENING it above its resting hue in dark and
       breaking the contrast of its own white label. So the companion is derived
       here instead.

       Mixing toward the palette's own text colour moves the hue the right way in
       both palettes with one declaration, which is what a token layer with no
       dark-mode selector needs: 7.68:1 in light (up from 5.62) and 5.35:1 in
       dark (up from 4.39). Text only - the fill and border tints above are
       unaffected, and an error ICON or dot keeps the base hue, which already
       clears the 3:1 non-text floor. */
    --bb-bad-text: color-mix(in srgb, var(--mud-palette-error) 75%, var(--mud-palette-text-primary) 25%);

    /* --bb-derived and --bb-type-portfolio below deliberately resolve to the
       same --mud-palette-tertiary value. They are different concepts that share
       a hue: --bb-derived is the derived-code-source tag, --bb-type-portfolio is
       a position in the work-item hierarchy. Retuning one must not move the
       other, which is why they are two names rather than one. */
    --bb-derived: var(--mud-palette-tertiary);
    --bb-derived-soft: rgba(var(--mud-palette-tertiary-rgb), 0.13);

    /* Map canvas geometry. The edge renderer and the CSS must agree on these
       numbers, so they are stated once and read by both. */
    --bb-map-gutter: 56px;   /* hero to first card row: parent/child rays live here */
    --bb-map-lane: 64px;     /* between card rows: dependency cables live here */
    --bb-map-colgap: 32px;
    --bb-map-cardmin: 230px;

    /* Work item type accents. Position in the hierarchy, not a type-name matrix:
       portfolio containers, delivery containers, work, defects, subtasks. Keyed
       this way so a provider with a different type vocabulary (Agility) needs no
       second mapping, and so no component has to name an ADO type to pick a hue. */
    --bb-type-portfolio: var(--mud-palette-tertiary);
    --bb-type-container: var(--mud-palette-warning);
    --bb-type-work: var(--mud-palette-primary);
    --bb-type-defect: var(--mud-palette-error);
    --bb-type-subtask: var(--mud-palette-text-disabled);

    /* Story 135.25. The work item basket's width, read from BOTH sides of a seam that a
       component-scoped property cannot cross: MainLayout.razor.css reserves it on .content-row
       (inside .page) and WorkItemBasketPanel.razor.css spends it on the panel (rendered OUTSIDE
       .page, so it inherits nothing declared there). It lives here rather than in either isolated
       stylesheet because a `:root` selector inside a .razor.css is rewritten to `:root[b-xxxxx]`
       and never matches <html>.

       One constant meaning "the basket's width" — the OPEN state is a class on .content-row, not a
       second value for this token. */
    --bb-basket-width: 424px;

    /* Story 132.4, AC 1: the combined height of the two sticky chrome rows above `main` — the
       2.6rem .top-bar plus the 54px min-height scope bar. Declared once so anything that needs to
       keep content clear of the chrome reads one value rather than re-adding the two numbers.

       Story 135.25 moved it here from `.page` for the same reason --bb-basket-width is here: the
       docked basket has to start at the top of the content row, and it is not a descendant of
       `.page`. `main`'s scroll-padding-top still resolves by inheritance. */
    --bb-sticky-chrome-offset: calc(2.6rem + 54px);
}

/* {breakpoints.mode-compact}: the taller wrapped top bar plus the collapsed scope control rather
   than the 54px bar. Moved with the declaration above so the two stay together. */
@media (max-width: 640.98px) {
    :root {
        --bb-sticky-chrome-offset: calc(3.5rem + 48px);
    }
}

html, body {
    margin: 0;
    min-height: 100%;
    font-family: var(--mud-typography-default-family, "Roboto Flex", "Roboto", "Helvetica Neue", Arial, sans-serif);
    background-color: var(--mud-palette-background);
    color: var(--mud-palette-text-primary);
}

a, .btn-link {
    color: var(--mud-palette-primary);
}

.btn:focus, .btn:active:focus, .btn-link.nav-link:focus, .form-control:focus, .form-check-input:focus {
  box-shadow: 0 0 0 0.1rem white, 0 0 0 0.25rem #258cfb;
}

.content {
    padding-top: 0.6rem;
}

/* Story 132.4: `:not(:focus-visible)` is what keeps this rule from suppressing a focus indicator
   the user actually needs. It arrived as a Blazor template default and was dead until this story
   gave every page heading tabindex="-1"; from then on <FocusOnNavigate> parks focus on an h1 after
   every in-app navigation, and a bare `outline: none` there is a WCAG 2.4.7 failure introduced by
   the very story that advertises the shell as keyboard-ready. Programmatic .focus() does not match
   :focus-visible, so the ring stays suppressed for the mouse-driven route change (where a sighted
   user is already looking at the page they clicked to); genuine keyboard focus still gets a ring. */
h1:focus:not(:focus-visible) {
    outline: none;
}

.valid.modified:not([type=checkbox]) {
    outline: 1px solid #26b050;
}

.invalid {
    outline: 1px solid #e50000;
}

.validation-message {
    color: #e50000;
}

.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."
    }

.darker-border-checkbox.form-check-input {
    border-color: #929292;
}

/* ==========================================================================
   Story 135.22 - the two Workflow hub objects more than one component renders.

   Blazor CSS isolation rewrites every selector in a .razor.css so it matches
   only that component's own elements, so two components drawing one visual
   object each had to carry a byte-identical copy of its rule. Both of these
   were copied that way before they landed here, and both are now defined once.
   HubSharedStyleOwnershipTests is what keeps them that way.
   ========================================================================== */

/* The work-item state pill: WorkflowMap's hero, WorkflowMapCard, and
   WorkflowHubHeader's subject line. Four states and four tints - there is no
   warning state, and --bb-warn-soft belongs to the score chip's middle tier
   below. The base rule declares no background: every state supplies its own, and
   the mapping that picks one has no fallthrough beyond `active`. */
.bb-workflow-state {
    display: inline-flex;
    align-items: center;
    gap: 6px;
    padding: 2px 10px;
    border-radius: 999px;
    font-size: 0.72rem;
    font-weight: 600;
    text-transform: uppercase;
    letter-spacing: 0.04em;
    border: 1px solid currentColor;
}

.bb-workflow-state--done {
    color: var(--mud-palette-success);
    background: var(--bb-good-soft);
}

.bb-workflow-state--active {
    color: var(--mud-palette-info);
    background: var(--bb-info-soft);
}

.bb-workflow-state--new {
    color: var(--mud-palette-text-secondary);
    background: var(--bb-surface-2);
}

.bb-workflow-state--blocked {
    color: var(--mud-palette-error);
    background: var(--bb-bad-soft);
}

/* The readiness score chip: WorkflowNavigationTree's wide row and its compact
   row. Three tiers on the semantic tints with the tier hue as TEXT, replacing a
   solid palette fill under `color: #fff` - the transparent border is the
   mockup's own base declaration, kept so the tint alone carries the tier. */
.bb-score-chip {
    font-size: 0.6875rem;
    font-weight: 700;
    padding: 1px 7px;
    border: 1px solid transparent;
    border-radius: 100px;
    flex-shrink: 0;
}

.bb-score-chip--sprintready {
    background-color: var(--bb-good-soft);
    color: var(--mud-palette-success, #2e7d32);
}

.bb-score-chip--acceptable {
    background-color: var(--bb-warn-soft);
    color: var(--mud-palette-warning-darken, #ed6c02);
}

.bb-score-chip--needsrefinement {
    background-color: var(--bb-bad-soft);
    color: var(--mud-palette-error, #d32f2f);
}

.bb-page-shell {
    display: flex;
    flex-direction: column;
    gap: 0.5rem;
}

.bb-page-header {
    border: 1px solid rgba(var(--mud-palette-primary-rgb), 0.14);
    border-radius: 14px;
    background:
        linear-gradient(145deg, rgba(var(--mud-palette-primary-rgb), 0.07), rgba(255, 255, 255, 0.01)),
        var(--mud-palette-surface);
}

.bb-page-kicker {
    letter-spacing: 0.08em;
    text-transform: uppercase;
    color: var(--mud-palette-text-secondary);
}

.bb-page-title {
    line-height: 1.2;
    /* Story 132.4: page titles render as <h1> now (they were <h4>/<h5> before, which left every
       page without one). MudBlazor's typography classes zero the bottom margin but not the top, so
       without this the UA stylesheet's h1 block margin would push every page header down. */
    margin-top: 0;
}

.bb-page-subtitle {
    color: var(--mud-palette-text-secondary);
}

.bb-page-actions {
    display: flex;
    align-items: center;
    gap: 0.5rem;
    flex-wrap: wrap;
}

/* ----------------------------------------------------------------
 * Danger zone — Story 136.7. Wraps a destructive maintenance tool so
 * it can never be mistaken for a read-only diagnostic sitting next to
 * it. Apply .bb-danger-zone to the section's own MudPaper and put the
 * heading row in a .bb-danger-zone-heading (icon + Color.Error title).
 * Reused by Story 136.9's workspace archive — extend here rather than
 * inventing a per-surface variant.
 * ---------------------------------------------------------------- */

.bb-danger-zone {
    border: 1px solid rgba(var(--mud-palette-error-rgb), 0.4);
    border-left: 4px solid var(--mud-palette-error);
    background-color: rgba(var(--mud-palette-error-rgb), 0.04);
}

.bb-danger-zone-heading {
    display: flex;
    align-items: center;
    gap: 0.4rem;
    flex-wrap: wrap;
}

/* ----------------------------------------------------------------
 * Restore zone — Epic 140 FR2. The deliberate counterpart to
 * .bb-danger-zone above, for a reversible lifecycle action that must
 * never be styled as destructive. Same geometry, success palette, so
 * the two read as siblings rather than as unrelated boxes.
 * ---------------------------------------------------------------- */

.bb-restore-zone {
    border: 1px solid rgba(var(--mud-palette-success-rgb), 0.4);
    border-left: 4px solid var(--mud-palette-success);
    background-color: rgba(var(--mud-palette-success-rgb), 0.04);
}

.bb-restore-zone-heading {
    display: flex;
    align-items: center;
    gap: 0.4rem;
    flex-wrap: wrap;
}

/* ----------------------------------------------------------------
 * Global density overrides — tighten MudBlazor defaults app-wide.
 * Goal: reduce vertical waste in tables, lists, buttons, toolbars,
 * expansion panels and form controls without harming readability.
 * ---------------------------------------------------------------- */

/* Tables — even with Dense="true" MudBlazor leaves 6px top/bottom.
 * Bring it down further while keeping touch targets reasonable. */
.mud-table-dense .mud-table-cell {
    padding-top: 4px;
    padding-bottom: 4px;
    padding-left: 8px;
    padding-right: 8px;
    line-height: 1.35;
}

.mud-table-dense .mud-table-cell.mud-table-cell-checkbox {
    padding: 0 4px;
}

.mud-table-toolbar {
    min-height: 2.25rem;
    padding-left: 0.5rem;
    padding-right: 0.5rem;
}

.mud-table-pagination-toolbar {
    min-height: 2.25rem;
}

/* DataGrid — same idea, the grid cell padding is separate from MudTable. */
.mud-data-grid-dense .mud-table-cell {
    padding-top: 4px;
    padding-bottom: 4px;
    padding-left: 8px;
    padding-right: 8px;
    line-height: 1.35;
}

/* Lists — Dense="true" lists still keep ~36px min-height; tighten further. */
.mud-list.mud-list-dense .mud-list-item {
    min-height: 28px;
    padding-top: 2px;
    padding-bottom: 2px;
}

.mud-list.mud-list-dense .mud-list-item-icon {
    min-width: 28px;
    margin-right: 8px;
}

/* Buttons — keep them legible but shrink default vertical pad. */
.mud-button-root {
    min-height: 1.85rem;
    padding-top: 0.25rem;
    padding-bottom: 0.25rem;
}

.mud-button-root.mud-icon-button {
    padding: 0.3rem;
}

.mud-button-root.mud-button-size-small {
    min-height: 1.55rem;
    padding-top: 0.15rem;
    padding-bottom: 0.15rem;
}

/* Toolbars — MudToolBar is regularly used as a card toolbar. */
.mud-toolbar {
    min-height: 2.25rem;
    padding-left: 0.5rem;
    padding-right: 0.5rem;
}

.mud-toolbar.mud-toolbar-dense {
    min-height: 2rem;
}

/* A disclosure panel that is the ONLY panel in its container gets no border from MudBlazor (it is
 * both :first-child and :last-child) and shares the surface color of the card hosting it, so its
 * header reads as a paragraph of text with a chevron stranded at the far edge. This class gives such
 * a header the shape of a control. Opt-in per call site: it is wrong for a real multi-panel
 * accordion, where MudBlazor's own per-panel borders already do this job. */
.bb-disclosure-panel .mud-expand-panel .mud-expand-panel-header {
    border: 1px solid var(--mud-palette-lines-default);
    border-radius: 8px;
    background: var(--bb-surface-2);
    padding-left: 12px;
    padding-right: 12px;
}

.bb-disclosure-panel .mud-expand-panel .mud-expand-panel-header:hover {
    border-color: var(--mud-palette-primary);
}

.bb-disclosure-panel .mud-expand-panel.mud-panel-expanded .mud-expand-panel-header {
    border-bottom-left-radius: 0;
    border-bottom-right-radius: 0;
}

.bb-disclosure-panel .mud-expand-panel .mud-expand-panel-content {
    border: 1px solid var(--mud-palette-lines-default);
    border-top: none;
    border-bottom-left-radius: 8px;
    border-bottom-right-radius: 8px;
}

/* Expansion panels — default headers are oversized. */
.mud-expand-panel-header {
    min-height: 2.5rem;
    padding-top: 0.25rem;
    padding-bottom: 0.25rem;
}

.mud-expand-panel-content .mud-expand-panel-text {
    padding: 0.5rem 0.75rem;
}

/* Form controls — pad below labels and on inputs is generous; trim it. */
.mud-input-control.mud-input-margin-dense {
    margin-top: 2px;
    margin-bottom: 2px;
}

.mud-input.mud-input-text {
    margin-top: 0;
}

/* Cards & papers — default 16px+ padding wastes a lot on dashboards. */
.mud-card-content {
    padding: 0.75rem 0.875rem;
}

.mud-card-actions {
    padding: 0.25rem 0.5rem;
}

/* Chips — used heavily in headers/filters. */
.mud-chip.mud-chip-size-small {
    height: 1.35rem;
    font-size: 0.7rem;
}

/* Tabs — MudTabs default header is tall. */
.mud-tabs-toolbar {
    min-height: 2.25rem;
}

.mud-tab {
    min-height: 2.25rem;
    padding: 0 0.75rem;
}

/* Dialogs — tighten default padding so modals don't feel cavernous. */
.mud-dialog-title {
    padding: 0.6rem 1rem;
}

.mud-dialog-content {
    padding: 0.5rem 1rem;
}

.mud-dialog-actions {
    padding: 0.4rem 0.75rem;
}

/* Alerts — give them a slightly tighter look in body text */
.mud-alert {
    padding: 0.4rem 0.75rem;
}

/* Page header chrome — already custom, but trim a hair. */
.bb-page-shell {
    gap: 0.4rem;
}

/* ----------------------------------------------------------------
 * KPI strip — used for unified statistics rows on Dashboard,
 * Story Enrichment, Comment Summaries, etc.
 * ---------------------------------------------------------------- */

.bb-stats-section {
    margin-bottom: 1rem;
}

.bb-stats-header {
    display: flex;
    align-items: baseline;
    flex-wrap: wrap;
    gap: 0.5rem;
    margin-bottom: 0.5rem;
}

.bb-stats-title {
    font-size: 0.8rem;
    font-weight: 600;
    text-transform: uppercase;
    letter-spacing: 0.08em;
    color: var(--mud-palette-text-secondary);
}

.bb-stats-summary {
    font-size: 0.78rem;
    color: var(--mud-palette-text-secondary);
}

.bb-stats-grid {
    display: grid;
    grid-template-columns: repeat(auto-fit, minmax(11rem, 1fr));
    gap: 0.6rem;
}

/* Dense stat / metric card used across dashboards (Bug Quality, Event Decisions,
 * Organization Governance) so single-row layouts fit on standard widths. */
.bb-stat-card {
    transition: box-shadow 120ms ease, transform 120ms ease;
}

.bb-stat-card .mud-card-content {
    padding: 0.5rem 0.5rem !important;
    gap: 0.15rem;
}

.bb-stat-card--clickable {
    cursor: pointer;
}

.bb-stat-card--clickable:hover {
    box-shadow: 0 4px 12px rgba(0, 0, 0, 0.12);
    transform: translateY(-1px);
}

.bb-stat-card--disabled {
    cursor: default;
    opacity: 0.85;
}

.bb-stat-card--disabled .mud-text-disabled {
    color: var(--mud-palette-text-disabled) !important;
}

/* Auto-fit grid for the Event Decision summary cards. Lets cards grow to fill
 * available width instead of being pinned to a fixed 12-column slot, so they
 * stay readable regardless of how many bucket categories are present. */
.bb-decision-grid {
    display: grid;
    grid-template-columns: repeat(auto-fit, minmax(13rem, 1fr));
    gap: 0.6rem;
}

/* Card grid for the workspace Connections tab (Story 123.4). Wide minimum so
 * each ConnectionCard fits its name, status chip, scope line, and health row
 * without wrapping into a cramped column. auto-fill (not auto-fit) keeps empty
 * phantom tracks on wide viewports, and the 26rem track max caps card width,
 * so a few cards render card-sized (20-26rem) with leftover space empty rather
 * than stretching into full-row slabs. min(20rem, 100%) lets the column shrink
 * below 20rem on narrow viewports so phones get a single full-width column
 * instead of horizontal overflow (when min exceeds max, min wins). */
.bb-connections-grid {
    display: grid;
    grid-template-columns: repeat(auto-fill, minmax(min(20rem, 100%), 26rem));
    gap: 0.75rem;
}

/* Connection card: column flex so the actions row (Configure / "Not yet
 * configurable") pins to the bottom edge when grid rows stretch cards to a
 * common height. overflow-wrap lets long unbroken names/scope paths wrap inside
 * the now-bounded (20-26rem) track instead of overflowing the card. */
.bb-connection-card {
    display: flex;
    flex-direction: column;
    overflow-wrap: anywhere;
}

.bb-connection-card .mud-card-actions {
    margin-top: auto;
    /* Story 136.4 review S9: the action row can now hold up to 4 buttons (Configure, Change,
       Test, and — SystemAdmin only — View credential) plus the admin-managed empty-state text;
       without wrap they overflow the card's bounded (20-26rem) width at any viewport. */
    flex-wrap: wrap;
}

/* Story 136.4 review S9 (AC 9): at compact width the connection-card grid collapses to one
   full-width column (see .bb-connections-grid's min(20rem, 100%) track above), and the action
   row's buttons must be touch-target sized there — MudButton Size="Small" renders well under the
   44px minimum recommended for a touch target. Scoped to the action row only, not every small
   button app-wide, and to the same 640.98px boundary the rest of this file's compact-width rules
   already use (MudBlazor's own Breakpoint.Xs ends at 600px; see the dialog-actions rule below for
   why 640.98px, not 600px, is this file's boundary). */
@media (max-width: 640.98px) {
    .bb-connection-card .mud-card-actions .mud-button-root {
        min-height: 44px;
    }
}

/* Dashed "Add connection" ghost card (Story 123.5): last grid item; the dashed
 * outline distinguishes the add affordance from real connection cards. Rendered
 * as a native <button> for keyboard semantics, so neutralize button defaults. */
.bb-add-connection-ghost-card {
    border: 2px dashed var(--mud-palette-lines-default);
    background: transparent;
    min-height: 10rem;
    width: 100%;
    font: inherit;
    color: inherit;
    cursor: pointer;
}

.bb-add-connection-ghost-card:hover,
.bb-add-connection-ghost-card:focus-visible {
    border-color: var(--mud-palette-primary);
}

/* The expanded inline add form spans the full grid row so its field stack gets
 * usable width instead of being squeezed into one card column. */
.bb-add-connection-card-expanded {
    grid-column: 1 / -1;
}

/* Notification badge — the top-bar sits flush against the viewport top, so the
   default MudBadge overlap (bottom: calc(100% - 12px)) positions the 20px badge
   8px above the icon's top edge, clipping it against the page overflow boundary.
   Shift it down so the badge top stays within the viewport. */
.bb-notification-badge {
    bottom: calc(100% - 18px) !important;
}

/* ==========================================================================
   Story 139.4 — the circuit-state surface (#components-reconnect-modal).

   Lives here rather than in MainLayout.razor.css because the element is in
   App.razor, outside the component tree, so scoped CSS would never reach it.

   The framework toggles ONE state class on #components-reconnect-modal and
   nothing else. Everything below is driven off that class:

     components-reconnect-show          rejoining          -> banner
     components-reconnect-retrying      rejoining, backing off -> banner
     components-reconnect-hide          recovered          -> nothing
     components-reconnect-failed        rejoin failed      -> modal + Reconnect
     components-reconnect-paused        paused by server   -> modal + Resume
     components-reconnect-resume-failed resume failed      -> modal + Reload
     components-reconnect-rejected      session gone       -> modal + Reload

   Rejoining is deliberately a NON-BLOCKING banner, not a modal: EXPERIENCE.md
   § State Patterns specifies "non-blocking banner 'Reconnecting…'" for a
   dropped circuit, and there is nothing for the user to decide while the
   framework is still retrying. Only the terminal states — the ones that need a
   decision — take the screen.

   No aria-hidden attribute anywhere: display:none already removes the surface
   from the accessibility tree, and a static aria-hidden="true" would still be
   there when the framework made the surface visible.
   ========================================================================== */

.bb-circuit {
    display: none;
}

.bb-circuit.components-reconnect-show,
.bb-circuit.components-reconnect-retrying,
.bb-circuit.components-reconnect-failed,
.bb-circuit.components-reconnect-paused,
.bb-circuit.components-reconnect-resume-failed,
.bb-circuit.components-reconnect-rejected {
    display: block;
}

/* Per-state content selection. Every variant is in the DOM at all times (the
   framework toggles classes, so nothing here can be conditional in C#), and
   exactly one is shown. */
.bb-circuit__on-rejoining,
.bb-circuit__on-retrying,
.bb-circuit__on-failed,
.bb-circuit__on-rejected,
.bb-circuit__on-paused,
.bb-circuit__on-signin {
    display: none;
}

.bb-circuit.components-reconnect-show .bb-circuit__on-rejoining,
.bb-circuit.components-reconnect-retrying .bb-circuit__on-rejoining,
.bb-circuit.components-reconnect-retrying .bb-circuit__on-retrying,
.bb-circuit.components-reconnect-failed .bb-circuit__on-failed,
.bb-circuit.components-reconnect-paused .bb-circuit__on-paused,
.bb-circuit.components-reconnect-resume-failed .bb-circuit__on-rejected,
.bb-circuit.components-reconnect-rejected .bb-circuit__on-rejected {
    display: revert;
}

/* --- Story 139.5: the fifth state — the sign-in expired, not the transport.

   Selected the same way as the four above, on a class: `bb-circuit--signin`,
   added by circuit-state.js once the server has confirmed the session ended.
   It is APP-owned and deliberately not one of the components-reconnect-*
   names — the framework wipes those on every state change, so a verdict named
   like one would either be erased or, worse, outlive the question it answered.

   Scoped to the two terminal states rather than standing alone: the answer
   only ever means anything once a reconnect has already failed. `resume-failed`
   is deliberately NOT here — circuit-state.js probes on `failed` and `rejected`
   only (AC3), so a rule for it would never fire. Widening it is a one-line
   change in both places if resume ever becomes reachable (nothing pauses
   circuits in production today).

   The suppress rules below carry four classes to the show rules' three, so
   they win on specificity without !important. That is what makes this a
   REPLACEMENT of the transport treatment rather than an addition to it —
   telling someone to reload into a sign-in redirect they did not ask for is
   the defect this state exists to remove. The same rules retire the matching
   ACTION, because `bb-circuit__on-failed` / `-rejected` sit on the Reconnect
   and Reload buttons too.
   ------------------------------------------------------------------------ */
.bb-circuit.bb-circuit--signin.components-reconnect-failed .bb-circuit__on-signin,
.bb-circuit.bb-circuit--signin.components-reconnect-rejected .bb-circuit__on-signin {
    display: revert;
}

.bb-circuit.bb-circuit--signin.components-reconnect-failed .bb-circuit__on-failed,
.bb-circuit.bb-circuit--signin.components-reconnect-rejected .bb-circuit__on-rejected {
    display: none;
}

/* --- Rejoining: a banner along the bottom, blocking nothing. ------------- */
.bb-circuit.components-reconnect-show .bb-circuit__dialog,
.bb-circuit.components-reconnect-retrying .bb-circuit__dialog {
    position: fixed;
    left: 0;
    right: 0;
    bottom: 0;
    z-index: 1200;
    display: flex;
    flex-wrap: wrap;
    align-items: baseline;
    gap: 0 0.5rem;
    padding: 0.6rem 1rem;
    background-color: var(--mud-palette-surface, #FFFFFF);
    color: var(--mud-palette-text-primary, rgba(0, 0, 0, 0.87));
    border-top: 1px solid var(--mud-palette-divider, rgba(0, 0, 0, 0.12));
    box-shadow: 0 -1px 2px rgba(0, 0, 0, 0.2);
    font-size: 0.8125rem;
}

/* Story 150.4 patch (N8 remainder): the static shell placeholder (z-index 2000, above) can still be
   covering the screen for up to ~3s of any hard load. A circuit that connects and then immediately
   flaps into "rejoining" inside that window would otherwise show this banner at 1200 - behind the
   placeholder - so nothing on screen would report it. Raises the banner above the placeholder only
   while `.page[data-shell-interactive="true"]` does not exist yet, i.e. only for that pre-interactive
   window; once the page is interactive the placeholder is already gone and 1200 is correct again. */
body:not(:has(.page[data-shell-interactive="true"])) .bb-circuit.components-reconnect-show .bb-circuit__dialog,
body:not(:has(.page[data-shell-interactive="true"])) .bb-circuit.components-reconnect-retrying .bb-circuit__dialog {
    z-index: 2100;
}

/* At compact the banner still sits at the bottom, so it can never cover the
   scope control at the top of the screen (EXPERIENCE.md § Compact). */

.bb-circuit.components-reconnect-show .bb-circuit__brand,
.bb-circuit.components-reconnect-retrying .bb-circuit__brand {
    display: none;
}

.bb-circuit.components-reconnect-show .bb-circuit__title,
.bb-circuit.components-reconnect-retrying .bb-circuit__title {
    margin: 0;
    font-size: 0.8125rem;
    font-weight: 600;
}

.bb-circuit.components-reconnect-show .bb-circuit__body,
.bb-circuit.components-reconnect-retrying .bb-circuit__body {
    margin: 0;
}

.bb-circuit.components-reconnect-show .bb-circuit__body p,
.bb-circuit.components-reconnect-retrying .bb-circuit__body p {
    margin: 0;
}

/* --- Terminal states: a modal, because a decision is required. ----------- */
.bb-circuit.components-reconnect-failed,
.bb-circuit.components-reconnect-paused,
.bb-circuit.components-reconnect-resume-failed,
.bb-circuit.components-reconnect-rejected {
    position: fixed;
    inset: 0;
    z-index: 10000;
    background-color: rgba(13, 31, 32, 0.55);
}

.bb-circuit.components-reconnect-failed .bb-circuit__dialog,
.bb-circuit.components-reconnect-paused .bb-circuit__dialog,
.bb-circuit.components-reconnect-resume-failed .bb-circuit__dialog,
.bb-circuit.components-reconnect-rejected .bb-circuit__dialog {
    width: min(24rem, calc(100% - 2rem));
    margin: 20vh auto 0;
    padding: 1.5rem;
    border-radius: 0.5rem;
    background-color: var(--mud-palette-surface, #FFFFFF);
    color: var(--mud-palette-text-primary, rgba(0, 0, 0, 0.87));
    box-shadow: 0 3px 12px 2px rgba(0, 0, 0, 0.35);
}

.bb-circuit__brand {
    margin: 0 0 0.75rem;
    font-size: 0.625rem;
    font-weight: 600;
    letter-spacing: 0.08em;
    text-transform: uppercase;
    color: var(--mud-palette-primary, #2A6466);
}

.bb-circuit__title {
    margin: 0 0 0.5rem;
    font-size: 1rem;
    font-weight: 600;
}

.bb-circuit__body p {
    margin: 0 0 0.75rem;
    font-size: 0.8125rem;
    line-height: 1.5;
}

.bb-circuit__actions {
    display: flex;
    gap: 0.5rem;
}

.bb-circuit__button {
    padding: 0.4rem 1rem;
    min-height: 2.25rem;
    border: 0;
    border-radius: 4px;
    background-color: var(--mud-palette-primary, #2A6466);
    color: #FFFFFF;
    font: inherit;
    font-size: 0.8125rem;
    cursor: pointer;
}

.bb-circuit__button:hover {
    filter: brightness(1.1);
}

/* Story 139.5 (AC9): stated rather than left to the user agent's default ring,
   which is drawn in the browser's own accent and can land close to the button's
   primary fill. Taken from text-primary and offset clear of the fill, so it
   reads against the card in both palettes. */
.bb-circuit__button:focus-visible {
    outline: 2px solid var(--mud-palette-text-primary, rgba(0, 0, 0, 0.87));
    outline-offset: 2px;
}

.bb-circuit__button[disabled] {
    cursor: progress;
    opacity: 0.7;
}

/* The banner never carries an action — the framework is already retrying and
   there is nothing for the user to decide. */
.bb-circuit.components-reconnect-show .bb-circuit__actions,
.bb-circuit.components-reconnect-retrying .bb-circuit__actions {
    display: none;
}

@media (forced-colors: active) {
    .bb-circuit__dialog {
        border: 1px solid CanvasText;
    }

    .bb-circuit__button {
        border: 1px solid ButtonText;
    }
}

/* Story 136.1, AC9 (review fix B1) — CreateWorkspaceDialog's footer at compact width. MudDialog's
   ActionsClass parameter attaches ".bb-cw-dialog-actions" to the library's own actions container
   element, which is rendered outside CreateWorkspaceDialog.razor's own CSS-isolation scope (that
   component's .razor.css can only reach elements IT declares, never an ancestor owned by the
   library) — so this rule has to live here, global and unscoped, to ever match. Full-screen itself
   is set through DialogOptions.FullScreen in code, driven by IBrowserViewportService's raw
   BrowserWindowSize.Width against the SAME 640px boundary this media query uses (review fix
   B1-b — MudBlazor's own Breakpoint.Xs ends at 600px, which disagreed with this file's 640.98px
   in the 600-640.98px band). */
@media (max-width: 640.98px) {
    /* Stacked, not a row: with Cancel/Skip still siblings of the primary action in the same flex
       container, "width: 100%" on the primary action alone cannot reach the full container width
       — it can only grow into whatever the row has left over. column-reverse puts the LAST child
       (the primary action) on top, matching "the primary action is pinned"; the secondary
       action (Cancel/Skip) stays a visible, de-emphasized control underneath it.

       Review fix B1-a: ".mud-dialog .mud-dialog-actions" in MudBlazor.min.css sets its own
       padding at specificity (0,2,0); a bare ".bb-cw-dialog-actions" class selector is (0,1,0) and
       loses that fight, so padding-bottom's safe-area calc below was computed but never applied
       (measured 8px regardless). Prefixing with ".mud-dialog" matches MudBlazor's own specificity. */
    .mud-dialog .bb-cw-dialog-actions {
        display: flex;
        flex-direction: column-reverse;
        align-items: stretch;
        gap: 0.5rem;
        position: sticky;
        bottom: 0;
        background-color: var(--mud-palette-surface);
        padding-bottom: calc(0.75rem + env(safe-area-inset-bottom, 0px));
    }

    /* MudSpacer renders a bare flex-grow-1 div — meaningless in a column layout and would
       otherwise consume the vertical gap this container's own `gap` already provides. */
    .mud-dialog .bb-cw-dialog-actions .flex-grow-1 {
        flex: 0 0 0;
    }

    .bb-cw-primary-action {
        width: 100%;
    }
}

/* Story 136.5 (AC 6): moved here from MainLayout.razor.css so the role badge shape is reusable
   outside MainLayout's own render tree — Blazor CSS isolation scopes a .razor.css file's rules to
   elements THAT component renders, so a class defined only in MainLayout.razor.css never visually
   applies to the identical class name used elsewhere (e.g. WorkspaceAdmin's People tab). The
   compact-width overrides (hide at <=480px, size tweak at the app-bar's own breakpoints) stay
   scoped in MainLayout.razor.css — those are specific to the identity-bar badge, not a global rule
   for every role-badge on the page. */
.role-badge {
    display: inline-flex;
    align-items: center;
    justify-content: center;
    padding: 0 0.4rem;
    height: 1.2rem;
    border-radius: 999px;
    font-size: 0.62rem;
    font-weight: 700;
    letter-spacing: 0.04em;
    border: 1px solid;
    white-space: nowrap;
}

.role-admin {
    color: #7a2b00;
    border-color: #f0b396;
    background: linear-gradient(135deg, #ffe4d2, #ffd3bc);
}

.role-manager {
    color: #0f4652;
    border-color: #88bcc6;
    background: linear-gradient(135deg, #d9f1f6, #bee2ea);
}

.role-authenticated {
    color: #3f3f3f;
    border-color: #d4d4d4;
    background: linear-gradient(135deg, #f2f2f2, #e5e5e5);
}

/* ── Static shell placeholder base visual (Story 150.4, AC1) ─────────────────────────────────────
   `.bb-shell-loading`'s box/typography styling lives in MainLayout.razor.css, CSS-isolated to
   elements MainLayout itself renders — the scope attribute isolation adds never reaches App.razor's
   copy, since it is a different component. Duplicated here, literally, keyed on
   `[data-bb-static-shell]` so it only ever targets the static instance: without this the static
   placeholder was unstyled text until MainLayout's own copy took over, which is exactly the "two
   visuals instead of one" AC1 rules out. Keep both copies in sync by hand if either changes. */
.bb-shell-loading[data-bb-static-shell] {
    position: fixed;
    inset: 0;
    z-index: 2000;
    display: flex;
    flex-direction: column;
    align-items: center;
    justify-content: center;
    gap: 0.75rem;
    background: linear-gradient(180deg, #1A3C3E, #0D1F20);
    color: #E8F1F1;
}

.bb-shell-loading[data-bb-static-shell] .bb-shell-loading__brand {
    font-size: 1.25rem;
    font-weight: 600;
    letter-spacing: 0.02em;
}

.bb-shell-loading[data-bb-static-shell] .bb-shell-loading__message {
    font-size: 0.875rem;
    color: #9FBFBF;
}

/* Fix cycle 1 (N5): the fallback's second message, hidden until the 3s timeout below reveals it.
   `visibility: hidden` (not `display: none`) is what the reveal keyframe can flip discretely; laid
   out from the start so revealing it does not reflow the centered column. */
.bb-shell-loading[data-bb-static-shell] .bb-shell-loading__still-connecting {
    display: flex;
    flex-direction: column;
    align-items: center;
    gap: 0.5rem;
    visibility: hidden;
}

.bb-shell-loading[data-bb-static-shell] .bb-shell-loading__reload {
    color: #E8F1F1;
    font-size: 0.875rem;
    text-decoration: underline;
}

/* ── Static shell placeholder removal (Story 150.4, AC2) ────────────────────────────────────────
   `App.razor`'s static `.bb-shell-loading[data-bb-static-shell]` cannot be torn down by a Blazor
   re-render — the whole point of it is to cover the window before any circuit exists to run one.
   Global (not CSS-isolated) because App.razor's markup is outside every component's render tree;
   an isolated copy of this rule in MainLayout.razor.css would never match it (Story 136.5's
   .role-badge precedent above, same reason). `:has()` is already used in this codebase
   (AddConnectionCard.razor.css) and is the only combinator that can reach `.page` from here: `.page`
   is nested inside `<Routes>`'s render tree, not a plain sibling, so a `~`/`+` combinator between
   the two elements cannot match regardless of DOM order. */
body:has(.page[data-shell-interactive="true"]) .bb-shell-loading[data-bb-static-shell] {
    display: none;
}

/* Bounded fallback for a circuit that has not connected yet (AC2; fix cycle 1 N5 rewrite) — a
   pure-CSS timeout, so it fires with no JS and no Blazor render. This used to hide the whole
   placeholder at 3s (`visibility: hidden` on the container), which left a genuinely blank page
   whenever the circuit connected LATE: MainLayout's own cover does not start ITS 3s budget
   (`ShellHydrationTimeout`, `MainLayout.razor.cs:44`) until the circuit is already live, so the two
   timers are independent, not one continuous budget, and a slow-connecting circuit could land in the
   gap between them. Fix: never hide the placeholder here — swap which of its two message blocks is
   visible instead, so the page stays covered (with a working reload action, no circuit required)
   until either `:has()` above removes the whole element for a real connection, or the user reloads.
   `visibility` (not `display`) is the classic discrete-animatable property with the widest support;
   `forwards` keeps the end state once each animation completes. 3s reuses the same
   `ShellHydrationTimeout` number rather than inventing a second one for the same "this is taking too
   long" case. Harmless on the happy path: `:has()` above already removes the whole element well
   before 3s in every case that matters, so these two animations' end states are moot whenever the
   circuit connects in time. */
@keyframes bb-static-shell-timeout-hide {
    to {
        visibility: hidden;
    }
}

@keyframes bb-static-shell-timeout-reveal {
    to {
        visibility: visible;
    }
}

.bb-shell-loading[data-bb-static-shell] .bb-shell-loading__initial {
    animation: bb-static-shell-timeout-hide 0s linear 3s forwards;
}

.bb-shell-loading[data-bb-static-shell] .bb-shell-loading__still-connecting {
    animation: bb-static-shell-timeout-reveal 0s linear 3s forwards;
}
