/*
 * PostMQ dashboard design system (UI redesign, 2026-08; brand re-token WP 0.3 + WP 2.2, 2026-08-18).
 * Quiet, dense, precise — Paper canvas / Paper cards with a hairline edge (dark: Ink canvas / neutral-900 cards),
 * one Cobalt accent reserved for actions + live states, six-tone status vocabulary, self-hosted IBM Plex Sans
 * 400/500/600 + IBM Plex Mono 400/500 (SIL OFL 1.1; files, provenance and hashes in wwwroot/fonts/README.md,
 * licence in wwwroot/fonts/LICENSE-OFL.txt). No external fonts or CDNs (CSP: everything same-origin).
 *
 * Colour roles are ALIASES of the brand's semantic tokens: css/postmq-tokens.css (generated from
 * design/brand/tokens/design-tokens.json by tools/generate-brand-tokens.py, linked BEFORE this file in
 * App.razor) declares --pmq-bg / --pmq-surface / --pmq-text / --pmq-accent / … in light AND dark, and the
 * `BEGIN pmq aliases` block below re-points --bg / --surface-1 / --text / --brand / … at them. The app's variable
 * NAMES are unchanged, so no Razor page changes; the site and the app read the same hexes. The status, icon and
 * chart vocabularies have no semantic counterpart in the kit yet and keep their own values — except the accent
 * status tone and chart series 1, which ARE the accent and alias it. WP 2.2 extended the same aliasing to TYPE
 * (--font-sans / --font-mono → --pmq-font-sans / --pmq-font-mono) and RADII (--radius-* → the kit's --pmq-r-*
 * scale: 2 / 6 / 10 / pill), and replaced the postmark glyph with the brand's pmq monogram (AppIcon "pmq" — the
 * ONE place Amber appears in the app; it is never a UI accent, see BrandTypographyTests).
 *
 * Theme wiring (proven, no-flash): :root is light, the prefers-color-scheme media query supplies the OS default
 * for anyone who has NOT chosen (:root:not([data-theme="light"]) — a bare :root there would leave every dark
 * token in force for a user who picked light on an OS-dark machine), and an explicit [data-theme] (set by
 * theme.js) overrides both. The dark token block therefore exists TWICE and the two copies must stay
 * byte-identical — DarkThemeBlockParityTests in PostMQ.Web.Tests enforces it. Aliased roles flip inside the
 * token file, so their dark lines are identical to light; ThemeContrastTests pins that too.
 *
 * Every fg/bg pair here is contrast-computed for WCAG 2.2 AA (4.5:1 text, 3:1 non-text) in both themes — by
 * ThemeContrastTests in PostMQ.Web.Tests, which resolves the alias → pmq chain for each theme and fails the
 * build on a pair below its bar. If you change a colour, run that test; don't eyeball.
 */

/* ----- Self-hosted type ----- */
/* IBM Plex Sans 400/500/600 + IBM Plex Mono 400/500 — the brand's three weights and two faces, nothing more
   (design/brand/typography/font-stack.md). Static faces from @fontsource 5.3.0 (wwwroot/fonts/README.md), Latin
   and Latin Extended subsets: the unicode-range gate means a page fetches the -ext file only when it renders a
   character in that range (user-authored names and titles can). font-display: swap so text is never invisible
   waiting on a woff2 (the fallback stacks are chosen to be competent, not identical — see the token file). */
@font-face {
    font-family: "IBM Plex Sans";
    src: url("fonts/ibm-plex-sans-latin-400-normal.woff2") format("woff2");
    font-weight: 400;
    font-style: normal;
    font-display: swap;
    unicode-range: U+0000-00FF, U+0131, U+0152-0153, U+02BB-02BC, U+02C6, U+02DA, U+02DC, U+0304, U+0308, U+0329, U+2000-206F, U+20AC, U+2122, U+2191, U+2193, U+2212, U+2215, U+FEFF, U+FFFD;
}

@font-face {
    font-family: "IBM Plex Sans";
    src: url("fonts/ibm-plex-sans-latin-500-normal.woff2") format("woff2");
    font-weight: 500;
    font-style: normal;
    font-display: swap;
    unicode-range: U+0000-00FF, U+0131, U+0152-0153, U+02BB-02BC, U+02C6, U+02DA, U+02DC, U+0304, U+0308, U+0329, U+2000-206F, U+20AC, U+2122, U+2191, U+2193, U+2212, U+2215, U+FEFF, U+FFFD;
}

@font-face {
    font-family: "IBM Plex Sans";
    src: url("fonts/ibm-plex-sans-latin-600-normal.woff2") format("woff2");
    font-weight: 600;
    font-style: normal;
    font-display: swap;
    unicode-range: U+0000-00FF, U+0131, U+0152-0153, U+02BB-02BC, U+02C6, U+02DA, U+02DC, U+0304, U+0308, U+0329, U+2000-206F, U+20AC, U+2122, U+2191, U+2193, U+2212, U+2215, U+FEFF, U+FFFD;
}

@font-face {
    font-family: "IBM Plex Sans";
    src: url("fonts/ibm-plex-sans-latin-ext-400-normal.woff2") format("woff2");
    font-weight: 400;
    font-style: normal;
    font-display: swap;
    unicode-range: U+0100-02BA, U+02BD-02C5, U+02C7-02CC, U+02CE-02D7, U+02DD-02FF, U+0304, U+0308, U+0329, U+1D00-1DBF, U+1E00-1E9F, U+1EF2-1EFF, U+2020, U+20A0-20AB, U+20AD-20C0, U+2113, U+2C60-2C7F, U+A720-A7FF;
}

@font-face {
    font-family: "IBM Plex Sans";
    src: url("fonts/ibm-plex-sans-latin-ext-500-normal.woff2") format("woff2");
    font-weight: 500;
    font-style: normal;
    font-display: swap;
    unicode-range: U+0100-02BA, U+02BD-02C5, U+02C7-02CC, U+02CE-02D7, U+02DD-02FF, U+0304, U+0308, U+0329, U+1D00-1DBF, U+1E00-1E9F, U+1EF2-1EFF, U+2020, U+20A0-20AB, U+20AD-20C0, U+2113, U+2C60-2C7F, U+A720-A7FF;
}

@font-face {
    font-family: "IBM Plex Sans";
    src: url("fonts/ibm-plex-sans-latin-ext-600-normal.woff2") format("woff2");
    font-weight: 600;
    font-style: normal;
    font-display: swap;
    unicode-range: U+0100-02BA, U+02BD-02C5, U+02C7-02CC, U+02CE-02D7, U+02DD-02FF, U+0304, U+0308, U+0329, U+1D00-1DBF, U+1E00-1E9F, U+1EF2-1EFF, U+2020, U+20A0-20AB, U+20AD-20C0, U+2113, U+2C60-2C7F, U+A720-A7FF;
}

@font-face {
    font-family: "IBM Plex Mono";
    src: url("fonts/ibm-plex-mono-latin-400-normal.woff2") format("woff2");
    font-weight: 400;
    font-style: normal;
    font-display: swap;
    unicode-range: U+0000-00FF, U+0131, U+0152-0153, U+02BB-02BC, U+02C6, U+02DA, U+02DC, U+0304, U+0308, U+0329, U+2000-206F, U+20AC, U+2122, U+2191, U+2193, U+2212, U+2215, U+FEFF, U+FFFD;
}

@font-face {
    font-family: "IBM Plex Mono";
    src: url("fonts/ibm-plex-mono-latin-500-normal.woff2") format("woff2");
    font-weight: 500;
    font-style: normal;
    font-display: swap;
    unicode-range: U+0000-00FF, U+0131, U+0152-0153, U+02BB-02BC, U+02C6, U+02DA, U+02DC, U+0304, U+0308, U+0329, U+2000-206F, U+20AC, U+2122, U+2191, U+2193, U+2212, U+2215, U+FEFF, U+FFFD;
}

@font-face {
    font-family: "IBM Plex Mono";
    src: url("fonts/ibm-plex-mono-latin-ext-400-normal.woff2") format("woff2");
    font-weight: 400;
    font-style: normal;
    font-display: swap;
    unicode-range: U+0100-02BA, U+02BD-02C5, U+02C7-02CC, U+02CE-02D7, U+02DD-02FF, U+0304, U+0308, U+0329, U+1D00-1DBF, U+1E00-1E9F, U+1EF2-1EFF, U+2020, U+20A0-20AB, U+20AD-20C0, U+2113, U+2C60-2C7F, U+A720-A7FF;
}

@font-face {
    font-family: "IBM Plex Mono";
    src: url("fonts/ibm-plex-mono-latin-ext-500-normal.woff2") format("woff2");
    font-weight: 500;
    font-style: normal;
    font-display: swap;
    unicode-range: U+0100-02BA, U+02BD-02C5, U+02C7-02CC, U+02CE-02D7, U+02DD-02FF, U+0304, U+0308, U+0329, U+1D00-1DBF, U+1E00-1E9F, U+1EF2-1EFF, U+2020, U+20A0-20AB, U+20AD-20C0, U+2113, U+2C60-2C7F, U+A720-A7FF;
}

