/*
 * App-wide CSS — the single hand-written global stylesheet beyond the
 * design-token sheet (sibling `tokens.css`). Keep this file minimal:
 * page- and component-specific rules belong in scoped `*.razor.css`
 * files, which the framework bundles into
 * `Companion.Presentation.Web.styles.css` (emitted at the wwwroot root by
 * Blazor — that single file is unavoidably outside this folder).
 *
 * Every rule references the design tokens; new rules should too.
 */

/* Brand text-selection highlight, app-wide: honeycomb amber behind
   brand-blue text (the lockup inverted). ::selection accepts no
   inheritance, so the one global rule covers every page and component —
   scoped styles never need their own. */
::selection {
    color: var(--color-selection-text);
    background: var(--color-selection-surface);
}

/* Inter Variable — self-hosted body face. The single file carries the full
   100–900 weight axis so we never load multiple weights; consumers vary
   weight via `font-weight: var(--font-weight-*)`. `font-display: swap` lets
   the system fallback render immediately and swaps to Inter the moment it
   lands — small metric shift, no FOUT blank period. `unicode-range` keeps
   the file Latin-only (the bulk of what the UI ships in); other ranges fall
   through to the OS UI font. When the woff2 is missing the declaration
   silently fails and consumers see the fallback. */
@font-face {
    font-family: "Inter Variable";
    src: url("/fonts/Inter-Variable.woff2") format("woff2-variations"),
         url("/fonts/Inter-Variable.woff2") format("woff2");
    font-weight: 100 900;
    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+2000-206F, U+2074, U+20AC, U+2122, U+2191, U+2193, U+2212, U+2215, U+FEFF, U+FFFD;
}

/* Space Grotesk — the brand DISPLAY face (headings + the wordmark). Body
   text stays Inter (see `--font-sans`); Space Grotesk is scoped to display
   contexts via `--font-display` per the brand system. Variable TTF carries
   the 300–700 weight axis, so we vary weight through `font-weight`. Same
   `swap` behaviour as Inter: the Inter/system fallback paints first and
   Space Grotesk swaps in when it lands. Ships as a TTF (no woff2 tooling on
   the build host); a future woff2 build would shrink the download. */
@font-face {
    font-family: "Space Grotesk";
    src: url("/fonts/SpaceGrotesk-Variable.ttf") format("truetype-variations"),
         url("/fonts/SpaceGrotesk-Variable.ttf") format("truetype");
    font-weight: 300 700;
    font-style: normal;
    font-display: swap;
}

/* Apply Inter sitewide. Components inherit through the cascade; mono
   stays system. Tabular-nums + ligatures give the crisp-tech feel
   (Linear / Vercel both run with these on). */
html {
    font-family: var(--font-sans);
    font-feature-settings: "cv02", "cv03", "cv04", "cv11";
    -webkit-font-smoothing: antialiased;
    -moz-osx-font-smoothing: grayscale;
    text-rendering: optimizeLegibility;
}

/* Headings use the brand display face (Space Grotesk); body stays Inter.
   Scoped to semantic headings so long-form body copy keeps Inter's higher
   reading comfort. The slight negative tracking matches the brand wordmark. */
h1, h2, h3 {
    font-family: var(--font-display);
    letter-spacing: -0.01em;
}

/* Buttons do NOT inherit the page font by default — the UA renders them
   in its own small UI stack, so any button whose rule forgot `font`
   dropped to ~13px system type beside 16px Inter (the bulk-action bar
   was the visible victim). Bare element selector on purpose: as a
   (0,0,1) reset it loses to every class-level rule, so controls that
   deliberately set their own size keep it. */
button {
    font: inherit;
}

/* Visible list markers carry the brand color product-wide — the same
   treatment .benchmark-content already applies, so every surface's
   bullets and list numbers agree. Lists that suppress markers
   (list-style: none) are unaffected; benchmark's nested-list muting
   comes later in this file and still wins. */
:is(.public-shell__main, .app-shell__main) :is(ul, ol) > li::marker {
    color: var(--color-brand);
}

/* Minimal reset.
   - `box-sizing: border-box` on every element so padding + borders are
     included in the declared width/height (modern norm; the alternative
     content-box model breaks the layout maths in scoped CSS files).
   - `margin: 0` on body to kill the user-agent default 8px margin —
     without this, `min-height: 100vh` on a top-level layout element
     (e.g. .app-shell, .public-shell) produces a `100vh + 16px` document
     and the viewport gains a vertical scrollbar even when there is no
     content to scroll.
   - `min-height: 100%` on body so a `height: 100%` child computes
     against the right context if any future page wants to use it. */
*,
*::before,
*::after {
    box-sizing: border-box;
}

html,
body {
    margin: 0;
}

body {
    min-height: 100%;
    /* Belt-and-braces against horizontal overflow at the page level.
       `.public-shell` and `.app-shell` both carry `overflow-x: clip`
       internally, but that only catches overflow INSIDE the shell —
       if a shell itself ends up wider than the viewport (a child
       with an intrinsic width that escapes a flex-shrink edge case,
       a stray inline-block, etc.) the document scrolls horizontally.
       Clipping at <body> short-circuits that whole class of bug
       without producing a horizontal scrollbar; vertical scrolling
       works as normal because we only clip on the x axis. */
    overflow-x: clip;
}

/* Blazor's FocusOnNavigate sets tabindex="-1" on every page's <h1>
   and shifts focus there on each navigation. Component-scoped rules
   on .page-header__heading / .list-page-hero__heading cover the
   heroes, but raw <h1> in pages like TenantLanding still showed the
   browser's default focus ring. A global rule keeps the ring out of
   the visual layer; AT users still hear the focus move. */
h1:focus,
h1:focus-visible {
    outline: none;
}

/* ----- Auth-page card layout ----------------------------------------- */
/* All Identity-ceremony pages (login, register, forgot-password,
   confirm-email, MFA, recovery-code, …) render inside PublicShell.
   The shell now leaves padding to its sections, so without a wrapper
   each auth form would float against the top of the viewport.
   `.public-shell__main > h1:first-of-type + *` and similar global
   selectors are fragile; instead we centre and card-wrap any auth
   form via a generic rule: any descendant of `.public-shell__main`
   whose root is an `<h1>` followed by an `<EditForm>` lands inside
   the implicit "auth-card" layout. The pages render no wrapper of
   their own, so the rules below target the actual elements. */
.public-shell__main:has(> h1):not(:has(> .home-hero)) {
    display: flex;
    flex-direction: column;
    align-items: center;
    /* `inline-size: 100%` (vs the default `auto`) anchors the flex
       column to the available width and stops a child's intrinsic
       content from stretching the parent past the viewport. Combined
       with `min-inline-size: 0` so flex children can shrink past
       their natural content width when they need to wrap. */
    inline-size: 100%;
    min-inline-size: 0;
    padding: clamp(var(--space-8), 8vw, var(--space-16)) var(--space-4);
}

    /* The auth ceremonies use plain h1 + EditForm + supporting p
       tags. Centre them at a readable measure inside an implicit
       card-styled container. */
    .public-shell__main:has(> h1):not(:has(> .home-hero)) > * {
        max-width: 32rem;
        width: 100%;
        /* Each direct child becomes a column-flex item with min-width:
           0 so it can shrink below its intrinsic content size and let
           descendants wrap. Without this a long unbroken token (a long
           email, a long error from ValidationSummary) would refuse to
           shrink and push the card past 32rem on the cross axis. */
        min-inline-size: 0;
    }

    .public-shell__main:has(> h1):not(:has(> .home-hero)) > h1 {
        font-size: var(--font-size-3xl);
        font-weight: var(--font-weight-bold);
        line-height: var(--line-height-tight);
        letter-spacing: var(--letter-spacing-tight);
        margin: 0 0 var(--space-6);
        /* Left-aligned to match the body copy + form labels below it
           (everything inside the card is left-aligned). A centered
           heading over a left-aligned form reads as a layout mistake. */
        text-align: left;
    }

    /* `.auth-card` joins `form` in the card selector so pages whose
       primary content is not a form (the second-factor chooser, the
       tenant picker) can opt into the identical white box by wrapping
       their intro + actions in <div class="auth-card">.
       `.auth-secondary-action` opts a form OUT — it's the wrapper for
       link-styled POST affordances that render BELOW the card (e.g. the
       OTP page's "Send a new code"), which must not grow a second box. */
    .public-shell__main:has(> h1):not(:has(> .home-hero)) > :is(form, .auth-card):not(.auth-secondary-action) {
        background: var(--color-surface);
        border: 1px solid var(--color-border-subtle);
        border-radius: var(--radius-xl);
        padding: var(--space-8);
        box-shadow: var(--shadow-lg);
    }

    /* Intro copy inside a card hugs the top of the box; the trailing
       margin keeps rhythm with the form-control spacing below it.
       first-of-type (not first-child): SSR EditForms emit hidden
       antiforgery / handler inputs before the visible content, so an
       intro <p> is never literally a form's first child. */
    .public-shell__main:has(> h1):not(:has(> .home-hero)) > :is(form, .auth-card) > p:first-of-type {
        margin-block-start: 0;
    }

    /* Below-card secondary POST action — visually identical to the plain
       <p><a> links the other auth pages render under their card. */
    .public-shell__main:has(> h1):not(:has(> .home-hero)) > form.auth-secondary-action {
        margin-block-start: var(--space-4);
    }

@media (forced-colors: active) {
    .public-shell__main:has(> h1):not(:has(> .home-hero)) > :is(form, .auth-card):not(.auth-secondary-action) {
        border: 1px solid CanvasText;
        box-shadow: none;
    }
}