/* ----- Design tokens ----- */
:root {
    color-scheme: light;

    /* BEGIN pmq aliases — colour roles read the brand's semantic tokens (css/postmq-tokens.css); the token
       flips with the theme, so each of these lines is IDENTICAL in the dark blocks below. Map: app role → pmq
       token; the two non-semantic choices are explained inline. */
    --bg: var(--pmq-bg);
    --surface-1: var(--pmq-surface);
    --surface-2: var(--pmq-surface-2);
    --surface-3: var(--pmq-surface-3);
    --overlay: var(--pmq-overlay);
    --border: var(--pmq-border);
    --border-strong: var(--pmq-border-strong);
    /* The kit grew --pmq-border-field (backlog 01a0136e), so this is a semantic alias again rather than a raw
       scale step. It exists separately from --border-strong because a field boundary is the control's only
       visible edge and must clear WCAG's 3:1 non-text bar, which --pmq-border-strong (n300, 1.6:1 on Paper) does
       not — that token keeps the ghost button, the switch track and the dashed empty state. */
    --border-field: var(--pmq-border-field);
    --text: var(--pmq-text);
    --text-muted: var(--pmq-text-2);
    --text-faint: var(--pmq-text-3);
    --brand: var(--pmq-accent);
    --brand-hover: var(--pmq-accent-hover);
    --brand-contrast: var(--pmq-on-accent);
    --brand-tint: var(--pmq-accent-soft);
    --brand-ink: var(--pmq-accent-text);
    --link: var(--pmq-link);
    --link-hover: var(--pmq-link-hover);
    /* --pmq-focus is a box-shadow ring, not a colour; the app draws a 2px outline, and the kit names --pmq-accent
       as its focus border. */
    --focus: var(--pmq-accent);
    /* END pmq aliases */

    /* Status tones — fg/bg/border triplets; fg is AA on its own bg AND on the card surface (ThemeContrastTests) */
    --status-neutral-fg: #4c5565;
    --status-neutral-bg: #eef0f4;
    --status-neutral-bd: #d5dae2;
    --status-info-fg: #155bd0;
    --status-info-bg: #e8f0fe;
    --status-info-bd: #c4d8fb;
    /* The accent tone IS the brand accent, so it aliases the kit's accent-as-text / -soft / -line trio (the
       other five tones have no kit counterpart yet and keep their own values). */
    --status-accent-fg: var(--pmq-accent-text);
    --status-accent-bg: var(--pmq-accent-soft);
    --status-accent-bd: var(--pmq-accent-line);
    --status-success-fg: #177245;
    --status-success-bg: #e5f6ec;
    --status-success-bd: #bfe8cf;
    --status-warn-fg: #8a5300;
    --status-warn-bg: #fcf0dc;
    --status-warn-bd: #f0d9ac;
    --status-danger-fg: #b42323;
    --status-danger-bg: #fdebeb;
    --status-danger-bd: #f5c6c6;

    /* Icon-strength status colors (≥3:1 non-text on card) */
    --icon-success: #178a4c;
    --icon-warn: #b47207;
    --icon-danger: #dc2626;
    --icon-info: #155bd0;

    /* Charts — all ≥3:1 vs the card surface */
    --chart-1: var(--pmq-accent);   /* the brand accent; series 2-6 keep their own values */
    --chart-2: #0d8f6c;
    --chart-3: #b45309;
    --chart-4: #dc2626;
    --chart-5: #7c3aed;
    --chart-6: #0e7490;
    --chart-grid: #e9ecf2;
    --chart-axis: #c9cfd9;
    --chart-arrived: var(--chart-1);
    --chart-resolved: var(--chart-2);
    --chart-wip: var(--chart-3);
    --chart-p85: var(--chart-4);
    --chart-usage: var(--chart-5);

    /* Back-compat aliases — existing markup and scoped files keep resolving */
    --surface: var(--surface-1);
    --danger: var(--status-danger-fg);
    --danger-surface: var(--status-danger-bg);
    --success: var(--status-success-fg);
    --success-surface: var(--status-success-bg);
    --warn: var(--status-warn-fg);
    --warn-surface: var(--status-warn-bg);
    --accent: var(--brand);

    /* Type scale. The two families alias the kit's stacks (WP 2.2) — "IBM Plex Sans", then system-ui …; "IBM Plex
       Mono", then ui-monospace … — so the site and the app render one type system; the @font-face rules above are
       what make the first name resolve. These are theme-invariant (declared once in the token file), so unlike the
       colour aliases they are NOT repeated in the dark blocks. */
    --font-sans: var(--pmq-font-sans);
    --font-mono: var(--pmq-font-mono);
    --text-3xl: 2.25rem;
    --text-2xl: 1.5rem;
    --text-xl: 1.125rem;
    --text-lg: 0.9375rem;
    --text-md: 1rem;
    --text-body: 0.875rem;
    --text-sm: 0.8125rem;
    --text-xs: 0.75rem;
    --text-overline: 0.6875rem;

    /* Spacing */
    --space-1: 0.25rem;
    --space-2: 0.5rem;
    --space-3: 0.75rem;
    --space-4: 1rem;
    --space-5: 1.5rem;
    --space-6: 2rem;
    --space-7: 2.5rem;
    --space-8: 3rem;

    /* Radii — the kit's --pmq-r-* scale (WP 2.2), mapped by ROLE the way the kit's own components use it: inline
       code / id chips take xs (2px, the queue tick's corner), buttons + inputs + menu items md (6px — the app's
       one small-control step, unchanged from the 0.375rem it was), alerts + notices md, cards + tables + panels lg
       (10px), pills the pill step. Theme-invariant, so not repeated in the dark blocks. */
    --radius-xs: var(--pmq-r-xs);
    --radius-sm: var(--pmq-r-md);
    --radius: var(--pmq-r-md);
    --radius-lg: var(--pmq-r-lg);
    --radius-full: var(--pmq-r-pill);

    /* Elevation */
    --shadow-1: 0 1px 2px rgba(16, 20, 30, 0.05);
    --shadow-2: 0 1px 2px rgba(16, 20, 30, 0.05), 0 4px 12px -2px rgba(16, 20, 30, 0.08);
    --shadow-3: 0 2px 4px rgba(16, 20, 30, 0.06), 0 12px 32px -4px rgba(16, 20, 30, 0.14);
    --shadow: var(--shadow-1);

    /* Z-order */
    --z-sticky: 10;
    --z-nav: 30;
    --z-dropdown: 40;
    --z-modal: 50;
    --z-toast: 60;

    /* Motion */
    --ease-out: cubic-bezier(0.2, 0.8, 0.3, 1);
    --ease-in-out: cubic-bezier(0.45, 0, 0.25, 1);
    --dur-1: 100ms;
    --dur-2: 160ms;
    --dur-3: 240ms;

    /* Layout */
    --container: 80rem;
    --container-wide: 88rem;
    --topbar-h: 3.5rem;
    --sidebar-w: 15rem;
    --sidebar-w-rail: 4rem;
}

/*
 * DARK THEME BLOCK — this exact declaration list appears twice (media query + [data-theme="dark"]) and the two
 * copies must stay byte-identical between the BEGIN/END markers; a lexical test enforces parity.
 */
@media (prefers-color-scheme: dark) {
    :root:not([data-theme="light"]) {
        /* BEGIN dark tokens */
        color-scheme: dark;
        /* pmq aliases — identical to the light block: the token flips, not the alias */
        --bg: var(--pmq-bg);
        --surface-1: var(--pmq-surface);
        --surface-2: var(--pmq-surface-2);
        --surface-3: var(--pmq-surface-3);
        --overlay: var(--pmq-overlay);
        --border: var(--pmq-border);
        --border-strong: var(--pmq-border-strong);
        --border-field: var(--pmq-border-field);
        --text: var(--pmq-text);
        --text-muted: var(--pmq-text-2);
        --text-faint: var(--pmq-text-3);
        --brand: var(--pmq-accent);
        --brand-hover: var(--pmq-accent-hover);
        --brand-contrast: var(--pmq-on-accent);
        --brand-tint: var(--pmq-accent-soft);
        --brand-ink: var(--pmq-accent-text);
        --link: var(--pmq-link);
        --link-hover: var(--pmq-link-hover);
        --focus: var(--pmq-accent);
        --status-neutral-fg: #aab3c2;
        --status-neutral-bg: #232834;
        --status-neutral-bd: #39404f;
        --status-info-fg: #82b1ff;
        --status-info-bg: #152647;
        --status-info-bd: #27406e;
        --status-accent-fg: var(--pmq-accent-text);
        --status-accent-bg: var(--pmq-accent-soft);
        --status-accent-bd: var(--pmq-accent-line);
        --status-success-fg: #5ecf96;
        --status-success-bg: #0e2b1c;
        --status-success-bd: #1d4a31;
        --status-warn-fg: #e5b45c;
        --status-warn-bg: #2e2410;
        --status-warn-bd: #574314;
        --status-danger-fg: #f59a9a;
        --status-danger-bg: #391519;
        --status-danger-bd: #62262c;
        --icon-success: #5ecf96;
        --icon-warn: #e5b45c;
        --icon-danger: #f59a9a;
        --icon-info: #82b1ff;
        --chart-1: var(--pmq-accent);
        --chart-2: #34d399;
        --chart-3: #fbbf24;
        --chart-4: #f87171;
        --chart-5: #a78bfa;
        --chart-6: #22d3ee;
        --chart-grid: #1e222d;
        --chart-axis: #39404f;
        --shadow-1: 0 1px 2px rgba(0, 0, 0, 0.5);
        --shadow-2: 0 1px 2px rgba(0, 0, 0, 0.5), 0 6px 16px -4px rgba(0, 0, 0, 0.55);
        --shadow-3: 0 2px 6px rgba(0, 0, 0, 0.55), 0 16px 40px -8px rgba(0, 0, 0, 0.6);
        /* END dark tokens */
    }
}

/* Explicit user choice wins over the OS default (higher specificity than :root). */
:root[data-theme="light"] {
    color-scheme: light;
}

:root[data-theme="dark"] {
    /* BEGIN dark tokens */
    color-scheme: dark;
    /* pmq aliases — identical to the light block: the token flips, not the alias */
    --bg: var(--pmq-bg);
    --surface-1: var(--pmq-surface);
    --surface-2: var(--pmq-surface-2);
    --surface-3: var(--pmq-surface-3);
    --overlay: var(--pmq-overlay);
    --border: var(--pmq-border);
    --border-strong: var(--pmq-border-strong);
    --border-field: var(--pmq-border-field);
    --text: var(--pmq-text);
    --text-muted: var(--pmq-text-2);
    --text-faint: var(--pmq-text-3);
    --brand: var(--pmq-accent);
    --brand-hover: var(--pmq-accent-hover);
    --brand-contrast: var(--pmq-on-accent);
    --brand-tint: var(--pmq-accent-soft);
    --brand-ink: var(--pmq-accent-text);
    --link: var(--pmq-link);
    --link-hover: var(--pmq-link-hover);
    --focus: var(--pmq-accent);
    --status-neutral-fg: #aab3c2;
    --status-neutral-bg: #232834;
    --status-neutral-bd: #39404f;
    --status-info-fg: #82b1ff;
    --status-info-bg: #152647;
    --status-info-bd: #27406e;
    --status-accent-fg: var(--pmq-accent-text);
    --status-accent-bg: var(--pmq-accent-soft);
    --status-accent-bd: var(--pmq-accent-line);
    --status-success-fg: #5ecf96;
    --status-success-bg: #0e2b1c;
    --status-success-bd: #1d4a31;
    --status-warn-fg: #e5b45c;
    --status-warn-bg: #2e2410;
    --status-warn-bd: #574314;
    --status-danger-fg: #f59a9a;
    --status-danger-bg: #391519;
    --status-danger-bd: #62262c;
    --icon-success: #5ecf96;
    --icon-warn: #e5b45c;
    --icon-danger: #f59a9a;
    --icon-info: #82b1ff;
    --chart-1: var(--pmq-accent);
    --chart-2: #34d399;
    --chart-3: #fbbf24;
    --chart-4: #f87171;
    --chart-5: #a78bfa;
    --chart-6: #22d3ee;
    --chart-grid: #1e222d;
    --chart-axis: #39404f;
    --shadow-1: 0 1px 2px rgba(0, 0, 0, 0.5);
    --shadow-2: 0 1px 2px rgba(0, 0, 0, 0.5), 0 6px 16px -4px rgba(0, 0, 0, 0.55);
    --shadow-3: 0 2px 6px rgba(0, 0, 0, 0.55), 0 16px 40px -8px rgba(0, 0, 0, 0.6);
    /* END dark tokens */
}

/* ----- Reset + base ----- */
*,
*::before,
*::after {
    box-sizing: border-box;
}

html {
    -webkit-text-size-adjust: 100%;
}

body {
    margin: 0;
    font-family: var(--font-sans);
    font-size: var(--text-body);
    line-height: 1.55;
    color: var(--text);
    background: var(--bg);
    font-optical-sizing: auto;
}

h1,
h2,
h3,
h4 {
    line-height: 1.25;
    margin: 0 0 var(--space-3);
    color: var(--text);
    font-weight: 600;
}

h1 {
    font-size: var(--text-2xl);
    letter-spacing: -0.02em;
}

h2 {
    font-size: var(--text-xl);
    letter-spacing: -0.015em;
}

h3 {
    font-size: var(--text-lg);
    letter-spacing: -0.01em;
}

p {
    margin: 0 0 var(--space-3);
}

a {
    color: var(--link);
    text-decoration: none;
}

a:hover {
    color: var(--link-hover);
    text-decoration: underline;
    text-underline-offset: 2px;
}

/* In running text the underline stays always-on — never rely on color alone.
   The footer belongs here for the same reason and was missed: its links sit inline with the copyright
   line inside a plain <span>, which none of the selectors below reached, so they were distinguished by
   colour alone. axe caught it as `link-in-text-block` (serious, WCAG 2.2 AA 1.4.1) on the project
   dashboards — on a docs-only PR, which is what showed it was already on main rather than newly broken.
   The version link is included deliberately: it is a link in the same text block and needs the same
   treatment, whatever its class says. */
p a,
td a:not([class]),
.prose a,
.app-footer a {
    text-decoration: underline;
    text-underline-offset: 2px;
}

code {
    font-family: var(--font-mono);
    font-size: 0.85em;
    background: var(--surface-2);
    border: 1px solid var(--border);
    padding: 0.1em 0.35em;
    border-radius: var(--radius-xs);
}

/* Plex ships three weights (400/500/600) and no Bold: pin emphasis to 600 so the browser never synthesises a 700
   (which it would from `bolder` on a 400 parent). */
strong,
b {
    font-weight: 600;
}

::selection {
    background: var(--pmq-c100);
    color: var(--pmq-ink);
}

:root[data-theme="dark"] ::selection {
    background: var(--pmq-c800);
    color: var(--pmq-text);
}

@media (prefers-color-scheme: dark) {
    :root:not([data-theme="light"]) ::selection {
        background: var(--pmq-c800);
        color: var(--pmq-text);
    }
}

:focus-visible {
    outline: 2px solid var(--focus);
    outline-offset: 2px;
    border-radius: var(--radius-sm);
}

@media (prefers-reduced-motion: reduce) {
    *,
    *::before,
    *::after {
        animation-duration: 0.01ms !important;
        animation-iteration-count: 1 !important;
        transition-duration: 0.01ms !important;
        scroll-behavior: auto !important;
    }
}

/* ----- Utilities ----- */
.container {
    width: 100%;
    max-width: var(--container);
    margin-inline: auto;
    padding-inline: var(--space-4);
}


.stack-sm > * + * {
    margin-top: var(--space-2);
}

.stack-md > * + * {
    margin-top: var(--space-4);
}

.stack-lg > * + * {
    margin-top: var(--space-6);
}

.text-danger {
    color: var(--status-danger-fg);
}

.text-muted {
    color: var(--text-muted);
}

.tnum {
    font-variant-numeric: tabular-nums;
}

.sr-only {
    position: absolute;
    width: 1px;
    height: 1px;
    padding: 0;
    margin: -1px;
    overflow: hidden;
    clip: rect(0, 0, 0, 0);
    white-space: nowrap;
    border: 0;
}

.skip-link {
    position: absolute;
    left: -9999px;
    top: 0;
    z-index: 100;
    background: var(--brand);
    color: var(--brand-contrast);
    padding: var(--space-2) var(--space-4);
    border-radius: 0 0 var(--radius) 0;
}

.skip-link:focus {
    left: 0;
}

.icon {
    flex: none;
}

/* ----- App shell: topbar + sidebar + one scrolling content column ----- */
/* The shell is exactly the viewport; ONLY .app-content scrolls. This keeps the bar and sidebar put without
   sticky-inside-grid quirks (sticky pins within its own grid row, which is useless for an app bar). */
.app-shell {
    display: grid;
    grid-template-columns: auto 1fr;
    grid-template-rows: var(--topbar-h) 1fr;
    grid-template-areas:
        "bar bar"
        "side content";
    height: 100vh;
    overflow: hidden;
}

.app-bar {
    grid-area: bar;
    z-index: var(--z-nav);
    display: flex;
    align-items: center;
    gap: var(--space-3);
    height: var(--topbar-h);
    padding-inline: var(--space-4);
    background: var(--surface-1);
    border-bottom: 1px solid var(--border);
}

/* Two-class specificity beats .btn-icon's display regardless of rule order. */
.app-bar .app-bar__menu {
    display: none;
}

/* The brand lockup — the kit's .pmq-brand (design/brand/components/components.css), the same lockup the site's
   header renders (site/src/components/Brand.astro): the pmq monogram at 24px with its ink parts following the text
   colour so it survives the dark theme and the amber tick fixed (the ONE Amber in the app), then the lowercase
   `postmq` wordmark in Plex Mono 500 at 20px / -0.01em. No tile behind the mark: the brand book forbids recolouring
   it, and a Cobalt tile with a Paper glyph was a recolour. */
.brand {
    display: inline-flex;
    align-items: center;
    gap: 0.625rem;
    color: var(--text);
    text-decoration: none;
    font-family: var(--font-mono);
    font-weight: 500;
    font-size: 1.25rem;
    letter-spacing: -0.01em;
}

.brand:hover {
    color: var(--text);
    text-decoration: none;
}

.brand__mark {
    display: block;
    width: 1.5rem;
    height: 1.5rem;
    color: inherit;
}

.brand__mark .icon {
    display: block;
    width: 100%;
    height: 100%;
}

/* The app bar's postmq.com nav (SITE-PLAN §9.1). Four curated links, RIGHT-aligned so they sit with the theme
   toggle and the session action rather than beside the lockup — see PostMQ.Web.Navigation.MarketingSite for why
   four and not the site header's twelve. Muted so the row reads as chrome rather than as app navigation: the
   sidebar owns "where am I in the app", this row only says "the rest of postmq.com is that way".

   It is display:none below 900px, and that is a real trade rather than an oversight. At that width the bar has
   already gained the drawer button, and the signed-out bar carries Sign in + Sign up as well — four more links do
   not fit on a phone without wrapping the bar to two rows. The brand lockup keeps its postmq.com target at EVERY
   width, so the route back to the site is never lost; only the shortcuts to four specific pages are. */
.app-bar__site-nav {
    display: none;
}

.app-bar__site-list {
    display: flex;
    align-items: center;
    gap: var(--space-4);
    margin: 0;
    padding: 0;
    list-style: none;
}

.app-bar__site-list a {
    color: var(--text-muted);
    text-decoration: none;
    font-size: 0.875rem;
    white-space: nowrap;
}

.app-bar__site-list a:hover {
    color: var(--text);
    text-decoration: none;
}


.app-bar__actions {
    display: flex;
    align-items: center;
    gap: var(--space-2);
    margin-inline-start: auto;
}

@media (min-width: 900px) {
    .app-bar__site-nav {
        display: block;
        /* The auto margin lives HERE rather than on .app-bar__actions, which is what right-aligns the nav: the
           free space collects to its left, so the nav and the session actions read as one right-hand cluster.
           Exactly ONE of the two may carry it — two auto margins split the free space between them and would
           open a gap down the middle of the cluster, which is the bug this rule looks like at a glance. */
        margin-inline-start: auto;
        /* Separation from the actions must EXCEED the nav's own 1rem item gap, or the last link (MCP) reads as
           belonging to the theme toggle. 0.75rem here plus the bar's 0.75rem gap = 1.5rem between the groups. */
        margin-inline-end: var(--space-3);
    }

    /* Yielded to the nav above, and this block MUST stay below the base rule: a media query adds no
       specificity, so an override written above it simply loses to the later `auto` and BOTH items end up with
       auto margins — flex then splits the free space between them and opens a gap down the middle of the
       cluster. That is what shipped for one iteration here; only looking at it in a browser caught it, because
       the DOM is identical either way. The base rule keeps `auto` for narrow viewports, where the nav is
       display:none and the actions are once again the only thing that can push itself right. */
    .app-bar__actions {
        margin-inline-start: 0;
    }
}


.inline-form {
    display: inline;
    margin: 0;
}

/* Workspace switcher (app bar). Renders only for a human who reaches more than one workspace, so it must not
   reserve space when absent — no fixed width, no placeholder. */
.workspace-switcher {
    display: flex;
    align-items: center;
    gap: var(--space-2);
    margin: 0;
}

/* Visible on wide screens, and the accessible name in every case: the select is the only unlabelled control in
   the bar, and "Workspace" is what tells a screen-reader user what the names in it are. */
.workspace-switcher__label {
    font-size: var(--text-sm);
    color: var(--text-muted);
    white-space: nowrap;
}

.workspace-switcher__select {
    font: inherit;
    font-size: var(--text-sm);
    max-width: 14rem;
    padding: var(--space-1) var(--space-2);
    border: 1px solid var(--border-strong);
    border-radius: var(--radius-sm);
    background: var(--bg);
    color: var(--text);
    transition: border-color var(--dur-1) var(--ease-out);
}

.workspace-switcher__select:hover {
    border-color: var(--border-field);
}

/* The bar gets crowded before the sidebar does; below this the label goes and the select carries its own
   accessible name from the <label>, which stays in the accessibility tree. */
@media (max-width: 900px) {
    .workspace-switcher__label {
        position: absolute;
        width: 1px;
        height: 1px;
        overflow: hidden;
        clip-path: inset(50%);
        white-space: nowrap;
    }

    .workspace-switcher__select {
        max-width: 8rem;
    }
}

/* Sidebar */
.app-sidebar {
    grid-area: side;
    display: flex;
    flex-direction: column;
    width: var(--sidebar-w);
    height: 100%;
    min-height: 0;
    background: var(--surface-1);
    border-right: 1px solid var(--border);
    z-index: calc(var(--z-nav) - 1);
}

.app-sidebar__nav {
    display: flex;
    flex-direction: column;
    flex: 1;
    min-height: 0;
}

.sidebar__scroll {
    flex: 1;
    min-height: 0;
    overflow-y: auto;
    padding: var(--space-3);
}

.sidebar__footer {
    border-top: 1px solid var(--border);
    padding: var(--space-2) var(--space-3);
    display: grid;
    gap: var(--space-1);
}

.side-list {
    list-style: none;
    margin: 0;
    padding: 0;
    display: grid;
    gap: 1px;
}

.nav-group {
    margin-top: var(--space-4);
}

.nav-group--operator {
    border-top: 1px solid var(--border);
    padding-top: var(--space-3);
}

.nav-group__header {
    display: block;
    padding: 0 var(--space-2) var(--space-1);
    font-size: var(--text-overline);
    font-weight: 600;
    text-transform: uppercase;
    letter-spacing: 0.08em;
    color: var(--text-muted);
}

.side-link {
    position: relative;
    display: flex;
    align-items: center;
    gap: var(--space-2);
    width: 100%;
    min-height: 2.5rem;
    padding: 0.4rem var(--space-2);
    border: 0;
    border-radius: var(--radius-sm);
    background: none;
    color: var(--text-muted);
    text-decoration: none;
    font: inherit;
    font-size: var(--text-sm);
    font-weight: 500;
    cursor: pointer;
    transition: background-color var(--dur-1) var(--ease-out), color var(--dur-1) var(--ease-out);
}

.side-link__icon {
    width: 1.125rem;
    height: 1.125rem;
    flex: none;
}

.side-link:hover {
    background: var(--surface-2);
    color: var(--text);
    text-decoration: none;
}

.side-link.active,
.side-link[aria-current="page"] {
    background: var(--brand-tint);
    color: var(--brand-ink);
}

.side-link.active::before {
    content: "";
    position: absolute;
    left: -0.75rem;
    top: 20%;
    bottom: 20%;
    width: 3px;
    border-radius: var(--radius-full);
    background: var(--brand);
}

/* Collapsed rail (persisted preference; ≥900px only — the drawer overrides below) */
@media (min-width: 900px) {
    html[data-sidebar="collapsed"] .app-sidebar {
        width: var(--sidebar-w-rail);
    }

    html[data-sidebar="collapsed"] .side-link {
        justify-content: center;
        padding-inline: 0;
    }

    html[data-sidebar="collapsed"] .side-link__label,
    html[data-sidebar="collapsed"] .nav-group__header {
        position: absolute;
        width: 1px;
        height: 1px;
        padding: 0;
        margin: -1px;
        overflow: hidden;
        clip: rect(0, 0, 0, 0);
        white-space: nowrap;
        border: 0;
    }

    html[data-sidebar="collapsed"] .nav-group {
        border-top: 1px solid var(--border);
        margin-top: var(--space-2);
        padding-top: var(--space-2);
    }

    html[data-sidebar="collapsed"] .side-link.active::before {
        left: -0.5rem;
    }
}