@media (max-width: 480px) {
    .public-shell__main:has(> h1):not(:has(> .home-hero)) > :is(form, .auth-card):not(.auth-secondary-action) {
        padding: var(--space-5);
        border-radius: var(--radius-lg);
    }
}

/* Modernise the bare-bones input + button defaults shown across
   auth pages AND signed-in settings pages. Each `<InputText>` renders
   a plain `<input>`; the global rule below skins them without per-page
   editing.

   Blazor's <InputText> renders no explicit `type` attribute when one
   isn't supplied (it inherits HTML's default of "text" only at the DOM
   level, not as an attribute), so `input[type="text"]` alone misses
   bare InputText instances such as the email field on /account/login.
   The `:not([type])` selector picks those up. <InputSelect> /
   <InputTextArea> render as `<select>` / `<textarea>` so they're added
   to the same rule to keep their look in lockstep with the inputs.

   Both shells (`.public-shell__main` for sign-in / register / public
   surfaces, `.app-shell__main` for signed-in settings and tenant-admin
   surfaces) participate so a form looks identical whether it lives in
   the auth chain or under /account/manage and /tenant/{slug}/settings/*. */
:is(.public-shell__main, .app-shell__main) input:not([type]),
:is(.public-shell__main, .app-shell__main) input[type="text"],
:is(.public-shell__main, .app-shell__main) input[type="email"],
:is(.public-shell__main, .app-shell__main) input[type="password"],
:is(.public-shell__main, .app-shell__main) input[type="search"],
:is(.public-shell__main, .app-shell__main) input[type="tel"],
:is(.public-shell__main, .app-shell__main) input[type="number"],
:is(.public-shell__main, .app-shell__main) input[type="url"],
:is(.public-shell__main, .app-shell__main) input[type="time"],
:is(.public-shell__main, .app-shell__main) input[type="date"],
:is(.public-shell__main, .app-shell__main) input[type="datetime-local"],
:is(.public-shell__main, .app-shell__main) select,
:is(.public-shell__main, .app-shell__main) textarea {
    width: 100%;
    min-height: 2.75rem;
    padding: var(--space-2) var(--space-3);
    background: var(--color-surface);
    color: var(--color-text);
    border: 1px solid var(--color-border);
    border-radius: var(--radius-md);
    font: inherit;
    transition: border-color var(--motion-duration-fast) var(--motion-easing-standard),
                box-shadow var(--motion-duration-fast) var(--motion-easing-standard);
    box-sizing: border-box;
}

    :is(.public-shell__main, .app-shell__main) input:not([type]):focus,
    :is(.public-shell__main, .app-shell__main) input[type="text"]:focus,
    :is(.public-shell__main, .app-shell__main) input[type="email"]:focus,
    :is(.public-shell__main, .app-shell__main) input[type="password"]:focus,
    :is(.public-shell__main, .app-shell__main) input[type="search"]:focus,
    :is(.public-shell__main, .app-shell__main) input[type="tel"]:focus,
    :is(.public-shell__main, .app-shell__main) input[type="number"]:focus,
    :is(.public-shell__main, .app-shell__main) input[type="url"]:focus,
    :is(.public-shell__main, .app-shell__main) select:focus,
    :is(.public-shell__main, .app-shell__main) textarea:focus {
        outline: 2px solid var(--color-focus-ring);
        outline-offset: 2px;
        border-color: var(--color-brand);
    }

    :is(.public-shell__main, .app-shell__main) textarea {
        min-height: 6rem;
        line-height: var(--line-height-normal);
        /* Cap user-resize so dragging the native handle can't push the
           form's submit row below the mobile fold. */
        resize: vertical;
        max-block-size: 20rem;
    }

    /* Native <select> chrome opt-out. Safari (macOS/iOS) ignores the
       shared padding/min-height on a native menulist, so a select next
       to a text input rendered visibly shorter (e.g. the members
       invite form's role picker). appearance:none makes every browser
       honour the shared box metrics; the inline-SVG chevron replaces
       the suppressed native arrow (slate stroke reads on both themes),
       with end-padding reserving its lane so options never underlap.
       The ListView pagination page-size select opts out: it already
       ships its own compact appearance:none design whose wrapper draws
       a ::after caret (lists.css) — the global chevron would paint a
       second, offset arrow over it. */
    :is(.public-shell__main, .app-shell__main) select:not(.pagination__page-size-select) {
        appearance: none;
        -webkit-appearance: none;
        background-image: url("data:image/svg+xml;charset=utf-8,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 16 16' fill='none'%3E%3Cpath d='m4 6 4 4 4-4' stroke='%2364748b' stroke-width='1.8' stroke-linecap='round' stroke-linejoin='round'/%3E%3C/svg%3E");
        background-repeat: no-repeat;
        background-position: right var(--space-3) center;
        background-size: 1rem;
        padding-inline-end: calc(var(--space-3) * 2 + 1rem);
    }

/* Native date/time pickers: the browser paints the calendar dropdown's
   selection, "today" ring, and internal controls with the element's
   accent-color — without this they render in the UA's default blue,
   off-brand next to every other control. The popup's own chrome (its
   Clear/Today buttons, month grid) is UA shadow DOM and follows
   accent-color + the page's color-scheme; it cannot be styled further. */
:is(.public-shell__main, .app-shell__main) input[type="date"],
:is(.public-shell__main, .app-shell__main) input[type="time"],
:is(.public-shell__main, .app-shell__main) input[type="datetime-local"] {
    accent-color: var(--color-brand);
}

:is(.public-shell__main, .app-shell__main) label {
    display: block;
    margin-bottom: var(--space-2);
    font-size: var(--font-size-sm);
    font-weight: var(--font-weight-medium);
    color: var(--color-text);
}

:is(.public-shell__main, .app-shell__main) form > div,
:is(.public-shell__main, .app-shell__main) form > fieldset {
    margin-bottom: var(--space-4);
}

/* Tighten form-group spacing on small screens so multi-field forms
   (login, settings) keep the submit button closer to the fold. */
@media (max-width: 480px) {
    :is(.public-shell__main, .app-shell__main) form > div,
    :is(.public-shell__main, .app-shell__main) form > fieldset {
        margin-bottom: var(--space-2);
    }
}

/* Native `<legend>` sits on the fieldset's top border, which makes
   the border line visually cut through the section heading. Floating
   the legend turns it into a normal block child so the fieldset's
   top border stays continuous above the heading. Scoped to the
   shells so we don't disturb other fieldset consumers (e.g. the
   list-filter-panel fieldset that resets its own border). The
   sibling ::after clearfix keeps the floated legend from collapsing
   the fieldset's height. */
:is(.public-shell__main, .app-shell__main) fieldset > legend {
    float: left;
    width: 100%;
}

    :is(.public-shell__main, .app-shell__main) fieldset::after {
        content: "";
        display: block;
        clear: both;
    }

/* Every auth-form CTA — primary submit, SSO continue, button-styled
   anchors (e.g. the password branch of the tenant picker) — shares
   one rule so the public shell looks visually consistent across the
   email-OTP gate, the credential page, SSO selection, password reset,
   and the manage-account confirm page. New auth CTAs only need to be
   a <button type="submit"> inside the public shell, or carry the
   .auth-cta-link class for the rare anchor-as-button case. */
/* Small icon-shaped submit buttons inside the public shell opt out
   via :not(...) so they can render as subtle borderless affordances
   instead of full-width navy CTAs. Add new opt-outs here when an
   icon-button surfaces inside the public shell. */
.public-shell__main button[type="submit"]:not(.login-credential__back):not(.link-button),
.public-shell__main .auth-cta-link {
    display: inline-flex;
    align-items: center;
    justify-content: center;
    width: 100%;
    min-height: 2.75rem;
    margin-top: var(--space-2);
    padding: 0 var(--space-5);
    /* Unified with the AppShell chrome surface so the brand identity
       reads as one brand blue across chrome + CTAs. White text holds
       WCAG 2.2 AA (~5.8:1) on the constant brand blue in both themes;
       the hover lighten is capped so it stays above AA. */
    background: var(--color-chrome-surface);
    color: var(--color-chrome-on-surface);
    font: inherit;
    font-weight: var(--font-weight-semibold);
    text-align: center;
    text-decoration: none;
    border: 1px solid transparent;
    border-radius: var(--radius-md);
    box-sizing: border-box;
    cursor: pointer;
    box-shadow: var(--shadow-sm);
    transition: filter var(--motion-duration-fast) var(--motion-easing-standard);
}

    .public-shell__main button[type="submit"]:not(.login-credential__back):not(.link-button):hover,
    .public-shell__main .auth-cta-link:hover {
        filter: brightness(1.08);
        color: var(--color-chrome-on-surface);
        text-decoration: none;
    }

    .public-shell__main button[type="submit"]:not(.login-credential__back):not(.link-button):active,
    .public-shell__main .auth-cta-link:active {
        filter: brightness(0.92);
    }

    .public-shell__main button[type="submit"]:not(.login-credential__back):not(.link-button):focus-visible,
    .public-shell__main .auth-cta-link:focus-visible {
        outline: 2px solid var(--color-focus-ring);
        outline-offset: 3px;
    }

    /* Secondary public-shell CTA — transparent fill with a navy outline
       that fills on hover. Mirrors the AppShell .secondary treatment so
       a page can give one action primary visual weight and another a
       supporting / "alternative" weight. Use sparingly: most public-
       shell pages have one primary action. */
    .public-shell__main button[type="submit"]:not(.login-credential__back).secondary {
        background: transparent;
        color: var(--color-chrome-surface);
        border-color: var(--color-chrome-surface);
        box-shadow: none;
        filter: none;
    }

        .public-shell__main button[type="submit"]:not(.login-credential__back).secondary:hover {
            background: var(--color-chrome-surface);
            color: var(--color-chrome-on-surface);
            filter: none;
        }

        .public-shell__main button[type="submit"]:not(.login-credential__back).secondary:active {
            filter: brightness(0.92);
        }