/* Mobile drawer (<900px): off-canvas, backdrop, focus handled in MainLayout */
.app-drawer-backdrop {
    display: none;
}

@media (max-width: 899.98px) {
    .app-shell {
        grid-template-columns: 1fr;
        grid-template-areas:
            "bar"
            "content";
    }

    .app-bar .app-bar__menu {
        display: inline-flex;
    }

    /* Closed, the drawer is visibility:hidden as well as translated off-canvas: a translated element is still in the
       tab order and the accessibility tree, so Tab from the menu button would walk 20 invisible links. Visibility
       flips to hidden only AFTER the slide-out (the delayed second transition) and to visible at once on open, so
       the animation is unchanged and MainLayout's post-render focus call always finds a focusable link. */
    .app-sidebar {
        position: fixed;
        top: var(--topbar-h);
        left: 0;
        height: calc(100vh - var(--topbar-h));
        width: min(18rem, 85vw);
        transform: translateX(-100%);
        visibility: hidden;
        transition: transform var(--dur-3) var(--ease-out), visibility 0s linear var(--dur-3);
        box-shadow: none;
        z-index: var(--z-modal);
    }

    .app-shell--drawer-open .app-sidebar {
        transform: none;
        visibility: visible;
        transition: transform var(--dur-3) var(--ease-out), visibility 0s;
        box-shadow: var(--shadow-3);
    }

    .app-shell--drawer-open .app-drawer-backdrop {
        display: block;
        position: fixed;
        inset: var(--topbar-h) 0 0 0;
        background: var(--overlay);
        z-index: calc(var(--z-modal) - 1);
    }

    body:has(.app-shell--drawer-open) {
        overflow: hidden;
    }

    .side-link {
        min-height: 2.75rem;
    }

    .sidebar__collapse {
        display: none;
    }
}

.app-content {
    grid-area: content;
    min-width: 0;
    min-height: 0;
    overflow-y: auto;
    display: flex;
    flex-direction: column;
}

.app-main {
    flex: 1 0 auto;
    min-width: 0;
    padding-block: var(--space-6);
}

.app-main:focus {
    outline: none;
}

.app-footer {
    border-top: 1px solid var(--border);
    padding: var(--space-4) 0;
    color: var(--text-muted);
    font-size: var(--text-sm);
}

.app-footer__inner {
    display: flex;
    justify-content: space-between;
    gap: var(--space-4);
    flex-wrap: wrap;
}

/* Legacy inline nav-link (in-table "View"/"Open" links on unmigrated pages) */
.nav-link {
    display: inline-block;
    padding: var(--space-1) var(--space-2);
    border-radius: var(--radius-sm);
    color: var(--link);
    text-decoration: none;
    font-weight: 500;
    font-size: var(--text-sm);
}

.nav-link:hover {
    background: var(--surface-2);
    text-decoration: underline;
    text-underline-offset: 2px;
}

/* ----- Buttons ----- */
.btn,
.btn-primary,
.btn-secondary,
.btn-danger,

.btn-primary {
    background: var(--brand);
    color: var(--brand-contrast);
    box-shadow: var(--shadow-1);
}

.btn-primary:hover {
    background: var(--brand-hover);
    color: var(--brand-contrast);
    text-decoration: none;
}

.btn-primary:active {
    box-shadow: none;
    translate: 0 1px;
}

.btn-secondary {
    background: var(--surface-1);
    color: var(--text);
    border-color: var(--border-strong);
    box-shadow: var(--shadow-1);
}

.btn-secondary:hover {
    background: var(--surface-2);
    border-color: var(--border-field);
    color: var(--text);
    text-decoration: none;
}

.btn-danger {
    background: transparent;
    color: var(--status-danger-fg);
    border-color: var(--status-danger-bd);
}

.btn-danger:hover {
    background: var(--status-danger-bg);
}


:is(.btn, .btn-primary, .btn-secondary, .btn-danger):disabled {
    opacity: 0.5;
    cursor: not-allowed;
    translate: none;
}

.btn-icon {
    display: inline-flex;
    align-items: center;
    justify-content: center;
    width: 2.25rem;
    height: 2.25rem;
    border-radius: var(--radius-sm);
    border: 1px solid var(--border-strong);
    background: var(--surface-1);
    color: var(--text);
    cursor: pointer;
    transition: background-color var(--dur-1) var(--ease-out), border-color var(--dur-1) var(--ease-out);
}

.btn-icon:hover {
    background: var(--surface-2);
}

.btn-icon:disabled {
    opacity: 0.5;
    cursor: not-allowed;
}

.btn-sm {
    padding: 0.3rem 0.65rem;
    font-size: var(--text-xs);
}

.btn-icon.btn-sm {
    width: 1.75rem;
    height: 1.75rem;
    padding: 0;
}

/* Loading state: pair with a text swap to "Loading…" so the state never relies on the spinner alone. */


@keyframes btn-spin {
    to {
        rotate: 360deg;
    }
}

/* ----- Auth (login + signup) ----- */
.auth-card {
    max-width: 28rem;
    margin-inline: auto;
    background: var(--surface-1);
    border: 1px solid var(--border);
    border-radius: var(--radius-lg);
    padding: var(--space-6);
    box-shadow: var(--shadow-2);
}

.field__hint {
    font-size: var(--text-xs);
    color: var(--text-muted);
}

/*
    The support form's honeypot field: reachable by a script that fills every input, reachable by nobody else.

    Positioned off-canvas rather than `display: none` on purpose. A crawler that skips hidden inputs skips a
    display:none one, which defeats the whole point — the field has to look fillable to something reading the DOM
    while being unreachable to a person. Sighted users cannot see it, pointer users cannot hit it, `tabindex="-1"`
    keeps it out of the keyboard order, and the wrapper's `aria-hidden` keeps it out of the accessibility tree, so a
    screen-reader user is never offered it either.
*/
.hp-field {
    position: absolute;
    left: -9999px;
    width: 1px;
    height: 1px;
    overflow: hidden;
}

.field-check {
    display: flex;
    align-items: flex-start;
    gap: var(--space-2);
    margin-bottom: var(--space-4);
}

.field-check input {
    margin-top: 0.2rem;
    flex: none;
    accent-color: var(--brand);
}

.field-check label {
    font-size: var(--text-sm);
}

/* A multi-select list inside a .field — used by the §12.4 decide form to pick which policy versions a takedown
   disables. Needed because `.field input` above styles inputs as TEXT boxes (padding, border, background), which a
   checkbox must opt out of; without the reset the boxes render as empty bordered rectangles. */
.checkbox-list {
    list-style: none;
    margin: 0;
    padding: 0;
    display: flex;
    flex-direction: column;
    gap: var(--space-2);
}

.checkbox-row {
    display: flex;
    align-items: flex-start;
    gap: var(--space-2);
    font-size: var(--text-sm);
    font-weight: 400;
}

.checkbox-row input[type="checkbox"] {
    margin-top: 0.2rem;
    padding: 0;
    flex: none;
    width: auto;
    border: none;
    background: none;
    accent-color: var(--brand);
}

/* The row's label text: the policy title, its version, and any state badges, wrapping as one line of prose. */
.checkbox-row > span {
    display: flex;
    align-items: center;
    flex-wrap: wrap;
    gap: var(--space-2);
}

/* The identity of a selectable target (policy ULID, and the workspace when a notice spans several). Muted and
   smaller than the title: it is what DISAMBIGUATES two same-titled rows, not what the operator reads first. */
.target-id {
    font-size: var(--text-xs);
    color: var(--text-muted);
}

/* A .field whose control is a list rather than a single input still needs a heading, and it must NOT be a <label>
   (a label pointing at nothing is a screen-reader dead end). */
.field__label {
    font-weight: 500;
    font-size: var(--text-sm);
}

/* SSO sign-in: an "or" rule between the password form and the provider buttons, and full-width provider buttons
   so a stack of them reads as one group rather than as ragged individual controls. */
.auth-divider {
    display: flex;
    align-items: center;
    gap: var(--space-3);
    margin: var(--space-5) 0 var(--space-4);
    color: var(--text-muted);
    font-size: var(--text-sm);
}

.auth-divider::before,
.auth-divider::after {
    content: "";
    flex: 1;
    height: 1px;
    background: var(--border);
}

.btn-block {
    display: block;
    width: 100%;
}

.auth-alt {
    margin-top: var(--space-5);
    padding-top: var(--space-4);
    border-top: 1px solid var(--border);
    color: var(--text-muted);
    font-size: var(--text-sm);
}

/* ----- Auth split-pane shell (AuthLayout; SITE-PLAN §9.1 / WP 2.3) -----
   Two panes ≥900px (the app's one breakpoint): the form pane on the left — brand lockup, one line, the page's
   .auth-card untouched, a Docs + theme-toggle footer — and a static context strip on the right. Below 900px only
   the form pane renders. The card's own rules above are NOT restated here: the layout wraps the card, it does not
   restyle it — the two exceptions are the phone-only ones the audit measured (16px inputs, 44px primary CTA). */
.auth-shell {
    display: grid;
    grid-template-columns: minmax(0, 1fr);
    min-height: 100vh;
}

.auth-pane {
    display: flex;
    flex-direction: column;
    min-width: 0;
}

/* The form pane's column: card-width, centred vertically with auto margins (auto margins absorb only POSITIVE free
   space, so a short viewport scrolls the column instead of clipping its top the way justify-content: center would). */
.auth-pane--form {
    padding: var(--space-6) var(--space-5);
}

.auth-pane__inner {
    width: 100%;
    max-width: 28rem;
    margin: auto;
}

/* The pages render <section class="container"> around the card; inside a card-width pane its side padding would only
   shrink the card. */
.auth-pane--form .container {
    padding-inline: 0;
}

.auth-pane__head {
    display: grid;
    gap: var(--space-2);
    margin-bottom: var(--space-5);
}

.auth-pane__lead {
    margin: 0;
    color: var(--text-muted);
    font-size: var(--text-sm);
}

.auth-pane__foot {
    display: flex;
    align-items: center;
    justify-content: space-between;
    gap: var(--space-3);
    margin-top: var(--space-6);
    font-size: var(--text-sm);
}

/* The context pane: hidden until the split (below), so a phone gets the form and nothing else. */
.auth-pane--context {
    display: none;
    align-items: center;
    justify-content: center;
    padding: var(--space-8) var(--space-6);
    background: var(--surface-2);
    border-left: 1px solid var(--border);
}

.auth-strip {
    width: 100%;
    max-width: 34rem;
}

.auth-strip__eyebrow {
    margin: 0 0 var(--space-3);
    font-size: var(--text-overline);
    font-weight: 600;
    text-transform: uppercase;
    letter-spacing: 0.08em;
    color: var(--text-muted);
}

.auth-strip__list {
    background: var(--surface-1);
    border: 1px solid var(--border);
    border-radius: var(--radius-lg);
    padding-inline: var(--space-4);
}

/* A non-link twin of .overview-list__link: same grid, no hover state, so a static row never reads as clickable. */
.overview-list__row {
    display: grid;
    gap: 0.15rem;
    padding: var(--space-3) var(--space-1);
}

.auth-strip .overview-list__title {
    white-space: normal;
}

.auth-strip__caption {
    margin: var(--space-3) 0 0;
    font-size: var(--text-xs);
    color: var(--text-muted);
}

@media (min-width: 900px) {
    .auth-shell {
        grid-template-columns: minmax(0, 31rem) minmax(0, 1fr);
    }

    .auth-pane--context {
        display: flex;
    }
}

/* The audit's two measured phone defects, fixed at every width (the auth card is a sparse form; only the dense
   dashboard needs 13px controls, and it is not in this shell). 16px is a literal, not a rem: iOS Safari zooms any
   focused control whose computed font-size is under 16px, and that threshold does not follow the root size. 44px is
   the platform's minimum tap target. */
.auth-shell .auth-card input,
.auth-shell .auth-card select,
.auth-shell .auth-card textarea {
    font-size: 16px;
}

.auth-shell .btn-primary {
    min-height: 44px;
}

/* Full-bleed card on a small phone: the pane is the card. */
@media (max-width: 639.98px) {
    .auth-pane--form {
        padding: var(--space-5) var(--space-4);
        background: var(--surface-1);
    }

    .auth-shell .auth-card {
        border: 0;
        border-radius: 0;
        box-shadow: none;
        padding: 0;
    }
}

/* The manually-entered MFA setup key / otpauth URI (no QR — CSP-clean). */
.mfa-code {
    display: block;
    margin-top: var(--space-1);
    padding: var(--space-2) var(--space-3);
    background: var(--surface-2);
    border-radius: var(--radius-sm);
    word-break: break-all;
    font-size: var(--text-sm);
}

/* A once-disclosed secret (issued/rotated credential plaintext): a read-only monospace field the user copies. */
.secret-value {
    font-family: var(--font-mono);
    word-break: break-all;
}

/* Doubled selector on purpose: .alert (declared later in this file) is a flex ROW, and a bare .secret-reveal loses
   to it at equal specificity — which laid the title, the secret field, the two config blocks and the button out
   side by side. The reveal is a stacked column. */
.alert.secret-reveal {
    display: grid;
    grid-template-columns: minmax(0, 1fr);
    gap: var(--space-3);
}

.alert.secret-reveal > .btn-primary {
    justify-self: start;
}

/* One block of the reveal — the paste-ready MCP configs first, the raw secret second (WP 2.6). */
.secret-reveal__block {
    display: grid;
    gap: var(--space-2);
}

/* The copy outcome, announced through aria-live; empty until a Copy button is pressed. */
.secret-reveal__status {
    margin: 0;
    font-size: var(--text-sm);
    color: var(--text-muted);
    min-height: 1.25em;
}

/* A code block's caption row: what the block is on the left, its Copy button on the right. */
.code-block-head {
    display: flex;
    align-items: center;
    justify-content: space-between;
    gap: var(--space-2);
    font-size: var(--text-sm);
    font-weight: 600;
    margin-top: var(--space-2);
}

/* A read-only, horizontally-scrollable code block (rendered JSON payloads, the MCP client config, …). */
.code-block {
    font-family: var(--font-mono);
    font-size: var(--text-sm);
    line-height: 1.5;
    background: var(--surface-2);
    border: 1px solid var(--border);
    border-radius: var(--radius-sm);
    padding: var(--space-3);
    margin: 0;
    overflow-x: auto;
    white-space: pre;
    max-height: 32rem;
}

/* The <code> inside a code block is the block's text, not an inline code span: no per-line chip styling. */
.code-block code {
    font-size: inherit;
    background: none;
    border: 0;
    padding: 0;
    border-radius: 0;
}

/* Markdown-ish long text (session state, backlog bodies, lesson prose): wrap, never overflow. */
.markdown-block,
.prewrap {
    white-space: pre-wrap;
    overflow-wrap: break-word;
    font-family: var(--font-sans);
    font-size: var(--text-sm);
    background: var(--surface-2);
    border: 1px solid var(--border);
    border-radius: var(--radius-sm);
    padding: var(--space-3);
    max-height: 40rem;
    overflow-y: auto;
}

/* A grouped checkbox set (credential scopes) — reset the browser fieldset chrome to match the design system. */
fieldset.field {
    border: 0;
    padding: 0;
    margin: 0;
    min-width: 0;
}

fieldset.field > legend {
    padding: 0;
    color: var(--text-muted);
    font-weight: 600;
    font-size: var(--text-sm);
}

/* ----- Signed-out landing (the app door) ----- */
/* The door's H1 is the site's H1 and must read like one: before this rule it fell through to the base h1 (24 px) —
   SITE-PLAN §9.1 measured it. Size, weight and measure only (the kit's hero title is 48 px at 16ch; this is the same
   measure at the app's scale), no colour — it sits outside both dark token blocks. */
.hero h1 {
    font-size: var(--text-3xl);
    font-weight: 600;
    letter-spacing: -0.022em;
    text-wrap: balance;
    max-width: 22ch;
}

.hero__lead {
    font-size: var(--text-md);
    color: var(--text-muted);
    max-width: 46rem;
}

/* ----- Cards ----- */

.card-grid {
    display: grid;
    grid-template-columns: repeat(auto-fit, minmax(15rem, 1fr));
    gap: var(--space-4);
}

.card {
    background: var(--surface-1);
    border: 1px solid var(--border);
    border-radius: var(--radius-lg);
    padding: var(--space-5);
    box-shadow: var(--shadow-1);
}

.card__title {
    display: flex;
    align-items: center;
    gap: var(--space-2);
    font-size: var(--text-lg);
}

.card--danger {
    border-color: var(--status-danger-bd);
    border-left: 3px solid var(--icon-danger);
}

/* A term/description grid for a metadata panel (dt in the muted first column, dd in the second). */
.detail-list {
    display: grid;
    gap: 0;
}

.detail-list > div {
    display: grid;
    grid-template-columns: minmax(9rem, 12rem) 1fr;
    gap: var(--space-4);
    align-items: baseline;
    padding: var(--space-2) 0;
    border-bottom: 1px solid var(--border);
}

.detail-list > div:last-child {
    border-bottom: 0;
}

.detail-list dt {
    font-size: var(--text-overline);
    font-weight: 600;
    text-transform: uppercase;
    letter-spacing: 0.08em;
    color: var(--text-muted);
}

.detail-list dd {
    margin: 0;
    font-size: var(--text-sm);
    overflow-wrap: anywhere;
}

.detail-list--cols {
    grid-template-columns: repeat(auto-fit, minmax(11rem, 1fr));
    gap: var(--space-4);
}

.detail-list--cols > div {
    grid-template-columns: 1fr;
    gap: var(--space-1);
    border-bottom: 0;
    padding: 0;
}

/* A horizontal group of buttons (confirm / cancel). */
.button-row {
    display: flex;
    flex-wrap: wrap;
    gap: var(--space-3);
}

.step-badge {
    display: inline-flex;
    align-items: center;
    justify-content: center;
    width: 1.6rem;
    height: 1.6rem;
    border-radius: 50%;
    background: var(--brand);
    color: var(--brand-contrast);
    font-size: var(--text-xs);
    font-weight: 600;
    flex: none;
}

.step-badge--done {
    background: var(--status-success-bg);
    color: var(--icon-success);
    border: 1px solid var(--status-success-bd);
}

/* ----- First-run checklist: two tracks (SITE-PLAN §9.3) ----- */
.track-grid {
    display: grid;
    grid-template-columns: repeat(auto-fit, minmax(18rem, 1fr));
    gap: var(--space-4);
    margin-top: var(--space-4);
}

.track {
    background: var(--surface-2);
    border: 1px solid var(--border);
    border-radius: var(--radius-lg);
    padding: var(--space-4);
}

.track__title {
    margin: 0 0 var(--space-3);
    font-size: var(--text-md);
}

.track__steps {
    list-style: none;
    margin: 0;
    padding: 0;
    display: grid;
    gap: var(--space-3);
}

.track__step {
    display: flex;
    align-items: flex-start;
    gap: var(--space-3);
}

.track__step-body {
    display: grid;
    gap: var(--space-1);
    min-width: 0;
}

.track__step-title {
    font-weight: 600;
}

.track__step[data-done="true"] .track__step-title {
    color: var(--text-muted);
}

.track__step-text {
    margin: 0;
    color: var(--text-muted);
    font-size: var(--text-sm);
}

.track__step-link {
    font-size: var(--text-sm);
    font-weight: 500;
}

/* ----- Page header + breadcrumb ----- */
.page-head {
    display: grid;
    gap: var(--space-2);
    margin-bottom: var(--space-5);
}


.page-head__title {
    margin: 0;
}


.page-head__lead {
    color: var(--text-muted);
    max-width: 46rem;
    margin: 0;
}


/* ----- Stat / KPI cards ----- */
.stat-grid {
    display: grid;
    grid-template-columns: repeat(auto-fill, minmax(11rem, 1fr));
    gap: var(--space-3);
}

.stat-card {
    display: flex;
    flex-direction: column;
    gap: var(--space-1);
    padding: var(--space-4);
    background: var(--surface-1);
    border: 1px solid var(--border);
    border-radius: var(--radius-lg);
    box-shadow: var(--shadow-1);
    min-width: 0;
    color: inherit;
    text-decoration: none;
}

a.stat-card:hover {
    text-decoration: none;
    border-color: var(--border-strong);
    color: inherit;
}

.card .stat-card {
    background: var(--surface-2);
    border-color: transparent;
    box-shadow: none;
}

.stat-card__label {
    font-size: var(--text-overline);
    text-transform: uppercase;
    letter-spacing: 0.08em;
    font-weight: 600;
    color: var(--text-muted);
}

.stat-card__value {
    font-size: 1.625rem;
    font-weight: 600;
    letter-spacing: -0.02em;
    line-height: 1.15;
    font-variant-numeric: tabular-nums;
}

.stat-card__sub {
    font-size: var(--text-xs);
    color: var(--text-muted);
}


/* ----- Status pills (+ legacy badge aliases) ----- */
.pill {
    display: inline-flex;
    align-items: center;
    gap: 0.35rem;
    padding: 0.1rem 0.55rem;
    border-radius: var(--radius-full);
    font-size: var(--text-xs);
    font-weight: 500;
    line-height: 1.5;
    white-space: nowrap;
    background: var(--pill-bg, var(--surface-2));
    color: var(--pill-fg, var(--text-muted));
    border: 1px solid var(--pill-bd, var(--border));
}

.pill .icon {
    width: 0.75rem;
    height: 0.75rem;
}

.pill__dot {
    width: 0.4rem;
    height: 0.4rem;
    border-radius: 50%;
    background: currentColor;
    flex: none;
}

.pill__dot--pulse {
    animation: pill-pulse 2s var(--ease-in-out) infinite;
}

@keyframes pill-pulse {
    0%,
    100% {
        opacity: 1;
    }

    50% {
        opacity: 0.35;
    }
}

.pill--neutral {
    --pill-fg: var(--status-neutral-fg);
    --pill-bg: var(--status-neutral-bg);
    --pill-bd: var(--status-neutral-bd);
}

.pill--info {
    --pill-fg: var(--status-info-fg);
    --pill-bg: var(--status-info-bg);
    --pill-bd: var(--status-info-bd);
}

.pill--accent {
    --pill-fg: var(--status-accent-fg);
    --pill-bg: var(--status-accent-bg);
    --pill-bd: var(--status-accent-bd);
}

.pill--success {
    --pill-fg: var(--status-success-fg);
    --pill-bg: var(--status-success-bg);
    --pill-bd: var(--status-success-bd);
}