/* Submit button rendered as an inline text link — used for secondary
   in-form affordances that need form submission (CSRF / antiforgery)
   but should READ as a plain hyperlink (e.g. "Send a new code" under
   the Continue button on the OTP entry form). The opt-out hooks at
   lines 253/279/286/291 keep the global navy CTA chrome off this
   variant; this block restores anchor-default visuals. A small
   margin-block-start lifts the link off the primary submit button
   that typically sits above it. */
.public-shell__main .link-button {
    background: none;
    border: 0;
    padding: 0;
    margin-block: var(--space-2) 0;
    margin-inline: 0;
    width: auto;
    min-height: 0;
    box-shadow: none;
    color: var(--color-brand);
    font: inherit;
    font-weight: inherit;
    text-align: inherit;
    text-decoration: none;
    cursor: pointer;
    display: inline-block;
}

    .public-shell__main .link-button:hover {
        text-decoration: none;
    }

    .public-shell__main .link-button:focus-visible {
        outline: 2px solid var(--color-focus-ring);
        outline-offset: 2px;
        border-radius: var(--radius-sm);
    }

/* Plain in-flow text links across the public / auth shell — "Register",
   "Use a different email address", "Back to sign in", "Forgot your password?".
   The public shell defines no anchor styling, so these otherwise fall back to
   the UA blue + underline + purple :visited. Paint them brand-coloured with no
   underline and no visited shift. Author origin beats the UA a:visited rule, so
   visited links stay brand without an explicit :visited selector. The full-width
   CTA anchors and the muted step-up Cancel link own their look and opt out. */
.public-shell__main a:not(.auth-cta-link):not(.account-confirm-card__cancel-link) {
    color: var(--color-brand);
    text-decoration: none;
}

/* Tenant picker list — defaults (bullet + left padding) work against
   the full-width auth CTAs; reset so each row renders as a clean
   vertical stack of CTA-shaped controls. */
.public-shell__main .auth-cta-list {
    list-style: none;
    padding: 0;
    margin: 0;
    display: flex;
    flex-direction: column;
    gap: var(--space-3);
}

    .public-shell__main .auth-cta-list > li {
        margin: 0;
    }

    .public-shell__main .auth-cta-list form {
        margin: 0;
    }

/* Signed-in settings submit buttons (account-manage, tenant settings,
   tenant SSO config). Mirrors the public-shell brand-styled CTA but
   stays at content width so multiple buttons on the same row (e.g.
   SsoConfig's Validate + Save pair) sit side-by-side instead of
   stacking full-width. A wrapping <div> with multiple buttons gets a
   flex layout for clean horizontal spacing. The .danger modifier
   colours destructive actions (Delete, Revoke) without changing
   shape so the visual rhythm stays consistent. */
/* Primary CTA chrome — uses --color-chrome-surface (the brand blue
   that sits behind the header / sidebar) so submit buttons and the
   AppShell chrome read as one brand family. The token is constant
   across light + dark themes by design (see tokens.css), so white
   text stays at WCAG 2.2 AA (~5.8:1) contrast on every theme; the
   hover lighten is capped at 1.08 so it stays above AA.
   `:not(.theme-toggle__button)` opts the inline footer theme-toggle
   form out — that button reads as inline text alongside the ·
   separators, not as a primary action. Any future inline-form
   button that wants the same opt-out can add the same class hook. */
.app-shell__main button[type="submit"]:not(.theme-toggle__button) {
    display: inline-flex;
    align-items: center;
    justify-content: center;
    min-height: 2.75rem;
    margin-top: var(--space-2);
    padding: 0 var(--space-5);
    background: var(--color-chrome-surface);
    color: var(--color-chrome-on-surface);
    font: inherit;
    font-weight: var(--font-weight-semibold);
    text-align: center;
    text-decoration: none;
    border: 1px solid transparent;
    border-radius: var(--radius-md);
    box-sizing: border-box;
    cursor: pointer;
    box-shadow: var(--shadow-sm);
    transition: filter var(--motion-duration-fast) var(--motion-easing-standard);
}

    .app-shell__main button[type="submit"]:not(.theme-toggle__button):hover {
        /* Lighten the navy on hover so the affordance is visible in
           both themes without needing per-theme tokens. */
        filter: brightness(1.08);
        color: var(--color-chrome-on-surface);
    }

    .app-shell__main button[type="submit"]:not(.theme-toggle__button):active {
        filter: brightness(0.92);
    }

    .app-shell__main button[type="submit"]:not(.theme-toggle__button):focus-visible {
        outline: 2px solid var(--color-focus-ring);
        outline-offset: 3px;
    }

    .app-shell__main button[type="submit"].secondary {
        background: transparent;
        color: var(--color-chrome-surface);
        border-color: var(--color-chrome-surface);
        box-shadow: none;
        filter: none;
    }

        .app-shell__main button[type="submit"].secondary:hover {
            background: var(--color-chrome-surface);
            color: var(--color-chrome-on-surface);
            filter: none;
        }

        .app-shell__main button[type="submit"].secondary:active {
            filter: brightness(0.92);
        }

    .app-shell__main button[type="submit"].danger {
        background: var(--color-danger);
        color: var(--color-text-on-accent);
    }

        .app-shell__main button[type="submit"].danger:hover {
            /* --color-danger-hover was never defined; the brightness filter
               is what actually darkens the hover, so reference the base
               token directly rather than an always-falling-back var. */
            background: var(--color-danger);
            filter: brightness(0.92);
        }

        .app-shell__main button[type="submit"].danger:active {
            filter: brightness(0.85);
        }

    /* Wrapping element that holds two or more action buttons (e.g.
       SsoConfig's Validate + Save, Passkeys row's Rename + Delete).
       Without this they stack vertically at full width — apply flex
       with a gap so they render as a horizontal pair, wrapping
       naturally on very narrow viewports.

       Two routes in:
         1. The legacy :has() rule recognises a <div> directly under
            <form> that holds adjacent submit buttons (SsoConfig pattern).
         2. The positive opt-in class `.button-row` works regardless of
            container element / button type / sibling structure — use
            this for new code and any case the :has() heuristic misses
            (e.g. a <form> with only one wrapping element + two non-
            adjacent buttons separated by hidden inputs). */
    .app-shell__main form > div:has(> button[type="submit"] + button[type="submit"]),
    .app-shell__main .button-row,
    .public-shell__main .button-row {
        display: flex;
        flex-wrap: wrap;
        gap: var(--space-3);
        margin-bottom: var(--space-4);
    }

/* Form-validation primitives surfaced by Blazor's `EditForm`. The classes
   are added by the framework (`valid.modified`, `invalid`,
   `validation-message`) so we have to style them globally. Token-driven
   so light / dark / High-Contrast all stay readable.

   Outline is 2px so non-text-contrast SC 1.4.11 is met; offset 1px
   keeps it from clipping the input border. The
   `.field-with-icon::after` glyph makes the state non-colour-only;
   the validation message text below the field already names the
   problem, but the inline glyph carries the same signal at a
   glance. */

.valid.modified:not([type=checkbox]) {
    outline: 2px solid var(--color-success);
    outline-offset: 1px;
}

.invalid {
    outline: 2px solid var(--color-danger);
    outline-offset: 1px;
}

.validation-message {
    color: var(--color-danger);
}

/* Inline icon adjacent to invalid inputs — the parent wrapper carries
   `data-invalid="true"` (set by the page when the field's
   ValidationMessage has content). Pages that haven't adopted the
   wrapper still see the outline + message text. */
.field-with-icon {
    position: relative;
}

.field-with-icon[data-invalid="true"]::after {
    content: "\26A0"; /* warning triangle */
    position: absolute;
    right: var(--space-2);
    top: 50%;
    transform: translateY(-50%);
    color: var(--color-danger);
    pointer-events: none;
    font-size: var(--font-size-md);
}

/* Callout: warning / danger surfaces for static (page-load-time)
   advisory text where `role="alert"` would be wrong. The role-less
   styled container is enough — readers read the text in document
   order and the leading glyph makes the message kind visible at a
   glance. Forced-colors mode swaps to a heavier border so the
   surface stays distinguishable without the warning colour token. */
.callout {
    margin: var(--space-3) 0;
    padding: var(--space-3) var(--space-4);
    border: 1px solid transparent;
    border-radius: var(--radius-md);
    display: flex;
    gap: var(--space-2);
    align-items: flex-start;
}

.callout--warning {
    background: var(--color-warning-surface);
    color: var(--color-warning-on-surface);
    border-color: var(--color-warning);
}

.callout--danger {
    background: var(--color-danger-surface);
    color: var(--color-danger-on-surface);
    border-color: var(--color-danger);
}

.callout::before {
    content: "\26A0"; /* warning triangle */
    font-size: var(--font-size-lg);
    line-height: 1;
    flex: none;
}

.callout--danger::before {
    content: "\2716"; /* heavy multiplication x */
}

@media (forced-colors: active) {
    .callout {
        border: 2px solid CanvasText;
        background: Canvas;
        color: CanvasText;
    }
}

/* ----- Shared primitives --------------------------------------------- */
/* Card surface — the recipe shared by StatCard, ActionCard,
   BenchmarkCard, the dashboard-section--card, list-page-hero-tile, and
   tenants-row. Opt in by composing `class="surface-card"` (or
   `.surface-card .surface-card--interactive` for hover lift) on a
   block-level container; the consumer adds its own grid/flex layout.
   New surfaces should reach for this before re-declaring the recipe;
   existing scoped duplicates can migrate when touched. */