.pill--warn {
    --pill-fg: var(--status-warn-fg);
    --pill-bg: var(--status-warn-bg);
    --pill-bd: var(--status-warn-bd);
}

.pill--danger {
    --pill-fg: var(--status-danger-fg);
    --pill-bg: var(--status-danger-bg);
    --pill-bd: var(--status-danger-bd);
}

.pill--outline {
    background: transparent;
}

/* Legacy .badge family — restyled onto the pill tone tokens so unmigrated pages match migrated ones. */
.badge {
    display: inline-flex;
    align-items: center;
    gap: 0.35rem;
    padding: 0.1rem 0.55rem;
    border-radius: var(--radius-full);
    font-size: var(--text-xs);
    font-weight: 500;
    line-height: 1.5;
    white-space: nowrap;
    background: var(--status-neutral-bg);
    color: var(--status-neutral-fg);
    border: 1px solid var(--status-neutral-bd);
}

.badge--ok {
    background: var(--status-success-bg);
    color: var(--status-success-fg);
    border-color: var(--status-success-bd);
}

.badge--soon {
    background: var(--status-warn-bg);
    color: var(--status-warn-fg);
    border-color: var(--status-warn-bd);
}

.badge--danger {
    background: var(--status-danger-bg);
    color: var(--status-danger-fg);
    border-color: var(--status-danger-bd);
}

/* ----- Notices + alerts ----- */
.notice {
    background: var(--surface-1);
    border: 1px solid var(--border);
    border-left: 3px solid var(--brand);
    border-radius: var(--radius);
    padding: var(--space-4);
    color: var(--text-muted);
    font-size: var(--text-sm);
}

.alert {
    display: flex;
    gap: var(--space-3);
    align-items: flex-start;
    padding: var(--space-3) var(--space-4);
    border: 1px solid var(--alert-bd, var(--border));
    border-left: 3px solid var(--alert-accent, var(--brand));
    border-radius: var(--radius);
    background: var(--alert-bg, var(--surface-1));
    font-size: var(--text-sm);
    color: var(--text);
}

.alert__icon {
    width: 1rem;
    height: 1rem;
    margin-top: 0.15rem;
    color: var(--alert-accent, var(--brand));
}

.alert__body {
    flex: 1;
}

.alert--danger {
    --alert-bg: var(--status-danger-bg);
    --alert-bd: var(--status-danger-bd);
    --alert-accent: var(--icon-danger);
}

.alert--success {
    --alert-bg: var(--status-success-bg);
    --alert-bd: var(--status-success-bd);
    --alert-accent: var(--icon-success);
}

.alert--warn {
    --alert-bg: var(--status-warn-bg);
    --alert-bd: var(--status-warn-bd);
    --alert-accent: var(--icon-warn);
}

.alert--info {
    --alert-bg: var(--status-info-bg);
    --alert-bd: var(--status-info-bd);
    --alert-accent: var(--icon-info);
}

/* ----- History-window notice (WP 4.5) -----
   The dashboard half of the plan history-window signal, rendered by HistoryWindowBanner.razor. It is the only
   thing that tells a reader "your plan withheld older rows" rather than "there are no older rows", and it shipped
   with NO style rule at all (backlog 01a01c4e) — degrading to a bare <p> with a bold lead on every surface that
   uses it. bUnit stayed green throughout, because bUnit asserts markup and not appearance.

   Styled as INFORMATION, deliberately not as an error: the boundary is the plan working as sold, not a fault —
   the component's own header says so, and making it look like a warning would misreport a correct answer. It
   borrows the `.alert--info` tokens rather than inventing colours, so it inherits both themes for free, but keeps
   its own class because it is a paragraph in the page flow rather than a dismissible alert box. */
.historywindow {
    margin: 0 0 var(--space-4);
    padding: var(--space-3) var(--space-4);
    border: 1px solid var(--status-info-bd);
    border-left: 3px solid var(--icon-info);
    border-radius: var(--radius);
    background: var(--status-info-bg);
    color: var(--text);
    font-size: var(--text-sm);
    line-height: 1.5;
}

/* The lead sentence. In the TRUNCATED branch this is a <strong> and carries the finding; in the untruncated one it
   is the whole notice and is a <span>, so the rule must not assume either element. Both read as the first line, so
   the span is promoted to a block and the strong inherits it. */
.historywindow__lead {
    display: block;
    margin-bottom: var(--space-1);
    color: var(--text);
}

/* Truncated: rows were withheld, so the lead is the finding and reads as one. The emphasis is on the STATE rather
   than on the <strong> element that happens to carry it — the two branches must be able to differ in markup
   without silently differing in weight. */
.historywindow--truncated .historywindow__lead {
    font-weight: 600;
}

/* Untruncated: nothing was withheld, so the notice is a footnote rather than a finding — one line, no emphasis.
   Keyed on an explicit modifier rather than `:only-child`. The structural selector worked, because the truncated
   branch has two element children and this one has a single span — but it would have changed meaning silently the
   first time anyone added an element to either branch, which is a spelling standing in for a state. */
.historywindow--complete .historywindow__lead {
    margin-bottom: 0;
    color: var(--text-muted);
}


/* ----- Forms ----- */
.field {
    display: flex;
    flex-direction: column;
    gap: var(--space-1);
    margin-bottom: var(--space-4);
}

.field > label {
    font-weight: 500;
    font-size: var(--text-sm);
}

.field input,
.field select,
.field textarea {
    font: inherit;
    font-size: var(--text-sm);
    padding: var(--space-2) var(--space-3);
    border: 1px solid var(--border-strong);
    border-radius: var(--radius-sm);
    background: var(--bg);
    color: var(--text);
    transition: border-color var(--dur-1) var(--ease-out);
}

.field input:hover,
.field select:hover,
.field textarea:hover {
    border-color: var(--border-field);
}

.field input:focus-visible,
.field select:focus-visible,
.field textarea:focus-visible {
    outline: 2px solid var(--focus);
    outline-offset: 1px;
}

.field input[type="checkbox"],
.field input[type="radio"] {
    accent-color: var(--brand);
}

.field__error {
    color: var(--status-danger-fg);
    font-size: var(--text-xs);
}

/* Filter row: selects flow inline and wrap on narrow viewports. */
.filters {
    display: flex;
    flex-wrap: wrap;
    gap: var(--space-4);
}

.filters .field {
    flex: 1 1 12rem;
    min-width: 12rem;
    margin: 0;
}

/* ----- Legacy tables (unmigrated pages get the RichTable visual treatment for free) ----- */
.table {
    width: 100%;
    border-collapse: collapse;
    font-size: var(--text-sm);
    font-variant-numeric: tabular-nums;
}

.table th,
.table td {
    text-align: left;
    padding: var(--space-2) var(--space-3);
    border-bottom: 1px solid var(--border);
    vertical-align: middle;
}

.table th {
    color: var(--text-muted);
    font-weight: 600;
    font-size: var(--text-xs);
    letter-spacing: 0.02em;
}

.table tbody tr {
    transition: background-color var(--dur-1) var(--ease-out);
}

.table tbody tr:hover {
    background: var(--surface-2);
}

.table-scroll {
    overflow-x: auto;
}

/* ----- RichTable ----- */
.rtable {
    background: var(--surface-1);
    border: 1px solid var(--border);
    border-radius: var(--radius-lg);
    box-shadow: var(--shadow-1);
    overflow: hidden;
}

.rtable__toolbar {
    display: flex;
    flex-wrap: wrap;
    align-items: center;
    gap: var(--space-3);
    padding: var(--space-3) var(--space-4);
    border-bottom: 1px solid var(--border);
}

.rtable__search {
    position: relative;
    flex: 0 1 20rem;
    min-width: 12rem;
}

.rtable__search-icon {
    position: absolute;
    left: 0.6rem;
    top: 50%;
    translate: 0 -50%;
    width: 1rem;
    height: 1rem;
    color: var(--text-muted);
    pointer-events: none;
}

.rtable__search-input {
    width: 100%;
    font: inherit;
    font-size: var(--text-sm);
    color: var(--text);
    background: var(--bg);
    border: 1px solid var(--border-strong);
    border-radius: var(--radius-sm);
    padding: 0.4rem 2rem 0.4rem 2rem;
    transition: border-color var(--dur-1) var(--ease-out), box-shadow var(--dur-1) var(--ease-out);
}

.rtable__search-input:hover {
    border-color: var(--border-field);
}

.rtable__search-input:focus-visible {
    outline: 2px solid var(--focus);
    outline-offset: 1px;
}

.rtable__search-clear {
    position: absolute;
    right: 0.3rem;
    top: 50%;
    translate: 0 -50%;
    display: inline-flex;
    padding: 0.25rem;
    border: 0;
    background: none;
    color: var(--text-muted);
    cursor: pointer;
    border-radius: var(--radius-sm);
}

.rtable__search-clear:hover {
    color: var(--text);
    background: var(--surface-2);
}

.rtable__filters {
    display: flex;
    flex-wrap: wrap;
    gap: var(--space-3);
    align-items: center;
}

.rtable__filters .field {
    margin: 0;
    flex-direction: row;
    align-items: center;
    gap: var(--space-2);
}

.rtable__filters .field > label {
    font-size: var(--text-sm);
    color: var(--text-muted);
    font-weight: 500;
}

.rtable__order {
    font-size: var(--text-xs);
}

.rtable__count {
    margin-inline-start: auto;
    font-size: var(--text-xs);
    color: var(--text-muted);
    font-variant-numeric: tabular-nums;
}

.rtable__scroll {
    overflow-x: auto;
    position: relative;
}

.rtable--sticky .rtable__scroll {
    max-height: var(--rtable-max-h, 34rem);
    overflow-y: auto;
}

.rtable.is-refreshing .rtable__scroll {
    opacity: 0.55;
    pointer-events: none;
    transition: opacity var(--dur-2) var(--ease-out);
}

.rtable__table {
    width: 100%;
    border-collapse: separate; /* REQUIRED: collapsed borders detach from position:sticky headers */
    border-spacing: 0;
    font-size: var(--text-sm);
    font-variant-numeric: tabular-nums;
}

.rtable__table th,
.rtable__table td {
    text-align: left;
    padding: 0 var(--space-3);
    height: var(--rtable-row-h, 2.75rem);
    border-bottom: 1px solid var(--border);
    vertical-align: middle;
    white-space: nowrap;
}

.rtable--dense {
    --rtable-row-h: 2rem;
}

.rtable__table tbody tr:last-child td {
    border-bottom: 0;
}

.rtable__table thead th {
    position: relative;
    background: var(--surface-1);
    color: var(--text-muted);
    font-size: var(--text-xs);
    font-weight: 600;
    letter-spacing: 0.02em;
    height: 2.25rem;
}

.rtable--sticky thead th {
    position: sticky;
    top: 0;
    z-index: var(--z-sticky);
    box-shadow: inset 0 -1px 0 var(--border);
    border-bottom: 0;
}

/* The non-sortable header label — the counterpart of `.rtable__sortbtn` below. Both branches of the same `<th>`
   must sit on the same baseline, and the button is an inline-flex box; a bare span is not, so a sortable and a
   non-sortable header in the same row drifted apart by the button's box metrics. Found by the sweep in
   SharedComponentStyleTests, which is also how it was noticed that the class had NO rule at all. */
.rtable__thlabel {
    display: inline-flex;
    align-items: center;
}

/* The load-failure block. The `.alert--danger` inside it is styled; this wrapper is what separates it from the
   toolbar above and the table below, and without a rule the error sat flush against both. */