.surface-card {
    padding: var(--space-4) var(--space-5);
    background: var(--color-surface);
    border: 1px solid var(--color-border-subtle);
    border-radius: var(--radius-lg);
    box-shadow: var(--shadow-sm);
}

.surface-card--interactive {
    transition:
        transform var(--motion-duration-fast) var(--motion-easing-standard),
        box-shadow var(--motion-duration-fast) var(--motion-easing-standard),
        border-color var(--motion-duration-fast) var(--motion-easing-standard);
}

    .surface-card--interactive:hover {
        transform: translateY(-1px);
        box-shadow: var(--shadow-md);
        border-color: var(--color-border);
    }

@media (prefers-reduced-motion: reduce) {
    .surface-card--interactive,
    .surface-card--interactive:hover {
        transform: none;
        transition: none;
    }
}

@media (forced-colors: active) {
    .surface-card {
        border: 1px solid CanvasText;
        box-shadow: none;
    }
}

/* All-caps label typography — repeated on .stat-card__label,
   .action-card__title, .status-pill, and several status-cell pills.
   Compose alongside the consumer's own font-size + colour. */
.eyebrow {
    font-weight: var(--font-weight-semibold);
    text-transform: uppercase;
    letter-spacing: var(--letter-spacing-wide);
}

/* Skeleton / shimmer loading placeholder. Pages without their own
   loading template show this while data is in flight so the surface
   reads as "still loading" rather than "no data". Compose with one of
   the size variants or set explicit width/height on the element.
   Respects reduced-motion. */
.skeleton {
    background:
        linear-gradient(
            90deg,
            var(--color-surface-raised) 0%,
            var(--color-surface-sunken) 50%,
            var(--color-surface-raised) 100%);
    background-size: 200% 100%;
    animation: companion-skeleton-shimmer 1.6s linear infinite;
    border-radius: var(--radius-sm);
}

.skeleton--row {
    block-size: 1rem;
}

.skeleton--card {
    block-size: 5rem;
    border-radius: var(--radius-md);
}

.skeleton--avatar {
    inline-size: 2.5rem;
    block-size: 2.5rem;
    border-radius: var(--radius-full);
}

@keyframes companion-skeleton-shimmer {
    from { background-position: 200% 0; }
    to   { background-position: -200% 0; }
}

@media (prefers-reduced-motion: reduce) {
    .skeleton {
        animation: none;
        opacity: 0.6;
    }
}

@media (forced-colors: active) {
    .skeleton {
        animation: none;
        background: Canvas;
        border: 1px dashed CanvasText;
        opacity: 1;
    }
}

/* Toast / notification primitive. Designed for transient feedback
   surfaced by a future ToastService; for now usable ad-hoc. Variants
   consume the paired state-surface tokens so contrast tracks the theme.
   Position lives with the consumer (fixed bottom-right, inline within
   a form, etc.) — the primitive only defines the chrome. */
.toast {
    display: inline-flex;
    align-items: flex-start;
    gap: var(--space-2);
    padding: var(--space-3) var(--space-4);
    background: var(--color-surface);
    color: var(--color-text);
    border: 1px solid var(--color-border-subtle);
    border-radius: var(--radius-md);
    box-shadow: var(--shadow-lg);
    font-size: var(--font-size-sm);
    max-inline-size: 28rem;
}

.toast--success {
    background: var(--color-success-surface);
    color: var(--color-success-on-surface);
    border-color: var(--color-success);
}

.toast--warning {
    background: var(--color-warning-surface);
    color: var(--color-warning-on-surface);
    border-color: var(--color-warning);
}

.toast--danger {
    background: var(--color-danger-surface);
    color: var(--color-danger-on-surface);
    border-color: var(--color-danger);
}

.toast--info {
    background: var(--color-info-surface);
    color: var(--color-info-on-surface);
    border-color: var(--color-info);
}

@media (forced-colors: active) {
    .toast {
        border: 2px solid CanvasText;
        background: Canvas;
        color: CanvasText;
        box-shadow: none;
    }
}

/* `.visually-hidden` — content for AT only, never visible. Used for
   "(required)" cues, screen-reader-only loading messages, etc. */
.visually-hidden {
    position: absolute;
    width: 1px;
    height: 1px;
    padding: 0;
    margin: -1px;
    overflow: hidden;
    clip: rect(0, 0, 0, 0);
    white-space: nowrap;
    border: 0;
}

/* Default error boundary surface for uncaught component exceptions.
   `<ErrorBoundary>` is not currently used anywhere in the project — the
   `#blazor-error-ui` toast in MainLayout.razor is the actual fallback —
   but the surface styling is preserved here so a future `<ErrorBoundary>`
   would render with sensible chrome out of the box.

   The framework's English `::after { content: "An error has occurred." }`
   pseudo-element rule is omitted because that string cannot be
   localised through CSS. When `<ErrorBoundary>` is introduced the
   calling site must supply an `<ErrorContent>` template that renders
   localised copy through `IStringLocalizer`. */
.blazor-error-boundary {
    padding: var(--space-4);
    color: var(--color-danger-on-surface);
    background: var(--color-danger-surface);
}

/* Enhanced-navigation loading overlay. Driven by `body.navigating`,
   toggled by `wwwroot/js/enhanced-nav-progress.js` on Blazor's
   enhanced-nav lifecycle events.
 *
 * Branded treatment — a ring spinner in the platform brand colour. It
 * reads from `--color-brand`, so a tenant's custom branding (applied by
 * TenantBrandingService) recolours the loader automatically. The show /
 * hide rules + the timing-delay UX below are independent of the
 * indicator visual.
 *
 * Timing UX: the overlay only fades in 200ms AFTER `body.navigating`
 * goes on, so fast navigations (most of them) never flash the loader.
 * On clear, the fade-out is immediate (no delay). Cold-start
 * navigations that take >200ms get the visible feedback; fast ones
 * stay silent. */

.app-loading-overlay {
    position: fixed;
    inset: 0;
    display: flex;
    align-items: center;
    justify-content: center;
    background: color-mix(in srgb, var(--color-surface) 70%, transparent);
    backdrop-filter: blur(2px);
    pointer-events: none;
    z-index: var(--z-modal);
    opacity: 0;
    /* Fast hide on the way out — base rule applies when body.navigating
       drops, so the overlay fades to 0 over 100ms with no delay. */
    transition: opacity 100ms ease-out;
}

body.navigating .app-loading-overlay {
    opacity: 1;
    /* Delayed show — only fade in if the navigation is taking long
       enough to be noticeable. 200ms is below the perceptual "feels
       slow" threshold; pages that respond inside that window never
       show the loader at all. */
    transition: opacity 150ms ease-out 200ms;
}

.app-loading-overlay__indicator {
    display: flex;
    gap: var(--space-2);
    padding: var(--space-4) var(--space-5);
    border-radius: var(--radius-lg);
    background: var(--color-surface-raised);
    box-shadow: var(--shadow-md);
}

/* Brand honeycomb loader ("Scan") — the animated "Admin Companion" mark that
   replaces the old ring spinner in the enhanced-nav loading overlay (and the
   consent-redirect / tenant-upgrading full-page loaders). Rendered by the
   BrandLoader component as inline SVG; the three blue cells inherit `color`
   (defaulting to --color-brand, so per-tenant branding + light / dark / HC
   themes recolour them), the flagged cell stays Signal Amber. Each loader host
   sits on a --color-surface-raised card, so the blue mark reads in every theme.

   The "Scan" sweep lights each cell with a small bounce in sequence. A
   small-area opacity + scale sweep is not a vestibular trigger (unlike large
   parallax / zoom), so it keeps animating under prefers-reduced-motion — just
   slower — rather than freezing or blinking. That is deliberate: loading
   feedback should read as "working", and a frozen or blinking mark reads as
   "stuck". */
.brand-loader {
    display: block;
    color: var(--color-brand, var(--color-focus-ring));
    /* Default footprint (the enhanced-nav overlay). Portrait honeycomb, so
       height drives and width follows the mark's aspect ratio. Hosts that want
       a different size override `height` on their own loader class. */
    height: 2.5rem;
    width: auto;
    aspect-ratio: 76.42 / 94.8;
}

.brand-loader__cell {
    /* Scale each cell around its own centre (not the SVG origin). */
    transform-box: fill-box;
    transform-origin: center;
    animation: brand-loader-scan 1.8s linear infinite;
}

/* Staggered starts sweep the highlight around the mark (top-left → bottom-left
   → bottom-right → top-right), mirroring the "scan" loader's phase offsets. */
.brand-loader__cell--tl { animation-delay: 0s; }
.brand-loader__cell--bl { animation-delay: -0.45s; }
.brand-loader__cell--br { animation-delay: -0.9s; }
.brand-loader__cell--tr { animation-delay: -1.35s; }

@keyframes brand-loader-scan {
    0%   { opacity: 0.26; transform: scale(1); }
    10%  { opacity: 1;    transform: scale(1.08); }
    20%  { opacity: 1;    transform: scale(1.04); }
    30%  { opacity: 0.26; transform: scale(1); }
    100% { opacity: 0.26; transform: scale(1); }
}

@media (prefers-reduced-motion: reduce) {
    /* Gentler, slower sweep — still moving (never a blink or a frozen mark),
       but calmer for motion-sensitive users. */
    .brand-loader__cell {
        animation-duration: 2.6s;
    }
}

/* Rotation keyframe shared by the inline button / list "busy" spinners below. */
@keyframes app-loading-spin {
    to { transform: rotate(360deg); }
}

/* ----------------------------------------------------------------------
   Reusable submit-busy treatment. Any form opted into the submit-busy
   guard (`data-busy-on-submit`, see wwwroot/js/form-submit-busy.js) gets
   this for free: while the form is mid-submit, the JS disables its submit
   controls and stamps `aria-busy="true"`, and these rules render that as a
   brand ring spinner + progress cursor. Global (not scoped) on purpose —
   it sits on the <button> inside the <form> that <EditForm> renders, which
   Blazor CSS isolation does not stamp with a component scope.
   --------------------------------------------------------------------- */
[data-busy-on-submit][data-submitting] button[aria-busy="true"],
[data-busy-on-submit][data-submitting] input[aria-busy="true"] {
    cursor: progress;
}

[data-busy-on-submit][data-submitting] button[aria-busy="true"]::before {
    content: "";
    display: inline-block;
    width: 0.85em;
    height: 0.85em;
    margin-right: 0.5em;
    vertical-align: -0.1em;
    border-radius: 50%;
    /* Spins in the button's own text colour so it reads on any button
       variant (primary / secondary / danger) without per-variant rules. */
    border: 2px solid color-mix(in srgb, currentColor 35%, transparent);
    border-top-color: currentColor;
    animation: app-loading-spin 0.7s linear infinite;
}

@media (prefers-reduced-motion: reduce) {
    /* Keep the button busy indicator SPINNING (just slower) rather than
       blinking — a small rotating ring reads as "working" far more clearly
       than an opacity pulse, and a spinner is not a vestibular-motion trigger. */
    [data-busy-on-submit][data-submitting] button[aria-busy="true"]::before {
        animation: app-loading-spin 1.2s linear infinite;
    }
}

/* ----------------------------------------------------------------------
   CIS M365 benchmark page — Overview / Settings card internals.
   Unscoped (global) on purpose: several of these classes sit on the
   <form> that <EditForm> renders, which Blazor CSS isolation does not
   stamp with a component scope. The benchmark-m365__ prefix keeps them
   page-local in practice.
   ---------------------------------------------------------------------- */
/* Summary tiles relocated out of the hero into each tab panel (so the tab
   strip sits directly under the hero description). The tab strip already
   carries a bottom margin, so this only needs spacing below the tiles. */
.benchmark-m365__tab-tiles {
    margin-bottom: var(--space-6);
}

/* Explains the Score / Failed tile sparklines (stored-count history vs
   the attestation-aware headline). Sits just under the tiles grid. */
.benchmark-m365__tiles-note {
    margin: calc(-1 * var(--space-3)) 0 var(--space-6);
    max-width: 72ch;
    font-size: var(--font-size-sm);
    color: var(--color-text-muted);
}

/* Controls-table control-id link — a standard brand text link (the cell
   otherwise rendered a default user-agent blue underline that didn't match
   the solution). Underlines on hover/focus for affordance. */
.app-shell__main a.benchmark-controls-table__control-link {
    color: var(--color-brand);
    font-weight: var(--font-weight-medium);
    text-decoration: none;
}

    .app-shell__main a.benchmark-controls-table__control-link:hover,
    .app-shell__main a.benchmark-controls-table__control-link:focus-visible {
        text-decoration: underline;
    }

/* Per-control Requirements column — ✓/✗ permission badges. Global (not
   scoped) because the cells are built by RenderTreeBuilder, which never
   carries the page's scope attribute. Mirrors the report detail +
   connection page probe-permission treatment; reuses design tokens. */
.benchmark-controls-table__reqs {
    display: flex;
    flex-wrap: wrap;
    gap: var(--space-2);
}

.benchmark-controls-table__req {
    display: inline-flex;
    align-items: center;
    gap: var(--space-1);
    padding: var(--space-1) var(--space-2);
    border-radius: var(--radius-sm, 6px);
    border: 1px solid var(--color-border-subtle);
    background: var(--color-surface);
    font-size: var(--font-size-sm);
    color: var(--color-text);
}

.benchmark-controls-table__req-glyph {
    display: inline-flex;
    align-items: center;
    justify-content: center;
    /* `flex: none` locks the dimensions so a long permission label
       on the badge can't shrink the circle below its siblings. */
    flex: none;
    width: 1.1rem;
    height: 1.1rem;
    border-radius: 50%;
    font-size: var(--font-size-xs, 0.75rem);
    line-height: 1;
}

.benchmark-controls-table__req--ok .benchmark-controls-table__req-glyph {
    color: var(--color-success);
    background: color-mix(in srgb, var(--color-success) 15%, transparent);
}

.benchmark-controls-table__req--missing {
    border-color: color-mix(in srgb, var(--color-danger) 40%, var(--color-border-subtle));
}

    .benchmark-controls-table__req--missing .benchmark-controls-table__req-glyph {
        color: var(--color-danger);
        background: color-mix(in srgb, var(--color-danger) 15%, transparent);
    }

@media (forced-colors: active) {
    .benchmark-controls-table__req {
        border: 1px solid CanvasText;
    }
}

/* ===================================================================
   Per-control Results tab (ControlTrend.razor) — summary tiles + a
   "Latest evidence" card + an "Outcome trend" timeline card. Global
   (not scoped) because the evidence value markup comes from a foreign
   RenderFragment (HtmlEvidenceVisitor) that never carries the page's
   scope attribute. Card recipe mirrors .dashboard-section--card.
   =================================================================== */
/* The tiles grid (.list-page-hero__tiles) already lays itself out; the
   namespaced hook adds breathing room between the Overview's actions card
   above and the tile row. */
.control-trend__tiles {
    margin: var(--space-6) 0 0;
}

/* Inline Run-gate note under a disabled Run button — same shape on the
   report detail page and the benchmark control page so the two surfaces
   read identically when a permission/connection is missing. */
.run-gate {
    display: flex;
    flex-wrap: wrap;
    align-items: center;
    gap: var(--space-2);
    margin: var(--space-2) 0 0;
    color: var(--color-text-muted);
    font-size: var(--font-size-sm);
}

/* Leading ✗ glyph on the Run-gate note — same red treatment as the
   missing-permission badge on the connection-test result so the gate
   reads visually identical across both surfaces. */
.run-gate__icon {
    display: inline-flex;
    align-items: center;
    justify-content: center;
    width: 1.1rem;
    height: 1.1rem;
    border-radius: 50%;
    font-size: var(--font-size-xs, 0.75rem);
    line-height: 1;
    color: var(--color-danger);
    background: color-mix(in srgb, var(--color-danger) 15%, transparent);
    flex: 0 0 auto;
}

/* Visually-disabled treatment for the Run / submit buttons in the
   Overview action row so a gated Run button reads "not available" at a
   glance, mirroring the disabled attribute the markup already sets when
   _inFlight || !IsRunnable. Mirrors the base submit-button selector
   specificity so it wins on order; the hover rule's background tint
   still cascades through the 0.55 opacity, which is visually fine —
   "dimmed bright" still reads as not-available. */
.app-shell__main .form-actions button[type="submit"]:disabled {
    opacity: 0.55;
    cursor: not-allowed;
    filter: grayscale(0.4);
}

.control-trend-card {
    padding: var(--space-5) var(--space-5) var(--space-4);
    background: var(--color-surface-raised);
    border: 1px solid var(--color-border-subtle);
    border-radius: var(--radius-lg);
    box-shadow: var(--shadow-sm);
}

.control-trend-card__heading {
    margin: 0 0 var(--space-3);
    font-size: var(--font-size-lg);
    font-weight: var(--font-weight-semibold);
    letter-spacing: var(--letter-spacing-tight, -0.01em);
    color: var(--color-text);
}

/* Evidence card header: title on the left, outcome pill(s) + timestamp on
   the right; wraps to a stacked column on narrow viewports. */
.control-trend-card__header {
    display: flex;
    flex-wrap: wrap;
    align-items: center;
    justify-content: space-between;
    gap: var(--space-2) var(--space-3);
    margin-bottom: var(--space-3);
}

    .control-trend-card__header .control-trend-card__heading {
        margin: 0;
    }

.control-trend-card__meta {
    display: inline-flex;
    align-items: center;
    flex-wrap: wrap;
    gap: var(--space-2);
}

.control-trend-card__when {
    font-size: var(--font-size-sm);
    color: var(--color-text-muted);
}

.control-trend__summary {
    margin: 0 0 var(--space-4);
    font-size: var(--font-size-sm);
    color: var(--color-text-muted);
}

.control-trend__overflow {
    margin: var(--space-3) 0 0;
    font-size: var(--font-size-xs);
    color: var(--color-text-muted);
}

/* ----- Latest-evidence body ----- */
.control-trend__evidence-summary {
    margin: 0 0 var(--space-4);
    color: var(--color-text);
    line-height: var(--line-height-normal, 1.5);
}

/* Sunken panel framing the captured signal so it reads as data the run
   produced, distinct from the surrounding card surface. */
.control-trend__evidence-body {
    padding: var(--space-4);
    border: 1px solid var(--color-border-subtle);
    border-radius: var(--radius-md);
    background: var(--color-surface-sunken, var(--color-surface));
}

.control-trend__evidence-none {
    margin: 0;
    color: var(--color-text-muted);
}

@media (forced-colors: active) {
    .control-trend-card,
    .control-trend__evidence-body {
        border: 1px solid CanvasText;
        box-shadow: none;
    }
}

/* Acknowledge-as-accepted-risk modal — matches the solution's dialog/form
   chrome: card surface, branded primary + outline secondary buttons, muted
   helper text. Previously unstyled (browser defaults). */