.rtable__error {
    margin: var(--space-3) 0;
}

.rtable__sortbtn {
    display: inline-flex;
    align-items: center;
    gap: 0.3rem;
    font: inherit;
    font-weight: inherit;
    letter-spacing: inherit;
    color: inherit;
    background: none;
    border: 0;
    padding: 0.35rem 0.25rem;
    margin: -0.35rem -0.25rem;
    cursor: pointer;
    border-radius: var(--radius-sm);
}

.rtable__sortbtn:hover {
    color: var(--text);
}

.rtable__sorticon {
    width: 0.8rem;
    height: 0.8rem;
    opacity: 0;
    transition: opacity var(--dur-1), rotate var(--dur-2) var(--ease-out);
}

.rtable__sortbtn:hover .rtable__sorticon {
    opacity: 0.6;
}

th.is-sorted .rtable__sortbtn {
    color: var(--text);
}

th.is-sorted .rtable__sorticon {
    opacity: 1;
}

th[aria-sort="ascending"] .rtable__sorticon {
    rotate: 180deg;
}

.rtable__row {
    transition: background-color var(--dur-1) var(--ease-out);
}

.rtable__row:hover {
    background: var(--surface-2);
}

.rtable__row[data-clickable] {
    cursor: pointer;
}

.rtable__row:has(.rtable__rowlink:focus-visible) {
    background: var(--brand-tint);
}

.rtable__rowlink {
    color: var(--text);
    font-weight: 500;
    text-decoration: none;
}

.rtable__rowlink:hover {
    color: var(--link);
    text-decoration: underline;
    text-underline-offset: 2px;
}

.rtable__td--mono,
td.rtable__td--mono code {
    font-family: var(--font-mono);
    font-size: 0.9em;
}

.rtable__td--end,
.rtable__th--end {
    text-align: right;
}

.rtable__td--truncate {
    max-width: 24rem;
    overflow: hidden;
    text-overflow: ellipsis;
}

/* Right-edge scroll cue (postmqUi.observeOverflow stamps data-overflowing) */
.rtable__scroll[data-overflowing]::after {
    content: "";
    position: sticky;
    left: 100%;
    top: 0;
    display: block;
    width: 2rem;
    height: 100%;
    margin-left: -2rem;
    float: right;
    background: linear-gradient(90deg, transparent, var(--surface-1));
    pointer-events: none;
}

.rtable__footer {
    display: flex;
    flex-wrap: wrap;
    align-items: center;
    gap: var(--space-3);
    padding: var(--space-3) var(--space-4);
    border-top: 1px solid var(--border);
    font-size: var(--text-sm);
}

.rtable__summary {
    color: var(--text-muted);
    font-variant-numeric: tabular-nums;
}

.rtable__pager {
    display: flex;
    align-items: center;
    gap: var(--space-2);
    margin-inline-start: auto;
}

.rtable__page {
    color: var(--text-muted);
    padding-inline: var(--space-2);
    font-variant-numeric: tabular-nums;
}

.rtable__psize {
    display: inline-flex;
    align-items: center;
    gap: var(--space-2);
    color: var(--text-muted);
}

.rtable__psize select {
    font: inherit;
    font-size: var(--text-sm);
    color: var(--text);
    background: var(--surface-1);
    border: 1px solid var(--border-strong);
    border-radius: var(--radius-sm);
    padding: 0.2rem 0.4rem;
}


/* ----- Empty state ----- */
.empty {
    display: grid;
    justify-items: center;
    gap: var(--space-2);
    padding: var(--space-8) var(--space-4);
    text-align: center;
}

.empty__icon {
    display: grid;
    place-items: center;
    width: 2.75rem;
    height: 2.75rem;
    border-radius: var(--radius-full);
    background: var(--surface-2);
    color: var(--text-muted);
    border: 1px solid var(--border);
}

.empty__icon .icon {
    width: 1.25rem;
    height: 1.25rem;
}

.empty__title {
    margin: 0;
    font-size: var(--text-lg);
}

.empty__body {
    margin: 0 0 var(--space-2);
    color: var(--text-muted);
    max-width: 28rem;
    font-size: var(--text-sm);
}

/* An empty state inside a card (the overview's list cards): the same anatomy, less air. */
.empty--compact {
    padding: var(--space-4) var(--space-2);
}

/* ----- Skeleton loaders ----- */
.skeleton {
    display: inline-block;
    height: 0.9em;
    width: 100%;
    border-radius: var(--radius-sm);
    background: linear-gradient(90deg, var(--surface-2) 25%, var(--surface-3) 42%, var(--surface-2) 58%);
    background-size: 200% 100%;
    animation: skeleton-shimmer 1.4s linear infinite;
}

@keyframes skeleton-shimmer {
    from {
        background-position: 120% 0;
    }

    to {
        background-position: -80% 0;
    }
}


/* ----- Breadcrumb -----
   Written back alongside the markup that uses it. An earlier version of these rules shipped with no caller for
   months and was deleted for that reason (see AppCssOrphanClassTests); the rule here is that a component's CSS
   and the page applying it land in one commit. */
.breadcrumb ol {
    display: flex;
    flex-wrap: wrap;
    gap: var(--space-1);
    list-style: none;
    margin: 0;
    padding: 0;
    font-size: var(--text-sm);
}

.breadcrumb li {
    display: flex;
    align-items: center;
    gap: var(--space-1);
    color: var(--text-muted);
}

.breadcrumb li + li::before {
    content: "/";
    color: var(--text-faint);
    padding-inline-end: var(--space-1);
}

.breadcrumb a {
    color: var(--text-muted);
    text-decoration: none;
}

.breadcrumb a:hover {
    color: var(--link);
    text-decoration: underline;
}

.breadcrumb [aria-current="page"] {
    color: var(--text);
    font-weight: 500;
}

/* ----- Copyable ID chip ----- */
.idchip {
    display: inline-flex;
    align-items: center;
    gap: 0.25rem;
    padding: 0.1rem 0.2rem 0.1rem 0.45rem;
    background: var(--surface-2);
    border: 1px solid var(--border);
    border-radius: var(--radius-xs);
    max-width: 100%;
}

.idchip__text {
    font-family: var(--font-mono);
    font-size: var(--text-xs);
    color: var(--text-muted);
    background: none;
    border: 0;
    padding: 0;
}

.idchip__copy {
    display: inline-flex;
    padding: 0.2rem;
    border: 0;
    background: none;
    color: var(--text-muted);
    cursor: pointer;
    border-radius: var(--radius-sm);
}

.idchip__copy:hover {
    color: var(--text);
    background: var(--surface-3);
}

.idchip__copy .icon {
    width: 0.8rem;
    height: 0.8rem;
}

.idchip__ic-check {
    display: none;
    color: var(--icon-success);
}

.idchip.is-copied .idchip__ic-copy {
    display: none;
}

.idchip.is-copied .idchip__ic-check {
    display: inline;
}

/* ----- Timeline / activity list ----- */
.timeline {
    list-style: none;
    margin: 0;
    padding: 0;
}

.timeline__item {
    position: relative;
    display: flex;
    gap: var(--space-4);
    padding-bottom: var(--space-5);
}

.timeline__item::before {
    content: "";
    position: absolute;
    left: 0.4375rem;
    top: 1.1rem;
    bottom: -0.2rem;
    width: 2px;
    background: var(--border);
}

.timeline__item:last-child::before {
    display: none;
}

.timeline__node {
    position: relative;
    z-index: 1;
    flex: none;
    width: 1rem;
    height: 1rem;
    margin-top: 0.2rem;
    border-radius: 50%;
    background: var(--surface-1);
    border: 2px solid var(--border-strong);
}


.timeline__body {
    flex: 1;
    min-width: 0;
}

.timeline__head {
    display: flex;
    align-items: baseline;
    gap: var(--space-3);
    flex-wrap: wrap;
}

.timeline__title {
    font-weight: 600;
    font-size: var(--text-body);
}

.timeline__time {
    margin-inline-start: auto;
    color: var(--text-faint);
    font-size: var(--text-xs);
    white-space: nowrap;
    font-variant-numeric: tabular-nums;
}


/* ----- Project dashboards (#923 / #924) ----- */

/* A row's second line: the coverage clause that has to travel with every token figure. It is small and muted
   because it qualifies the number above it rather than competing with it — but it is NEVER optional, so it is
   styled as part of the cell instead of being left to a title attribute nobody opens. */
.rtable__subtext {
    display: block;
    margin-top: 0.15rem;
    font-size: var(--text-xs);
    color: var(--text-muted);
    font-variant-numeric: normal;
}

/* The names on a project row. Wraps rather than truncating: a project with four people on it is exactly the
   row a manager is looking for, and hiding the fourth name to keep the row one line tall would lose the answer
   to the question this screen exists to answer. */
.rollup-people {
    display: flex;
    flex-wrap: wrap;
    gap: var(--space-1);
    align-items: center;
    /* Claims enough width for two names side by side inside the table's own scroller. Without it the column
       collapses on a narrow viewport and every name stacks vertically, which triples the height of exactly the
       rows a manager is scanning for — the ones with the most people on them. The TABLE scrolls horizontally;
       the page never does. */
    min-width: 11rem;
}

/* ----- Overview dashboard (Home) ----- */
.page-grid--overview {
    display: grid;
    grid-template-columns: 2fr 1fr;
    gap: var(--space-4);
    align-items: start;
}

@media (max-width: 1024px) {
    .page-grid--overview {
        grid-template-columns: 1fr;
    }
}

.page-grid--overview > .page-grid__col {
    display: grid;
    gap: var(--space-4);
    min-width: 0;
}


.overview-list {
    list-style: none;
    margin: 0;
    padding: 0;
    display: grid;
}

.overview-list__item {
    border-bottom: 1px solid var(--border);
}

.overview-list__item:last-child {
    border-bottom: 0;
}

.overview-list__link {
    display: grid;
    gap: 0.15rem;
    padding: var(--space-2) var(--space-1);
    color: inherit;
    text-decoration: none;
    border-radius: var(--radius-sm);
}

.overview-list__link:hover {
    background: var(--surface-2);
    color: inherit;
    text-decoration: none;
}

.overview-list__title {
    font-weight: 500;
    font-size: var(--text-sm);
    overflow: hidden;
    text-overflow: ellipsis;
    white-space: nowrap;
}

.overview-list__meta {
    display: flex;
    align-items: center;
    gap: var(--space-2);
    font-size: var(--text-xs);
    color: var(--text-muted);
    flex-wrap: wrap;
}

.card__footer-link {
    display: inline-block;
    margin-top: var(--space-3);
    font-size: var(--text-sm);
    font-weight: 500;
}

/* ----- Blazor circuit reconnect modal (Doc 04 §4) ----- */
.reconnect-modal {
    position: fixed;
    inset: 0;
    z-index: 1000;
    display: none;
    align-items: center;
    justify-content: center;
    background: var(--overlay);
}

/* blazor.web.js toggles these classes on the modal element. */
.components-reconnect-show,
.components-reconnect-failed,
.components-reconnect-rejected {
    display: flex;
}

.components-reconnect-hide {
    display: none;
}

.reconnect-dialog {
    background: var(--surface-1);
    color: var(--text);
    border: 1px solid var(--border);
    border-radius: var(--radius-lg);
    padding: var(--space-5);
    box-shadow: var(--shadow-3);
    max-width: 24rem;
}

.reconnect-message {
    margin: 0;
}

.reconnect-message--failed {
    display: none;
}