.benchmark-controls-table__ack-dialog {
    width: min(34rem, calc(100vw - 2 * var(--space-4)));
    padding: var(--space-5);
    border: 1px solid var(--color-border);
    border-radius: var(--radius-lg);
    background: var(--color-surface);
    color: var(--color-text);
    box-shadow: var(--shadow-lg);
}

    .benchmark-controls-table__ack-dialog::backdrop {
        background: rgba(0, 0, 0, 0.45);
    }

.benchmark-controls-table__ack-form {
    display: flex;
    flex-direction: column;
    gap: var(--space-4);
    margin: 0;
}

.benchmark-controls-table__ack-title {
    margin: 0;
    font-size: var(--font-size-lg);
    font-weight: var(--font-weight-semibold);
    color: var(--color-text);
}

.benchmark-controls-table__ack-warning {
    margin: 0;
    color: var(--color-text-muted);
    font-size: var(--font-size-sm);
    line-height: var(--line-height-normal, 1.6);
}

.benchmark-controls-table__ack-label {
    display: flex;
    flex-direction: column;
    gap: var(--space-1);
    margin: 0;
    font-size: var(--font-size-sm);
    font-weight: var(--font-weight-medium);
    color: var(--color-text);
}

.benchmark-controls-table__ack-error {
    margin: 0;
    color: var(--color-danger);
    font-size: var(--font-size-sm);
}

.benchmark-controls-table__ack-actions {
    display: flex;
    flex-wrap: wrap;
    justify-content: flex-end;
    gap: var(--space-3);
    margin-top: var(--space-2);
}

/* Confirm / Cancel are type="button" (the dialog uses method="dialog" +
   preventDefault), so they don't inherit the global submit-button skin —
   mirror it here: Confirm = branded primary, Cancel = outline secondary. */
.benchmark-controls-table__ack-confirm,
.benchmark-controls-table__ack-cancel {
    display: inline-flex;
    align-items: center;
    justify-content: center;
    min-height: 2.75rem;
    padding: 0 var(--space-5);
    border-radius: var(--radius-md);
    font: inherit;
    font-weight: var(--font-weight-semibold);
    cursor: pointer;
    box-sizing: border-box;
    border: 1px solid transparent;
    transition: filter var(--motion-duration-fast) var(--motion-easing-standard),
                background var(--motion-duration-fast) var(--motion-easing-standard),
                color var(--motion-duration-fast) var(--motion-easing-standard);
}

.benchmark-controls-table__ack-confirm {
    background: var(--color-chrome-surface);
    color: var(--color-chrome-on-surface);
    box-shadow: var(--shadow-sm);
}

    .benchmark-controls-table__ack-confirm:hover:not(:disabled) {
        filter: brightness(1.08);
    }

    .benchmark-controls-table__ack-confirm:disabled {
        opacity: 0.55;
        cursor: not-allowed;
    }

.benchmark-controls-table__ack-cancel {
    background: transparent;
    color: var(--color-chrome-surface);
    border-color: var(--color-chrome-surface);
}

    .benchmark-controls-table__ack-cancel:hover {
        background: var(--color-chrome-surface);
        color: var(--color-chrome-on-surface);
    }

    .benchmark-controls-table__ack-confirm:focus-visible,
    .benchmark-controls-table__ack-cancel:focus-visible {
        outline: 2px solid var(--color-focus-ring);
        outline-offset: 2px;
    }

/* Overview "about this benchmark" blurb under the identity header. */
.benchmark-m365__about {
    margin: var(--space-4) 0 0;
    max-width: 70ch;
    color: var(--color-text-muted);
    font-size: var(--font-size-sm);
    line-height: var(--line-height-normal, 1.6);
}

/* The About block is a short stack of paragraphs (what the benchmark is,
   how it is organized, how Admin Companion uses it). Space them evenly and
   drop the trailing margin so the card padding stays balanced. */
.benchmark-m365__about > p {
    margin: 0 0 var(--space-3);
}

.benchmark-m365__about > p:last-child {
    margin-bottom: 0;
}

/* Overview identity header: benchmark name + version badge on the left,
   the version switcher (when >1 version) on the right. */
.benchmark-m365__identity {
    display: flex;
    flex-wrap: wrap;
    align-items: flex-end;
    justify-content: space-between;
    gap: var(--space-4) var(--space-6);
}

.benchmark-m365__identity-eyebrow {
    margin: 0 0 var(--space-1);
    font-size: var(--font-size-sm);
    font-weight: var(--font-weight-medium);
    color: var(--color-text-muted);
    text-transform: uppercase;
    letter-spacing: var(--letter-spacing-wide);
}

.benchmark-m365__identity-title {
    display: flex;
    flex-wrap: wrap;
    align-items: center;
    gap: var(--space-3);
    margin: 0;
    font-size: var(--font-size-2xl);
    font-weight: var(--font-weight-semibold);
    color: var(--color-text);
    line-height: 1.2;
}

.benchmark-m365__identity-badge {
    display: inline-flex;
    align-items: center;
    padding: var(--space-1) var(--space-3);
    background: var(--color-brand-subtle);
    color: var(--color-brand);
    border-radius: var(--radius-full);
    font-size: var(--font-size-sm);
    font-weight: var(--font-weight-semibold);
    letter-spacing: var(--letter-spacing-normal);
}

/* Shared account-manage card — the surface every /account/manage/*
   sub-page wears for its primary form. Hairline + radius + subtle
   shadow match the profile card on /account/manage's hub so the user
   sees a consistent "settings surface" treatment across the area.
   Pair with <PageHeader> for the hero and `.settings-form` inside for
   the field rhythm. */
.account-form-card {
    background: var(--color-surface);
    border: 1px solid var(--color-border-subtle);
    border-radius: var(--radius-lg);
    padding: var(--space-5) var(--space-6);
    box-shadow: var(--shadow-sm);
    /* Full-width on the page — the card anchors the form layout
       beneath the hero rather than floating as a narrow strip. Inputs
       inside still cap at the .settings-form max-inline-size rule, so
       fields stay at a comfortable reading width on wide viewports. */
    inline-size: 100%;
    margin-top: var(--space-6);
}

@media (forced-colors: active) {
    .account-form-card {
        border: 1px solid CanvasText;
        box-shadow: none;
    }
}

@media (max-width: 599px) {
    .account-form-card {
        padding: var(--space-4);
    }
}

/* Shared settings-form layout — a vertical stack of field groups with a
   trailing action row. Extracted from the benchmark settings page so the
   report Settings tab (and any future settings surface) reuses the exact
   same rhythm instead of restating it under a feature-scoped name. */
.settings-form {
    display: flex;
    flex-direction: column;
    gap: var(--space-4);
    margin-top: var(--space-2);
}

/* Settings-form inputs cap at a comfortable reading width — the global
   `:is(.public-shell__main, .app-shell__main) input[type=…]` rule
   sets `width: 100%` so the field stretches to its container, which
   looks awkward on the now-full-width account-manage surface cards
   (e.g. `recovery-email-candidate` would otherwise span the entire
   card on a 1440-wide viewport). Cap to the same ~32rem the standard
   stacked-form heuristics target; the field still fills its column on
   narrow viewports. <textarea> opts out because long-form text wants
   the full width. */
.settings-form :is(input[type="text"], input[type="email"], input[type="password"], input[type="search"], input[type="tel"], input[type="number"], input[type="url"], input:not([type]), select) {
    max-inline-size: 32rem;
}

/* HelpText (rendered as `<small class="form-help">`) wraps at the
   same width as the field it describes. `<small>` is inline by
   default, so without this the hint would flow to the full container
   width and read as a wider element than the input above it. Display
   block + the matching cap keeps the hint visually tied to its
   field. */
.settings-form .form-help {
    display: block;
    max-inline-size: 32rem;
}

.benchmark-m365__version-picker {
    display: inline-flex;
    align-items: center;
    gap: var(--space-2);
    margin: 0;
}

    /* The leading "Version" label sits inline with the select/button rather
       than as a stacked block (the global label rule), and the select
       shouldn't stretch to fill the row. */
    .benchmark-m365__version-picker label {
        margin: 0;
        white-space: nowrap;
        font-size: var(--font-size-sm);
        color: var(--color-text-muted);
    }

    .benchmark-m365__version-picker select {
        width: auto;
        min-width: 8rem;
    }

    /* The global submit-button rule adds a top margin for stacked-form
       rhythm; inside this centered single-row picker it pushes Apply
       below the select's midline. Specificity must match that rule
       (class + attribute + :not(class)) so this later declaration wins. */
    .app-shell__main .benchmark-m365__version-picker button[type="submit"] {
        margin-top: 0;
    }

.form-actions {
    display: flex;
    flex-wrap: wrap;
    align-items: center;
    gap: var(--space-3);
    margin-top: var(--space-2);
}

    /* An EditForm dropped into the action row collapses its own box so its
       submit button participates directly in the flex row alongside the
       sibling link actions (the hidden version/tab inputs render nothing). */
    .form-contents {
        display: contents;
    }

/* Page action toolbar header on the Controls tab — a hairline divider plus
   spacing below sets the Run / View jobs / Export buttons apart from the
   controls table (and its own Filters / Columns toolbar) beneath it.
   Used by every benchmark family's Microsoft 365 settings page. */
.benchmark-m365__controls-header {
    margin-bottom: var(--space-5);
    padding-bottom: var(--space-4);
    border-bottom: 1px solid var(--color-border);
}

    /* The shared submit-button rule adds its own margin-top; the row above
       already provides that spacing. Zero it here so a submit button and a
       sibling .benchmark-m365__action-link (both min-height 2.75rem) line up on
       the same baseline instead of the button sitting a step lower. Mirror
       the base rule's full selector (incl. :not(.theme-toggle__button)) so
       this override actually outweighs it — a plainer selector loses the
       specificity match and the stale margin-top wins. */
    .app-shell__main .form-actions button[type="submit"]:not(.theme-toggle__button) {
        margin-top: 0;
    }