.components-reconnect-failed .reconnect-message--active,
.components-reconnect-rejected .reconnect-message--active {
    display: none;
}

.components-reconnect-failed .reconnect-message--failed,
.components-reconnect-rejected .reconnect-message--failed {
    display: block;
}

/* ----- Blazor error UI (framework toggles display:block on #blazor-error-ui) ----- */
#blazor-error-ui {
    display: none;
    position: fixed;
    bottom: 0;
    left: 0;
    right: 0;
    z-index: 1000;
    background: var(--status-warn-bg);
    color: var(--text);
    border-top: 1px solid var(--status-warn-bd);
    padding: var(--space-3) var(--space-4);
}

#blazor-error-ui .reload {
    margin-inline-start: var(--space-3);
}

#blazor-error-ui .dismiss {
    cursor: pointer;
    float: right;
    display: inline-flex;
    align-items: center;
}

/* ----- Long-form prose (release notes; any future markdown-rendered page) ----- */
/* Ported from the brand kit's .pmq-prose (design/brand/components/components.css §23) rather than imported:
   App.razor loads only css/postmq-tokens.css + app.css, so the dashboard does not consume components.css at
   all and every kit pattern it needs is re-expressed here against the same aliased tokens. Sized to the app's
   --text-md (16px) rather than the kit's marketing 18px, so it reads as "more readable than a dense dashboard
   card" without fighting the shell's 14px ambient scale. `.prose a` already exists above (the shared link-
   underline rule) — this completes a class the stylesheet was already half-referencing. */
.prose {
    font-size: var(--text-md);
    line-height: 1.7;
    max-width: 66ch;
}

.prose > * + * { margin-top: 1.1em; }
.prose p { margin: 0; }

.prose h3 {
    margin: 1.6em 0 0.5em;
    font-size: 1.125rem;
    font-weight: 600;
    letter-spacing: -0.01em;
    color: var(--text);
}

.prose ul { margin: 0; padding-left: 1.3em; }
.prose li + li { margin-top: 0.4em; }

/* A code sample inside prose can overflow at 375px. It gets its own scroller AND tabindex="0" in markup —
   axe treats scrollable-region-focusable as serious and the suites run at 375 wide. */
.prose pre { margin: 0; overflow-x: auto; }

/* ----- Release notes: /release-notes (public) + /operator/release-notes (internal) ----- */
/* Both pages share every class here and differ only in copy, grouping (feature arc vs release unit) and the
   --dense modifier. No card, no border and no rule between entries on purpose: typography and whitespace
   carry the hierarchy, so nothing competes with the headline. */

.release-notes-head .page-head__title {
    /* The app's one other display-grade step, already established by Home.razor's signed-out .hero h1. Reused
       at the same size rather than inventing a larger one — the global h1 is 24px and used everywhere else. */
    font-size: var(--text-3xl);
    font-weight: 600;
    letter-spacing: -0.022em;
    line-height: 1.15;
}

.page-head__meta {
    margin: var(--space-2) 0 0;
    font-family: var(--font-mono);
    font-size: var(--text-sm);
    color: var(--text-muted);
}

.release-notes-contents { display: flex; flex-direction: column; gap: var(--space-4); }

.release-notes-contents__label {
    margin: 0 0 var(--space-2);
    font-size: var(--text-overline);
    font-weight: 600;
    text-transform: uppercase;
    letter-spacing: 0.08em;
    color: var(--text-muted);
}

.release-notes-contents__list {
    margin: 0;
    padding-left: 1.1em;
    display: grid;
    gap: var(--space-1);
    font-size: var(--text-sm);
}

/* Layout constant, not a brand token — same family as --sidebar-w in the Layout block above. */
:root { --toc-rail-w: 13.75rem; }

.release-notes-layout {
    display: grid;
    grid-template-columns: minmax(0, 1fr) var(--toc-rail-w);
    gap: var(--space-7);
    align-items: start;
}

.release-notes-layout > * { min-width: 0; }

@media (max-width: 1080px) {
    .release-notes-layout { grid-template-columns: minmax(0, 1fr); }
    /* Hidden rather than reflowed: the page already renders an inline Contents card, so reflowing the rail
       would duplicate it. */
    .release-notes-toc { display: none; }
}

/* position:sticky resolves against .app-content, which is the app's ONE scroll container (MainLayout.razor) —
   not the viewport. That is the single piece of this pattern that differs from the kit's page-scrolls-the-
   window context, and the reason it lives here rather than in components.css. */
.release-notes-toc {
    position: sticky;
    top: var(--space-5);
    align-self: start;
    font-size: var(--text-sm);
}

.release-notes-toc__title {
    margin: 0 0 var(--space-2);
    font-size: var(--text-overline);
    font-weight: 600;
    text-transform: uppercase;
    letter-spacing: 0.08em;
    color: var(--text-muted);
}

.release-notes-toc__list {
    list-style: none;
    margin: 0;
    padding: 0;
    border-left: 1px solid var(--border);
    display: grid;
}

.release-notes-toc__list a {
    display: block;
    padding: var(--space-1) 0 var(--space-1) var(--space-3);
    margin-left: -1px;
    border-left: 2px solid transparent;
    color: var(--text-muted);
    text-decoration: none;
    line-height: 1.4;
}

.release-notes-toc__list a:hover { color: var(--text); border-left-color: var(--border-strong); }

.release-notes-entries { margin-top: var(--space-8); }
.release-notes-entries > * + * { margin-top: 5rem; }
.release-notes-entries--dense > * + * { margin-top: 2.5rem; }

.release-notes-entry { scroll-margin-top: var(--space-4); }

.release-notes-entry__title {
    margin: 0;
    font-size: 1.5rem;
    font-weight: 600;
    letter-spacing: -0.015em;
    line-height: 1.25;
}

.release-notes-entry__meta {
    display: flex;
    align-items: baseline;
    gap: 0.4rem;
    margin: var(--space-2) 0 0;
    font-size: var(--text-sm);
    color: var(--text-muted);
}

.release-notes-entry__version { font-family: var(--font-mono); font-variant-numeric: tabular-nums; }

.release-notes-entry__body { margin-top: var(--space-4); }

/* The collapsed line for a release with no user-visible change. A quieter register, not a warning. */
.release-notes-entry__noop {
    margin: var(--space-3) 0 0;
    font-style: italic;
    color: var(--text-muted);
}

/* The footer's version number — the public notes' one entry point. */
.app-footer__version { font-family: var(--font-mono); font-variant-numeric: tabular-nums; }

/* ── OAuth consent + connected agents ─────────────────────────────────────────────────────────────────────────
   Both surfaces exist to help a person decide whether to trust something, so the styling has one job: keep the
   two facts that carry the decision — WHO is asking and WHERE the connection goes — legible together, and keep an
   unverified name from reading like a verified one. Everything else is ordinary list layout.                    */

.consent-identity { display: grid; grid-template-columns: auto 1fr; gap: var(--space-2) var(--space-4); margin: var(--space-4) 0; }
.consent-identity dt { color: var(--text-muted); font-size: var(--text-sm); }
.consent-identity dd { margin: 0; }

/* The redirect host is monospaced deliberately: it is the one value on the screen a person is being asked to
   READ CHARACTER BY CHARACTER, because a lookalike host is the whole attack it defends against. */
.consent-identity code { font-family: var(--font-mono); word-break: break-all; }

.consent-unverified,
.connection__unverified {
    display: inline-block;
    margin-left: var(--space-2);
    padding: 0 var(--space-2);
    border-radius: var(--radius-xs);
    background: var(--warn-surface);
    color: var(--warn);
    font-size: var(--text-sm);
    font-weight: 600;
    white-space: nowrap;
}

.consent-warning {
    margin: var(--space-4) 0;
    padding: var(--space-3);
    border-radius: var(--radius-sm);
    background: var(--warn-surface);
    color: var(--warn);
}

.consent-scopes { margin: 0 0 var(--space-4); padding-left: var(--space-5); }
.consent-scopes li { margin-bottom: var(--space-1); }
.consent-actions { display: flex; gap: var(--space-3); margin-top: var(--space-5); flex-wrap: wrap; }

.connection-list { list-style: none; margin: 0; padding: 0; display: grid; gap: var(--space-4); }

.connection {
    padding: var(--space-4);
    border: 1px solid var(--border);
    border-radius: var(--radius);
    background: var(--surface-1);
}

/* A withdrawn connection stays in the list — a person asking "did I disconnect that?" needs the answer — but must
   not read as live at a glance. Muted rather than hidden. */
.connection--revoked { opacity: 0.65; }

.connection__head { margin-bottom: var(--space-3); }
.connection__name { margin: 0; font-size: var(--text-lg); }
.connection__meta { margin: var(--space-1) 0 0; color: var(--text-muted); }

.connection__facts { display: grid; grid-template-columns: auto 1fr; gap: var(--space-1) var(--space-4); margin: 0 0 var(--space-3); }
.connection__facts dt { color: var(--text-muted); font-size: var(--text-sm); }
.connection__facts dd { margin: 0; font-variant-numeric: tabular-nums; }

.connection__can { margin: 0 0 var(--space-2); font-size: var(--text-sm); color: var(--text-muted); font-weight: 600; }
.connection__scopes { margin: 0 0 var(--space-4); padding-left: var(--space-5); }
.connection__revoked-note { margin: 0; color: var(--text-muted); font-size: var(--text-sm); }

/* The outbound cue on an ExternalLink — the same arrow-up-right the sidebar's one outbound link uses, so an
   agent-supplied PR link reads as leaving the dashboard. Inline-block keeps it on the text baseline inside a
   <dd> or a list item without changing the line box. */
.externallink__icon {
    display: inline-block;
    vertical-align: -0.1em;
    margin-inline-start: 0.25em;
    opacity: 0.75;
}

/* A create form that collapses once the list has something in it (backlog 019fcf35-12ee item 4). Native
   <details>/<summary>, so the keyboard and screen-reader behaviour is the platform's rather than ours: the
   summary is focusable, Enter/Space toggles it, and the expanded state is announced. The marker is restyled
   rather than removed — `list-style: none` alone strips the affordance in Safari and leaves no cue at all. */
.card--disclosure > summary {
    cursor: pointer;
    list-style: none;
    display: flex;
    align-items: center;
    gap: 0.5rem;
}

.card--disclosure > summary::-webkit-details-marker {
    display: none;
}

.card--disclosure > summary::before {
    content: "";
    width: 0.5rem;
    height: 0.5rem;
    border-right: 2px solid currentColor;
    border-bottom: 2px solid currentColor;
    transform: rotate(-45deg);
    transition: transform 120ms ease;
    opacity: 0.7;
    flex: none;
}

.card--disclosure[open] > summary::before {
    transform: rotate(45deg);
}

@media (prefers-reduced-motion: reduce) {
    .card--disclosure > summary::before {
        transition: none;
    }
}

/* The pending-candidate count beside the Candidates link (backlog 019fcf35-12ee item 7). Decorative: the count
   is also written into an sr-only span on the link, so the badge conveys nothing a screen reader loses. Sized in
   ch so a three-digit queue widens it rather than clipping. */
.side-link__badge {
    margin-inline-start: auto;
    min-width: 2ch;
    padding: 0.05rem 0.35rem;
    border-radius: var(--radius-full);
    font-size: 0.75rem;
    font-variant-numeric: tabular-nums;
    text-align: center;
    background: var(--pmq-accent-soft, var(--pmq-surface-2));
    color: var(--pmq-text);
    flex: none;
}

/* Collapsed rail: the label is hidden, so the count would float with nothing to anchor it. */