/* Settings accordion — a collapsible group list (native <details>/<summary>,
   so it works without JS in static SSR). Each group's summary shows its
   current value; expanding reveals the edit form. The just-saved group stays
   open across a post-back to surface its inline status message. Extracted
   from the benchmark settings page so the report Settings tab reuses it. */
.settings-accordion {
    margin-top: var(--space-4);
    display: flex;
    flex-direction: column;
    gap: var(--space-4);
}

/* Each section is its own card with clear separation, so a section's controls
   (and its Save button) read as belonging to that section rather than floating
   in one merged list. */
.settings-accordion__item {
    border: 1px solid var(--color-border);
    border-radius: var(--radius-lg);
    background: var(--color-surface);
    overflow: hidden;
}

.settings-accordion__summary {
    display: flex;
    align-items: center;
    gap: var(--space-3) var(--space-4);
    padding: var(--space-4) var(--space-5);
    cursor: pointer;
    list-style: none;
    color: var(--color-text);
    font-weight: var(--font-weight-semibold);
    /* Tinted header bar so the summary reads as the section's main header,
       clearly distinct from the white body below it once expanded. The
       3px leading accent uses --color-chrome-surface (the same fixed
       brand blue as the header/sidebar) rather than --color-brand, so it
       stays the exact header blue in every theme and doesn't drift darker
       under a tenant's custom brand or lighter in dark mode. */
    background: var(--color-surface-sunken);
    box-shadow: inset 3px 0 0 0 var(--color-chrome-surface);
}

    /* The section title is the primary label — a touch larger + bolder than
       the body so the header hierarchy is obvious at a glance. */
    .settings-accordion__title {
        font-size: var(--font-size-md);
        font-weight: var(--font-weight-bold, 700);
        letter-spacing: var(--letter-spacing-tight, -0.01em);
    }

    /* Suppress the native disclosure triangle in both engines. */
    .settings-accordion__summary::-webkit-details-marker {
        display: none;
    }

    .settings-accordion__summary:hover {
        background: color-mix(in srgb, var(--color-brand) 8%, var(--color-surface-sunken));
    }

    .settings-accordion__summary:focus-visible {
        outline: 2px solid var(--color-focus-ring);
        outline-offset: -2px;
    }

/* Title + muted description stacked on the left; the wrapper takes the
   leftover width so the value + chevron sit at the right edge. */
.settings-accordion__heading {
    display: flex;
    flex-direction: column;
    gap: 2px;
    margin-right: auto;
}

.settings-accordion__desc {
    font-weight: var(--font-weight-regular);
    font-size: var(--font-size-sm);
    color: var(--color-text-muted);
}

.settings-accordion__value {
    color: var(--color-text-muted);
    font-weight: var(--font-weight-regular);
    font-size: var(--font-size-sm);
    text-align: end;
}

/* Custom chevron after the value; rotates down when the item is open. */
.settings-accordion__summary::after {
    content: "";
    flex: none;
    width: 0.5rem;
    height: 0.5rem;
    margin-inline-start: var(--space-1);
    border-right: 2px solid var(--color-text-muted);
    border-bottom: 2px solid var(--color-text-muted);
    transform: rotate(-45deg);
    transition: transform var(--motion-duration-fast) var(--motion-easing-standard);
}

.settings-accordion__item[open] > .settings-accordion__summary {
    border-bottom: 1px solid var(--color-border);
}

    .settings-accordion__item[open] > .settings-accordion__summary::after {
        transform: rotate(45deg);
    }

.settings-accordion__body {
    padding: var(--space-3) var(--space-5) var(--space-5);
}

    /* The form's own top margin is redundant inside the accordion body. */
    .settings-accordion__body .settings-form {
        margin-top: 0;
    }

/* Read-only summary inside a settings-accordion body — used by the
   benchmark control's "active acknowledgement" view to render the
   rationale / expiry pair before the Revoke button. Term/description
   pairs aligned in a two-column grid so the values share a vertical line. */
.settings-summary-list {
    margin: 0 0 var(--space-4);
    display: grid;
    grid-template-columns: minmax(6rem, max-content) 1fr;
    column-gap: var(--space-4);
    row-gap: var(--space-2);
}

    .settings-summary-list dt {
        margin: 0;
        font-weight: var(--font-weight-semibold);
        color: var(--color-text-muted);
    }

    .settings-summary-list dd {
        margin: 0;
        color: var(--color-text);
    }

/* Label-left / control-right setting rows. The left column carries the
   label + helper text; the right column carries the control, capped so
   inputs don't sprawl across the card. Rows share a hairline divider for
   rhythm and collapse to a single stacked column on narrow viewports. */
.settings-fields {
    display: flex;
    flex-direction: column;
}

.settings-row {
    display: grid;
    grid-template-columns: minmax(0, 1fr) minmax(12rem, 18rem);
    align-items: start;
    column-gap: var(--space-6);
    padding: var(--space-4) 0;
}

    .settings-row + .settings-row {
        border-top: 1px solid var(--color-border);
    }

/* Class-based hide used by the settings-form's conditional rows.
   Razor's `attr="@bool"` for plain HTML attributes is unreliable
   (some pipelines render `attr="False"` literally, which is still
   "present" by attribute-selector rules), so a dedicated class wins
   without ambiguity. The accompanying border-collapse rule keeps
   dividers from drawing into a hidden gap. */
.settings-row.is-hidden,
.settings-row__control--inline > .is-hidden {
    display: none !important;
}

    .settings-row.is-hidden + .settings-row,
    .settings-row + .settings-row.is-hidden {
        border-top-color: transparent;
    }

/* CSS-only conditional visibility for the action settings form, driven
   by the live `:checked` state of the Mode / Cadence / DayOfMonthPreset
   dropdowns. Uses `:has()` to scope each rule to a form whose dropdown
   has the matching option currently selected. Browser-native — no JS
   needed for visibility (the day-of-month clamp is the only JS bit
   that remains). Requires :has() — every evergreen browser since 2023.

   Note: only the *approval-only* rows collapse when Mode = Scheduled.
   The Schedule accordion itself is always visible — operators want to
   see and configure a fallback / next-run cadence even when the
   current Mode is Require-approval, so hiding the whole branch on
   mode toggle reads as data loss. */
.settings-form:has(#action-mode option[value="Scheduled"]:checked) [data-mode-approval] {
    display: none !important;
}

/* Cadence-driven row visibility used to live here, gated on the
   action-detail-specific #action-cadence / #action-day-of-month-preset
   ids. After the Theme D CadencePicker lift those ids no longer
   exist — the component owns its visibility in its scoped CSS at
   Components/Ui/Forms/CadencePicker.razor.css. */

.settings-row__text {
    display: flex;
    flex-direction: column;
    gap: var(--space-1);
}

    .settings-row__text label {
        margin: 0;
        font-size: var(--font-size-md);
        font-weight: var(--font-weight-semibold);
        color: var(--color-text);
    }

    .settings-row__text .form-help {
        margin: 0;
    }

/* The toggle pill is much narrower than the regular control column
   (select / number / time). Right-aligning it inside the column reads
   as a one-glance affordance — the label sits left, the switch sits
   under the right edge of the column where the eye naturally lands
   when scanning a form's "is this on?" controls. Vertically centred
   against the label block. */
.settings-row__control--toggle {
    align-self: center;
    justify-self: end;
}

/* Sliding toggle switch — re-skins a native <input type="checkbox">
   either via the `.settings-row__control--toggle` wrapper (forms that
   live inside a settings grid pick this up automatically) OR via a
   `.toggle-switch` class on the checkbox itself (one-off surfaces
   like the SSO enable / Remediation allow-writes one-line forms that
   don't use the settings-grid markup).

   The native semantics (checked / focus / disabled / form value)
   survive the restyle, so screen readers, keyboard users, and the
   form binder all keep working. Use :checked / :focus-visible
   pseudo-classes for state so the switch tracks the live input
   without any JS. */
.settings-row__control--toggle input[type="checkbox"],
input[type="checkbox"].toggle-switch {
    /* Reset the native chrome so the box, tick, and OS-specific styling
       all go away; we paint the whole switch from CSS below. */
    appearance: none;
    -webkit-appearance: none;
    -moz-appearance: none;
    margin: 0;
    /* 3 : 1.5 rem track. Touch-friendly height (24px is the WCAG 2.5.8
       AA target floor; the track is 24px so the entire switch meets
       the touch target without padding tricks). */
    width: 3rem;
    height: 1.5rem;
    flex-shrink: 0;
    background: var(--color-border);
    border-radius: 999px;
    position: relative;
    cursor: pointer;
    /* Border keeps the off-state shape visible against light surfaces
       where the muted track would otherwise wash into the page. */
    border: 1px solid var(--color-border);
    transition: background var(--motion-duration-fast) var(--motion-easing-standard),
                border-color var(--motion-duration-fast) var(--motion-easing-standard);
    /* WCAG 2.5.8 AA — match the global target-size token so the click
       area meets the same floor as the other interactive surfaces. */
    min-width: var(--target-size-touch);
}

/* The sliding thumb — drawn via ::before so we don't need extra
   markup. Starts pinned to the left edge of the track; the :checked
   rule below translates it to the right. */
.settings-row__control--toggle input[type="checkbox"]::before,
input[type="checkbox"].toggle-switch::before {
    content: '';
    position: absolute;
    top: 50%;
    left: 2px;
    width: calc(1.5rem - 6px);
    height: calc(1.5rem - 6px);
    background: var(--color-surface);
    border-radius: 50%;
    transform: translateY(-50%);
    transition: transform var(--motion-duration-fast) var(--motion-easing-standard);
    box-shadow: 0 1px 2px rgba(0, 0, 0, 0.18);
}

/* On — track flips to brand colour, thumb slides to the right edge. */
.settings-row__control--toggle input[type="checkbox"]:checked,
input[type="checkbox"].toggle-switch:checked {
    background: var(--color-brand);
    border-color: var(--color-brand);
}

    .settings-row__control--toggle input[type="checkbox"]:checked::before,
    input[type="checkbox"].toggle-switch:checked::before {
        transform: translate(calc(3rem - 1.5rem), -50%);
    }

    .settings-row__control--toggle input[type="checkbox"]:hover:not(:disabled),
    input[type="checkbox"].toggle-switch:hover:not(:disabled) {
        border-color: var(--color-brand);
    }

    .settings-row__control--toggle input[type="checkbox"]:focus-visible,
    input[type="checkbox"].toggle-switch:focus-visible {
        outline: 2px solid var(--color-focus-ring);
        outline-offset: 2px;
    }

    .settings-row__control--toggle input[type="checkbox"]:disabled,
    input[type="checkbox"].toggle-switch:disabled {
        opacity: 0.5;
        cursor: not-allowed;
    }

/* Forced-colors / high-contrast: use system tokens so the switch
   remains visible when the user has overridden the palette. */
@media (forced-colors: active) {
    .settings-row__control--toggle input[type="checkbox"],
    input[type="checkbox"].toggle-switch {
        background: ButtonFace;
        border-color: CanvasText;
    }
        .settings-row__control--toggle input[type="checkbox"]::before,
        input[type="checkbox"].toggle-switch::before {
            background: CanvasText;
        }
        .settings-row__control--toggle input[type="checkbox"]:checked,
        input[type="checkbox"].toggle-switch:checked {
            background: Highlight;
        }
}

@media (prefers-reduced-motion: reduce) {
    .settings-row__control--toggle input[type="checkbox"],
    .settings-row__control--toggle input[type="checkbox"]::before,
    input[type="checkbox"].toggle-switch,
    input[type="checkbox"].toggle-switch::before {
        transition: none;
    }
}

/* Two controls side by side in one settings row (e.g. a "Specific day"
   preset + custom number). Lay them out on a single line with a real
   gap so the second control doesn't crowd the first. */
.settings-row__control--inline {
    display: flex;
    flex-wrap: wrap;
    align-items: center;
    gap: var(--space-3);
}

/* When the inline group is inside a settings row, give the children
   a sensible inherent width so they don't collapse to 0 width. */
.settings-row__control--inline > select,
.settings-row__control--inline > input {
    width: auto;
    min-width: 10rem;
    flex: 0 1 auto;
}

@media (max-width: 40rem) {
    .settings-row {
        grid-template-columns: 1fr;
        row-gap: var(--space-2);
    }
}

.benchmark-m365__retention-note,
.benchmark-m365__readonly-note {
    margin-top: var(--space-3);
    color: var(--color-text-muted);
    font-size: var(--font-size-sm);
}

/* Secondary action rendered as a link (e.g. "View jobs") — mirror the
   secondary submit-button treatment so it reads as a sibling action of
   the primary button next to it instead of a bare text link. Shared across
   the benchmark and report action rows. */
.app-shell__main a.action-link {
    display: inline-flex;
    align-items: center;
    justify-content: center;
    min-height: 2.75rem;
    padding: 0 var(--space-5);
    background: transparent;
    color: var(--color-chrome-surface);
    border: 1px solid var(--color-chrome-surface);
    border-radius: var(--radius-md);
    font: inherit;
    font-weight: var(--font-weight-semibold);
    text-decoration: none;
    box-sizing: border-box;
    transition: background var(--motion-duration-fast) var(--motion-easing-standard),
                color var(--motion-duration-fast) var(--motion-easing-standard);
}

    .app-shell__main a.action-link:hover {
        background: var(--color-chrome-surface);
        color: var(--color-chrome-on-surface);
        text-decoration: none;
    }

    .app-shell__main a.action-link:focus-visible {
        outline: 2px solid var(--color-focus-ring);
        outline-offset: 3px;
    }

/* ----------------------------------------------------------------------
   Rendered benchmark control documentation — the server-sanitised
   SafeHtml from the content sync, injected via MarkupString on the
   per-control Guidance tab. Unscoped on purpose: MarkupString HTML is
   raw, so Blazor CSS isolation can't stamp it with a component scope.
   The body's leading <h1> repeats the page's own control-title heading,
   so it's hidden — the page <h1> stays the single heading.
   ---------------------------------------------------------------------- */
.benchmark-content {
    max-width: 70ch;
    line-height: var(--line-height-normal, 1.6);
    color: var(--color-text);
}

    .benchmark-content > h1:first-child {
        display: none;
    }

    .benchmark-content h2 {
        margin: var(--space-6) 0 var(--space-2);
        font-size: var(--font-size-lg);
        font-weight: var(--font-weight-semibold);
        color: var(--color-text);
    }

    .benchmark-content h3 {
        margin: var(--space-5) 0 var(--space-2);
        font-size: var(--font-size-md);
        font-weight: var(--font-weight-semibold);
        color: var(--color-text);
    }

    .benchmark-content p,
    .benchmark-content ul,
    .benchmark-content ol {
        margin: 0 0 var(--space-3);
    }

    .benchmark-content ul,
    .benchmark-content ol {
        padding-inline-start: var(--space-6);
    }

    .benchmark-content li {
        margin-bottom: var(--space-1);
    }

    /* Brand-coloured list markers: numbers on <ol> and bullets on <ul>
       paint in the Companion accent so a long Audit / Remediation
       checklist scans more quickly. ::marker accepts color + font-weight
       but not full font-size (Chromium-only) — kept conservative for
       cross-browser parity. */
    .benchmark-content ol > li::marker {
        color: var(--color-brand);
        font-weight: var(--font-weight-semibold);
    }

    .benchmark-content ul > li::marker {
        color: var(--color-brand);
    }

    /* Nested lists tone the marker down a notch so the top level still
       leads visually. */
    .benchmark-content ol ol > li::marker,
    .benchmark-content ul ul > li::marker,
    .benchmark-content ol ul > li::marker,
    .benchmark-content ul ol > li::marker {
        color: var(--color-text-muted);
    }

    .benchmark-content a {
        color: var(--color-brand);
        text-decoration: underline;
    }

    .benchmark-content code {
        font-family: var(--font-mono, ui-monospace, "Cascadia Code", Consolas, monospace);
        font-size: 0.9em;
        background: var(--color-surface-sunken);
        padding: 0.1em 0.35em;
        border-radius: var(--radius-sm, 3px);
    }

    .benchmark-content pre {
        overflow-x: auto;
        padding: var(--space-3);
        background: var(--color-surface-sunken);
        border-radius: var(--radius-md);
    }

        .benchmark-content pre code {
            background: none;
            padding: 0;
        }

    .benchmark-content blockquote {
        margin: 0 0 var(--space-3);
        padding: var(--space-2) var(--space-4);
        border-inline-start: 3px solid var(--color-border);
        color: var(--color-text-muted);
    }

    .benchmark-content table {
        border-collapse: collapse;
        width: 100%;
        margin: 0 0 var(--space-4);
    }

    .benchmark-content th,
    .benchmark-content td {
        border: 1px solid var(--color-border);
        padding: var(--space-2) var(--space-3);
        text-align: start;
    }

    .benchmark-content th {
        background: var(--color-surface-sunken);
        font-weight: var(--font-weight-semibold);
    }
/* Per-control trend detail — the platform-admin debug expander needs
   bespoke styling; the rest of the page renders on element defaults +
   global tokens. Lived as scoped razor.css under CIS/ControlTrend.razor
   originally; promoted here so the parallel SCuBA/ControlTrend.razor
   gets the same affordance without forking a second scoped copy. */

/* (Header section/title styles removed — the page now uses the shared
   <ListPageHero> for its heading + description.) */

.control-trend__debug {
    margin-top: var(--space-5);
    padding: var(--space-4) var(--space-5);
    border: 1px solid var(--color-border-subtle);
    border-radius: var(--radius-lg);
    background: var(--color-surface-sunken, var(--color-surface));
}

.control-trend__debug-summary {
    cursor: pointer;
    font-weight: var(--font-weight-semibold);
    color: var(--color-text);
}

    .control-trend__debug-summary:focus-visible {
        outline: 2px solid var(--color-focus-ring);
        outline-offset: 2px;
    }

.control-trend__debug-hint {
    margin: var(--space-3) 0 var(--space-2);
    color: var(--color-text-muted);
    font-size: var(--font-size-sm);
}

/* Full exception detail — wrap long lines and cap the height so a deep
   stack trace scrolls within the card instead of stretching the page. */
.control-trend__debug-detail {
    margin: 0;
    max-height: 24rem;
    overflow: auto;
    padding: var(--space-3);
    border-radius: var(--radius-md);
    background: var(--color-surface);
    border: 1px solid var(--color-border-subtle);
    color: var(--color-text);
    font-family: var(--font-family-mono, ui-monospace, SFMono-Regular, "Cascadia Mono", Menlo, Consolas, monospace);
    font-size: var(--font-size-xs);
    line-height: var(--line-height-snug, 1.4);
    white-space: pre-wrap;
    word-break: break-word;
}

@media (forced-colors: active) {
    .control-trend__debug,
    .control-trend__debug-detail {
        border: 1px solid CanvasText;
    }
}
