/* ── Typeface ─────────────────────────────────────────────────────────────────────────
   Inter, self-hosted as ONE variable file (v4.1, 344 KB, wght 100–900). SIL Open Font
   License 1.1 — free for commercial use, embedding and redistribution.

   Self-hosted because the app previously loaded no font at all: it named 'Segoe UI' and
   relied on that being installed, which is a Windows assumption. On macOS and Linux the
   stack silently fell through to Helvetica or Arial, so the product already rendered in a
   different typeface per machine — invisible only because everyone testing it was on
   Windows. Shipping the file makes rendering deterministic everywhere, which is the whole
   reason for this change; matching the shared reference's typeface is a side effect, not
   the goal.

   ONE file, upright only. The variable font carries every weight the app uses, so there is
   no per-weight request. Italic is deliberately absent: the whole product uses it in one
   place (a "bypasses visibility" label on /settings/users) and the browser's synthesised
   oblique is a fair trade for not shipping a second 344 KB face.

   font-display: swap — the file is served from the same host and cached after the first
   load, so the fallback is visible for at most one paint. */
@font-face {
    font-family: 'Inter';
    font-style: normal;
    font-weight: 100 900;
    font-display: swap;
    src: url('fonts/InterVariable.woff2') format('woff2');
}

:root {
    /* ONE monospace stack for the whole app (2026-08-31). There were five, all meaning
       "code": Bootstrap's --bs-font-monospace, and four hand-written variants across
       JsonCodeBlock, LifecycleBranchesPanel, the two lifecycle assign modals and
       AzureConnect. On Windows they all landed on Consolas, which is why nobody noticed —
       on macOS and Linux they diverge, and one of them leads with ui-monospace while the
       others lead with Consolas. Consolas first because this app's users are largely on
       Windows; ui-monospace picks up the platform's own UI mono elsewhere. */
    --app-font-mono: Consolas, "Cascadia Mono", ui-monospace, SFMono-Regular, Menlo, Monaco, "Liberation Mono", monospace;

    /* The one card elevation of the record drawer's notes/activity timeline. Measured live on
       the shared reference 2026-09-12: every floating surface there (the timeline node
       circles, the note cards, the activity cards and the quick-add card) carries this same
       three-layer shadow and no border. Defined on :root because the components that use it
       are CSS-isolated, and a custom property inherits through the DOM regardless of scope. */
    /* ── Type scale ────────────────────────────────────────────────────────────────────────
       The app ships ONE typeface (Inter, declared once in this file) and every element inherits
       it — no component names a font stack, and a form control that does not inherit says
       `font-family: inherit` rather than repeating the stack. Font SIZE had no such single
       source: it was 40+ literals, and the fractional `rem` ones resolved to values that exist
       nowhere else in the app. At this app's 14px root, `0.68rem` is 9.52px, `0.76rem` is
       10.64px, `0.85rem` is 11.9px and `0.88rem` is 12.32px. Inside ONE panel that put meta text
       at 10.64px beside meta text at 12px, and body text at 12.32px beside body text at 14px —
       gaps too small to read as a hierarchy and too large to look deliberate.

       These six steps are the app's own most-used values, counted rather than invented: 14px is
       the root and the body size (40 declarations), 12px is next (25), then 13px (11), then
       10.5px for badges (9, and the size `--chip-optical-shift` is calibrated for) and 16px for
       titles (7); 23px is the one display size, measured on the shared reference for the
       activity form's subject field.

       `--fs-section` is the odd one out and is kept on purpose: 21px is what `/settings/activities`
       measured on the reference for its section headings, where the hierarchy runs 21 → 16 → 14
       (recorded in `03-features/activities/ui-behaviour.md`, "page-wide typography pass"). The
       app's OWN older settings pages head their sections with `.section-card-title` at 1.05rem
       (14.7px) / 600 instead, so the app currently has two section-heading conventions. That is a
       product decision to reconcile, not a bug to quietly normalise, so both stay and both are
       named here.

       Every Activities surface now sizes text ONLY from these — that area carries no font-size
       literal at all. The rest of the app still does; migrating it is BACKLOG 141. New code uses
       the tokens. */
    --fs-chip: 10.5px;
    --fs-meta: 12px;
    --fs-control: 13px;
    --fs-body: 14px;
    --fs-title: 16px;
    --fs-section: 21px;
    --fs-display: 23px;

    --elev-card: 0 0 1px 1px rgba(0, 0, 0, .05), 0 1px 2px -2px rgba(0, 0, 0, .06), 0 1px 3px 0 rgba(0, 0, 0, .07);

    /* The RAISED elevation, the other half of the reference's two-step hierarchy (measured
       live 2026-09-12). Everything that sits ON the page carries --elev-card: the sticky top
       row, the tabbed composer card, the timeline's cards and node circles. Everything that
       sits OVER the page carries this one: the record detail drawer and every modal. There is
       no third step — a surface that needs to read as lifted takes --elev-overlay, and nothing
       in this app draws its own one-off shadow stack any more. */
    --elev-overlay: 0 0 2px 0 rgba(0, 0, 0, .24), 0 16px 24px 2px rgba(0, 0, 0, .05), 0 6px 30px 5px rgba(0, 0, 0, .06), 0 8px 10px -5px rgba(0, 0, 0, .1);

    /* Bootstrap paints <code>, <kbd>, <pre> and <samp> from its OWN variable, so pointing
       ours at the token is what actually unifies them — replacing only our rules left
       Bootstrap's stack (SFMono first, Consolas fifth) on every <code> in the app,
       measured on /subscriptions/{id}'s Subscription ID and Tenant. */
    --bs-font-monospace: var(--app-font-mono);

    /* Shared corner radius for every anchored popover/dropdown menu (row-action kebabs,
       column panel, grid domain filters, profile menu, permission-set row menus, the
       storage-account label dropdown). Single source of truth so no consumer drifts to
       its own value — see llm-wiki/06-patterns/dropdown-pattern.md "Surface Chrome".
       Does NOT apply to edge-docked flyouts/drawers (those stay radius-less by design)
       or to tooltips (tracked separately, BACKLOG 84). */
    --popover-radius: 8px;

    /* ── Corner radius scale for BUTTONS and button-like controls (2026-08-29) ──────────
       Before this the app had TEN different radii on button-like controls, from three
       independent systems:

         1. Bootstrap's rem-based defaults, which nobody chose and nobody wrote down.
            `html { font-size: 14px }` means 0.375rem renders as 5.25px and 0.25rem as
            3.5px — values that appear nowhere in this codebase yet governed 173 buttons.
         2. Hand-written px in app.css and 20+ scoped stylesheets: 2px, 4px, 6px, 8px, 10px,
            20px, each decided in isolation.
         3. One token, --popover-radius, added 2026-08-27 for exactly this problem on
            popovers ("before this, five different values").

       The scale is deliberately TWO steps, decided by control height:

         --btn-radius     8px  — standard controls, 28px tall and up. Already the app's
                                 de-facto value (grid toolbar, selection bar, notes
                                 toolbar, the permission-set split button) and already
                                 written down in 01-design-system/visual-language.md.
         --btn-radius-sm  6px  — controls UNDER 28px, and menu rows. `.btn-sm` is 27.4px
                                 and the A–Z filter buttons are 27px; 8px on those reads
                                 as disproportionately round.

       --pill-radius is not a third size step — it is semantic. A pill/chip is not a
       button that happens to be rounder; it is a different object, and it stays fully
       round at any height. Circular icon buttons (50%) and the deliberately square-edged
       segmented switch / nav tabs (0) are semantic in the same way and are NOT part of
       this scale.

       See llm-wiki/01-design-system/visual-language.md for the full inventory. */
    --btn-radius: 8px;
    --btn-radius-sm: 6px;
    --pill-radius: 999px;

    /* ── Modal shell radius (2026-08-29, product decision — permanent) ────────────────────
       A ModalForm is DOCKED to an edge of the viewport, and the rule is:

           the docked edge gets NO radius; only the free edge is rounded.

       On desktop `.fm-modal` is `position: fixed; top: 0`, so it is flush with the top of
       the page and only its BOTTOM corners are rounded. Rounding the top would lift the
       shell off the page edge and break the "this sheet is pulled down over the page"
       reading the whole component is built around.

       The same rule flips on mobile: at <=480px the shell docks to `bottom: 0` instead, so
       there the TOP corners are the free edge and carry the radius. Same principle, mirrored
       — it is not an exception.

       This is a standing decision, not a one-off: any new modal, drawer or sheet follows it.
       See llm-wiki/02-components/component-library.md. */
    --modal-radius: 12px;
}

/* Bootstrap bridge — moves all 173 `.btn` instances onto the scale without touching a
   single markup file. Targets --bs-btn-border-radius specifically, NOT --bs-border-radius:
   the latter also drives cards, alerts, inputs, badges and dropdowns, which are a separate
   decision and deliberately stay where they are. Verified in the running app: buttons move,
   those five do not. */
.btn,
.btn-lg { --bs-btn-border-radius: var(--btn-radius); }
.btn-sm { --bs-btn-border-radius: var(--btn-radius-sm); }

html, body {
    /* Inter first, then the old stack as a fallback: if the woff2 ever fails to load the
       app degrades to exactly what it rendered before rather than to a browser default. */
    font-family: Inter, 'Segoe UI', 'Helvetica Neue', Helvetica, Arial, sans-serif;
    font-size: 14px;
}

a, .btn-link {
    color: #006bb7;
}

.btn-primary {
    color: #fff;
    background-color: #1b6ec2;
    border-color: #1861ac;
}

.btn:focus, .btn:active:focus, .btn-link.nav-link:focus, .form-control:focus, .form-check-input:focus {
  box-shadow: 0 0 0 0.1rem white, 0 0 0 0.25rem #258cfb;
}

.content {
    padding-top: 1.1rem;
}

h1:focus {
    outline: none;
}

.valid.modified:not([type=checkbox]) {
    outline: 1px solid #26b050;
}

.invalid {
    outline: 1px solid #e50000;
}

.validation-message {
    color: #e50000;
}

.blazor-error-boundary {
    background: url(data:image/svg+xml;base64,PHN2ZyB3aWR0aD0iNTYiIGhlaWdodD0iNDkiIHhtbG5zPSJodHRwOi8vd3d3LnczLm9yZy8yMDAwL3N2ZyIgeG1sbnM6eGxpbms9Imh0dHA6Ly93d3cudzMub3JnLzE5OTkveGxpbmsiIG92ZXJmbG93PSJoaWRkZW4iPjxkZWZzPjxjbGlwUGF0aCBpZD0iY2xpcDAiPjxyZWN0IHg9IjIzNSIgeT0iNTEiIHdpZHRoPSI1NiIgaGVpZ2h0PSI0OSIvPjwvY2xpcFBhdGg+PC9kZWZzPjxnIGNsaXAtcGF0aD0idXJsKCNjbGlwMCkiIHRyYW5zZm9ybT0idHJhbnNsYXRlKC0yMzUgLTUxKSI+PHBhdGggZD0iTTI2My41MDYgNTFDMjY0LjcxNyA1MSAyNjUuODEzIDUxLjQ4MzcgMjY2LjYwNiA1Mi4yNjU4TDI2Ny4wNTIgNTIuNzk4NyAyNjcuNTM5IDUzLjYyODMgMjkwLjE4NSA5Mi4xODMxIDI5MC41NDUgOTIuNzk1IDI5MC42NTYgOTIuOTk2QzI5MC44NzcgOTMuNTEzIDI5MSA5NC4wODE1IDI5MSA5NC42NzgyIDI5MSA5Ny4wNjUxIDI4OS4wMzggOTkgMjg2LjYxNyA5OUwyNDAuMzgzIDk5QzIzNy45NjMgOTkgMjM2IDk3LjA2NTEgMjM2IDk0LjY3ODIgMjM2IDk0LjM3OTkgMjM2LjAzMSA5NC4wODg2IDIzNi4wODkgOTMuODA3MkwyMzYuMzM4IDkzLjAxNjIgMjM2Ljg1OCA5Mi4xMzE0IDI1OS40NzMgNTMuNjI5NCAyNTkuOTYxIDUyLjc5ODUgMjYwLjQwNyA1Mi4yNjU4QzI2MS4yIDUxLjQ4MzcgMjYyLjI5NiA1MSAyNjMuNTA2IDUxWk0yNjMuNTg2IDY2LjAxODNDMjYwLjczNyA2Ni4wMTgzIDI1OS4zMTMgNjcuMTI0NSAyNTkuMzEzIDY5LjMzNyAyNTkuMzEzIDY5LjYxMDIgMjU5LjMzMiA2OS44NjA4IDI1OS4zNzEgNzAuMDg4N0wyNjEuNzk1IDg0LjAxNjEgMjY1LjM4IDg0LjAxNjEgMjY3LjgyMSA2OS43NDc1QzI2Ny44NiA2OS43MzA5IDI2Ny44NzkgNjkuNTg3NyAyNjcuODc5IDY5LjMxNzkgMjY3Ljg3OSA2Ny4xMTgyIDI2Ni40NDggNjYuMDE4MyAyNjMuNTg2IDY2LjAxODNaTTI2My41NzYgODYuMDU0N0MyNjEuMDQ5IDg2LjA1NDcgMjU5Ljc4NiA4Ny4zMDA1IDI1OS43ODYgODkuNzkyMSAyNTkuNzg2IDkyLjI4MzcgMjYxLjA0OSA5My41Mjk1IDI2My41NzYgOTMuNTI5NSAyNjYuMTE2IDkzLjUyOTUgMjY3LjM4NyA5Mi4yODM3IDI2Ny4zODcgODkuNzkyMSAyNjcuMzg3IDg3LjMwMDUgMjY2LjExNiA4Ni4wNTQ3IDI2My41NzYgODYuMDU0N1oiIGZpbGw9IiNGRkU1MDAiIGZpbGwtcnVsZT0iZXZlbm9kZCIvPjwvZz48L3N2Zz4=) no-repeat 1rem/1.8rem, #b32121;
    padding: 1rem 1rem 1rem 3.7rem;
    color: white;
}

    .blazor-error-boundary::after {
        content: "An error has occurred."
    }

.darker-border-checkbox.form-check-input {
    border-color: #929292;
}

.form-floating > .form-control-plaintext::placeholder, .form-floating > .form-control::placeholder {
    color: var(--bs-secondary-color);
    text-align: end;
}

.form-floating > .form-control-plaintext:focus::placeholder, .form-floating > .form-control:focus::placeholder {
    text-align: start;
}

/* ===== ModalForm input classes ===== */
.fm-input,
.fm-select {
    display: block;
    width: 100%;
    height: 36px;
    padding: 0 10px;
    font-size: 14px;
    font-family: inherit;
    border: 0.5px solid rgba(15, 23, 42, 0.20);
    border-radius: 8px;
    background: #fff;
    color: #212529;
    transition: border-color 120ms ease, box-shadow 120ms ease;
    -webkit-appearance: none;
    appearance: none;
}

.fm-select {
    /* Same caret as <CaretDown />, as a data-URI because a CSS background cannot host a
       component. Path and 16x16 box must stay in sync with Components/CaretDown.razor. */
    background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='16' height='16' viewBox='0 0 16 16'%3E%3Cpath fill='%236c757d' d='M4.2 6h7.6a.5.5 0 0 1 .354.854l-3.8 3.8a.5.5 0 0 1-.708 0l-3.8-3.8A.5.5 0 0 1 4.2 6Z'/%3E%3C/svg%3E");
    background-repeat: no-repeat;
    background-position: right 10px center;
    padding-right: 30px;
}

.fm-input:focus,
.fm-select:focus {
    outline: none;
    border-color: #1b6ec2;
    box-shadow: 0 0 0 3px rgba(27, 110, 194, 0.15);
}

.fm-textarea {
    display: block;
    width: 100%;
    min-height: 80px;
    padding: 8px 10px;
    font-size: 14px;
    font-family: inherit;
    border: 0.5px solid rgba(15, 23, 42, 0.20);
    border-radius: 8px;
    background: #fff;
    color: #212529;
    resize: vertical;
    transition: border-color 120ms ease, box-shadow 120ms ease;
}

.fm-textarea:focus {
    outline: none;
    border-color: #1b6ec2;
    box-shadow: 0 0 0 3px rgba(27, 110, 194, 0.15);
}

.fm-input:disabled,
.fm-select:disabled,
.fm-textarea:disabled {
    background: #f1f3f5;
    color: #6c757d;
    cursor: not-allowed;
}

/* ===== Standalone settings-page sections (Azure connect, Profile, Company settings) =====
   These pages sit outside any DataGrid/ModalForm flow, so they get their own light section
   chrome — global (not page-scoped) so the three pages read as one family instead of each
   inventing its own card look. */
.section-card {
    margin-top: 1.25rem;
    padding: 1.25rem 1.5rem;
    background: #fff;
    border: 1px solid rgba(15, 23, 42, 0.08);
    border-radius: 10px;
    box-shadow: 0 1px 2px rgba(15, 23, 42, 0.04);
}

.section-card-title {
    margin: 0 0 1rem;
    font-size: 1.05rem;
    font-weight: 600;
    color: #1f2937;
}

.section-intro {
    max-width: 62rem;
    margin-top: 0.35rem;
    line-height: 1.55;
}

.fm-divider {
    display: flex;
    align-items: center;
    gap: 0.625rem;
    margin: 0.25rem 0 1rem;
    color: #6c757d;
    font-size: 11px;
    font-weight: 600;
    text-transform: uppercase;
    letter-spacing: 0.06em;
}

.fm-divider::before,
.fm-divider::after {
    content: '';
    flex: 1;
    height: 1px;
    background: rgba(15, 23, 42, 0.10);
}

.fm-divider-label { white-space: nowrap; flex-shrink: 0; }

/* ===== Compact horizontal settings form (Pipedrive company-settings reference, 2026-09-03) =====
   One flat form instead of per-field section cards: right-aligned label column + fixed-width
   control column CENTRED in the content area, helper text under the input, ONE Save for the
   whole form. Measured off the reference (row rhythm 16px, control column originally 480px
   centred — label cell and the empty space right of the input are each exactly (row - width) / 2
   at every tested width, so the label takes flex:1 and the ::after spacer mirrors it regardless
   of the column's fixed width; helper 12px/18px) and mapped onto the app palette/tokens
   (.fm-input 36px, app text/muted colours). Control column widened to 720px (+50%) on
   2026-09-07 per request, still centred by the same flex:1 label/spacer mechanism. Global for
   the same reason .section-card is: the standalone settings pages must read as one family. */
.settings-form {
    margin-top: 0.25rem;
}

.settings-form-row {
    display: flex;
    align-items: flex-start;
    /* the 16px label↔input distance comes from gap, NOT label padding — padding on a
       flex-basis-0 item floors its box at 16px and shifts the centred column sideways;
       gap also lands between the control and the ::after spacer, keeping both sides equal */
    gap: 16px;
    margin-bottom: 16px;
}

/* Right spacer, equal to the label column, so the 480px control column sits centred —
   the reference's actual geometry, not a max-width wrapper. */
.settings-form-row::after {
    content: "";
    flex: 1 1 0;
}

.settings-form-label {
    flex: 1 1 0;
    min-width: 0;
    /* 8px top centres the 20px label line against the 36px .fm-input */
    padding: 8px 0 0;
    text-align: right;
    font-size: 14px;
    line-height: 20px;
    font-weight: 500;
    color: #212529;
}

.settings-form-control {
    flex: 0 0 720px;
    max-width: 100%;
    min-width: 0;
}

.settings-form-help {
    margin-top: 4px;
    font-size: 12px;
    line-height: 18px;
    color: #6c757d;
}

.settings-form-actions {
    margin-top: 8px;
}

/* A locked field: disabled .fm-input showing the current value, pencil button hung 8px
   OUTSIDE the control column (the reference keeps the input at the full 480 and lets the
   button borrow from the empty right half). The button opens a ModalForm that owns the
   actual edit — the page Save never touches locked fields. */
.settings-form-input-group {
    position: relative;
}

.settings-form-edit-btn {
    position: absolute;
    left: calc(100% + 8px);
    top: 0;
    display: inline-flex;
    align-items: center;
    justify-content: center;
    width: 36px;
    height: 36px;
    padding: 0;
    border: 0.5px solid rgba(15, 23, 42, 0.20);
    border-radius: 8px;
    background: #fff;
    color: #495057;
    cursor: pointer;
    transition: background 120ms ease, border-color 120ms ease;
}

.settings-form-edit-btn:hover:not(:disabled) {
    background: #f8f9fa;
    border-color: rgba(15, 23, 42, 0.35);
}

.settings-form-edit-btn:disabled {
    opacity: 0.55;
    cursor: not-allowed;
}

@media (max-width: 768px) {
    .settings-form-row {
        flex-direction: column;
        gap: 4px;
    }

    .settings-form-row::after {
        content: none;
    }

    .settings-form-label {
        flex: none;
        padding: 0;
        text-align: left;
    }

    .settings-form-control {
        flex: none;
        width: 100%;
    }

    /* No empty right half to borrow from — the pencil tucks inside the row instead. */
    .settings-form-input-group {
        display: flex;
        align-items: center;
        gap: 8px;
    }

    .settings-form-input-group .fm-input {
        flex: 1 1 auto;
        min-width: 0;
    }

    .settings-form-edit-btn {
        position: static;
        flex: 0 0 auto;
    }
}

/* Branding rows (Company Settings -> General): thumbnail + Upload/Replace + Remove, one
   row per uploaded asset kind. The thumbnail is sized per kind so it previews at roughly
   the box it actually renders in (nav rail tile / login header mark / browser tab icon) —
   object-fit: contain, same as every consuming surface, so the preview is honest. */
.settings-branding-row {
    display: flex;
    align-items: center;
    gap: 12px;
}

.settings-branding-thumb {
    display: inline-flex;
    object-fit: contain;
    border-radius: 6px;
    border: 1px solid rgba(15, 23, 42, 0.12);
    background: #f8f9fa;
    flex-shrink: 0;
}

.settings-branding-thumb-nav {
    width: 28px;
    height: 28px;
}

.settings-branding-thumb-login {
    height: 32px;
    max-width: 160px;
}

.settings-branding-thumb-favicon {
    width: 20px;
    height: 20px;
}

.settings-branding-upload {
    margin: 0;
}

.settings-branding-upload-disabled {
    opacity: 0.55;
    pointer-events: none;
}

/* ===== ModalForm button classes =====
   Global for the same reason .fm-input/.fm-select/.fm-textarea above are: a custom
   <Footer> is markup written in the PAGE's own .razor file, so it carries that page's
   CSS-isolation scope attribute (or none), never ModalForm's — a scoped .fm-btn rule in
   ModalForm.razor.css silently never matches it, rendering an unstyled native button
   (reported 2026-08-16 — the Assignments modal's Close button rendered as a bare
   browser-default button). ModalForm's own default footer buttons are styled by this same
   global rule; nothing button-related remains in ModalForm.razor.css. */
.fm-btn {
    display: inline-flex;
    align-items: center;
    gap: 6px;
    height: 34px;
    padding: 0 1rem;
    border-radius: var(--btn-radius);
    font-size: 13px;
    font-weight: 500;
    cursor: pointer;
    border: 1px solid transparent;
    transition: background 120ms ease, border-color 120ms ease;
    white-space: nowrap;
    font-family: inherit;
}

.fm-btn:disabled {
    opacity: 0.55;
    cursor: not-allowed;
}

.fm-btn-primary {
    background: #1b6ec2;
    color: #fff;
    border-color: #1861ac;
}

.fm-btn-primary:hover:not(:disabled) {
    background: #1861ac;
}

.fm-btn-secondary {
    background: transparent;
    color: #495057;
    border-color: #ced4da;
}

/* Destructive primary, for ConfirmDeleteModal. Filled red and LAST in the footer — the
   reference puts the destructive button on the right, where the eye and the pointer finish,
   with the neutral Cancel before it. Our own danger red (#dc3545 / #b02a37 hover), not the
   reference's: structure and placement carry over from it, palette does not. */
.fm-btn-danger {
    background: #dc3545;
    color: #fff;
    border-color: #dc3545;
}

.fm-btn-danger:hover:not(:disabled) {
    background: #b02a37;
    border-color: #b02a37;
}

/* Consequence sentence: the thing someone actually reads before destroying something, so it
   gets normal body weight rather than muted small print. */
.fm-confirm-body {
    margin: 0;
    font-size: 13px;
    color: #212529;
}

/* The reassuring / recoverable fact. Tinted accent box, same family as .gf-btn--active's
   #eff6ff — deliberately NOT red: it is the calming half of the dialog, and colouring it as a
   second warning would flatten the difference between "this is what happens" and "this is why
   it is survivable". */
.fm-confirm-note {
    display: flex;
    align-items: flex-start;
    gap: 8px;
    margin-top: 12px;
    padding: 10px 12px;
    border: 1px solid #bfdbfe;
    border-radius: var(--btn-radius);
    background: #eff6ff;
    color: #1e293b;
    font-size: 12.5px;
    line-height: 1.45;
}

.fm-confirm-note svg {
    flex-shrink: 0;
    margin-top: 1px;
    color: #0d6efd;
}

.fm-btn-secondary:hover:not(:disabled) {
    background: rgba(15, 23, 42, 0.04);
    border-color: #adb5bd;
}

.fm-spinner {
    display: inline-block;
    width: 13px;
    height: 13px;
    border: 2px solid rgba(255, 255, 255, 0.35);
    border-top-color: #fff;
    border-radius: 50%;
    animation: fm-spin 600ms linear infinite;
    flex-shrink: 0;
}

@keyframes fm-spin {
    to { transform: rotate(360deg); }
}

/* ===== Nav tabs — thin blue active indicator ===== */
.nav-tabs .nav-link {
    border-top: none;
    border-left: none;
    border-right: none;
    border-bottom: 2px solid transparent;
    border-radius: 0;
    margin-bottom: -1px;
    color: #6c757d;
    background: transparent;
    transition: color 120ms ease, border-color 120ms ease;
}

.nav-tabs .nav-link:hover:not(.active) {
    color: #495057;
    border-bottom-color: #dee2e6;
}

.nav-tabs .nav-link.active,
.nav-tabs .nav-item.show .nav-link {
    color: #1b6ec2;
    background: transparent;
    border-top: none;
    border-left: none;
    border-right: none;
    border-bottom: 2px solid #1b6ec2;
}

/* A "Coming soon" tab (SubscriptionTabs, 2026-09-17): a span, not a link — the same muted grey
   and inert look as the customer panel's placeholder rows it replaced. Deliberately NOT
   Bootstrap's .disabled, whose pointer-events:none would also kill the "Coming soon" title. */
.nav-tabs .nav-link.nav-link--soon,
.nav-tabs .nav-link.nav-link--soon:hover {
    color: #a3a6ad;
    border-bottom-color: transparent;
    cursor: default;
    user-select: none;
}

/* Secondary/meta text at a FIXED size. Bootstrap's own `.small` is `font-size: .875em` —
   relative to the PARENT — so the same class renders at a different size depending on where it
   sits: 12.25px under the 14px body, but 10.72px inside a `.dg-popover` (which sets 0.875rem),
   which is what it was doing to a group label in the activity type picker. This is the fixed
   equivalent, and it is the class to reach for instead of `.small`. */
.text-meta {
    font-size: var(--fs-meta);
}

/* A compact alert. Eleven places wrote `class="alert … py-2" style="font-size:13px"` inline,
   which is a font size outside the one place font is managed — this class is that size, named.
   The two Activities occurrences use it; the nine in Lifecycle are BACKLOG 141. */
.alert-compact {
    font-size: var(--fs-control);
}

/* ===== Anchored popover chrome (DismissiblePanel consumers: DataGrid row menu +
   column panel, Visibility Groups row menus) =====
   Global because the panel content is rendered by DismissiblePanel, which never carries
   the consuming component's scoped-CSS attribute. Popovers are positioned position:fixed
   by profile-dropdown.js, so they need a z-index from the app's global scale — 1100 sits
   above ordinary page chrome and BELOW both the breadcrumb tooltip (1200) and ModalForm
   (1300/1301, raised there on 2026-09-01 so a modal opened FROM a popover is not covered by
   the popover that spawned it). The typography reset matters because position:fixed escapes
   ancestor clipping but NOT CSS inheritance. */
.dg-popover {
    z-index: 1100;
    min-width: 180px;
    max-width: 320px;
    padding: 0.35rem;
    background: #fff;
    border: 1px solid #dbe2ea;
    border-radius: var(--popover-radius);
    box-shadow: 0 10px 28px rgba(15, 23, 42, 0.18), 0 2px 6px rgba(15, 23, 42, 0.08);
    text-transform: none;
    letter-spacing: normal;
    font-weight: 400;
    font-size: 0.875rem;
    white-space: normal;
    color: #1e293b;
    text-align: left;
}

/* ===== DataGrid row-action menu items =====
   Global (not CSS-isolated) because these classes are applied to markup authored in each
   consuming page's DataGridColumn.Cell, which renders inside DataGrid's popover — it does
   not carry DataGrid.razor's scoped-CSS attribute. */
.dg-menu-item {
    display: flex;
    align-items: center;
    gap: 0.5rem;
    width: 100%;
    padding: 0.4rem 0.75rem;
    border: 0;
    background: transparent;
    text-align: left;
    font-size: 0.875rem;
    color: #1e293b;
    text-decoration: none;
    white-space: nowrap;
    border-radius: var(--btn-radius-sm);
}

.dg-menu-item:hover:not(:disabled),
.dg-menu-item:focus-visible:not(:disabled) {
    background-color: #f1f5f9;
    color: #1e293b;
    text-decoration: none;
}

.dg-menu-item:disabled {
    color: #94a3b8;
    cursor: not-allowed;
}

.dg-menu-item--danger {
    color: #b02a37;
}

.dg-menu-item--active {
    background-color: #eef2f7;
    font-weight: 600;
}

.dg-menu-item--danger:hover:not(:disabled),
.dg-menu-item--danger:focus-visible:not(:disabled) {
    background-color: #fdecea;
    color: #b02a37;
}

.dg-menu-divider {
    height: 1px;
    margin: 0.25rem 0.5rem;
    background: rgba(15, 23, 42, 0.10);
}

.dg-menu-hint {
    padding: 0.3rem 0.75rem;
    font-size: 0.75rem;
    color: #64748b;
    white-space: normal;
}

/* ===== Selection-bar action buttons =====
   GLOBAL for the same reason as .dg-menu-item: the buttons are authored by the PAGE
   (inside DataGrid's SelectionActions fragment), so DataGrid's scoped CSS can never
   reach them. Chrome mirrors the toolbar control family (.gf-btn: 32px, 8px radius,
   #cbd5e1 border) with a leading icon; the icon-only variant is the reference-style
   minimal destructive action (borderless round ghost — the count lives in the
   "N selected" pill and the button's title/aria-label, not its face). */
.dg-selectbar-btn {
    display: inline-flex;
    align-items: center;
    gap: 0.4rem;
    height: 32px;
    padding: 0 0.65rem;
    border: 1px solid #cbd5e1;
    border-radius: var(--btn-radius);
    background: #fff;
    color: #1e293b;
    font-size: 0.875rem;
    white-space: nowrap;
    cursor: pointer;
}

.dg-selectbar-btn:hover:not(:disabled) {
    background: #f1f5f9;
}

.dg-selectbar-btn:disabled {
    color: #94a3b8;
    border-color: #dbe2ea;
    cursor: default;
}

.dg-selectbar-btn svg {
    flex-shrink: 0;
    color: #64748b;
}

.dg-selectbar-btn:disabled svg {
    color: #b6bcc8;
}

/* Destructive variant of the LABELLED selection-bar button — used when a destructive action
   sits alongside non-destructive siblings and has to read as one of them (same size, same
   count in its face), which the icon-only ghost above cannot do. */
.dg-selectbar-btn--danger:hover:not(:disabled),
.dg-selectbar-btn--danger:focus-visible:not(:disabled) {
    color: #b02a37;
    border-color: #e6b3b9;
    background: rgba(176, 42, 55, 0.08);
}

.dg-selectbar-btn--danger:hover:not(:disabled) svg {
    color: #b02a37;
}

/* Icon-only variant: same button chrome as .dg-selectbar-btn (bordered, 8px radius —
   it must still read as a BUTTON, not a floating glyph), square. */
.dg-selectbar-icon-btn {
    display: inline-flex;
    align-items: center;
    justify-content: center;
    width: 32px;
    height: 32px;
    padding: 0;
    border: 1px solid #cbd5e1;
    border-radius: var(--btn-radius);
    background: #fff;
    color: #3f4450;
    cursor: pointer;
}

.dg-selectbar-icon-btn:hover:not(:disabled),
.dg-selectbar-icon-btn:focus-visible:not(:disabled) {
    background: #f1f5f9;
}

.dg-selectbar-icon-btn--danger:hover:not(:disabled),
.dg-selectbar-icon-btn--danger:focus-visible:not(:disabled) {
    color: #b02a37;
    background: rgba(176, 42, 55, 0.08);
}

.dg-selectbar-icon-btn:disabled {
    color: #b6bcc8;
    border-color: #dbe2ea;
    cursor: default;
}


/* ===== Grid-toolbar filter pattern (LabelFilter / OwnerFilter) =====
   Toolbar trigger buttons + searchable selection popovers for grid list pages.
   Global for the same two reasons as .dg-popover and the .fm-btn family above: the
   popover content renders inside DismissiblePanel (which never carries a consumer's
   scoped-CSS attribute), and two components share every class here. Contract:
   llm-wiki/06-patterns/grid-toolbar-filter-pattern.md. */
/* No gap: when the clear (x) segment renders next to an active single-select trigger,
   the two must fuse into one segmented control (reference behaviour) — the shared edge
   between them is the divider, not whitespace. */
.gf-btn-group {
    display: inline-flex;
    align-items: center;
    gap: 0;
}

.gf-btn {
    display: inline-flex;
    align-items: center;
    gap: 0.4rem;
    height: 32px;
    padding: 0 0.65rem;
    border: 1px solid #cbd5e1;
    border-radius: var(--btn-radius);
    background: #fff;
    color: #1e293b;
    font-size: 0.875rem;
    white-space: nowrap;
    cursor: pointer;
}

.gf-btn:hover {
    background: #f1f5f9;
}

/* Icon-only square variant, for a toolbar action whose glyph says it all — measured off the
   reference's selection-toolbar delete: 32px, 8px radius, white, hairline border, and
   NEUTRAL. The destructive intent is carried by the trash glyph and by the confirmation that
   follows, not by a red button. A red control in a toolbar shouts before the user has done
   anything; the dialog is where the shouting belongs. Give it a title/aria-label — an
   unlabelled icon is only acceptable when hovering explains it. */
.gf-btn--icon {
    width: 32px;
    padding: 0;
    justify-content: center;
}

/* No hover reddening here either — same reasoning as .gf-trash. It inherits .gf-btn's plain
   #f1f5f9 hover, so this modifier needs no hover rule of its own; one was added on 2026-09-01
   and removed the same day. */

.gf-btn svg {
    flex-shrink: 0;
}

/* Active = any selection applied: accent border over a light accent tint, regular dark
   text (the tint carries the "active" signal — reference behaviour). */
.gf-btn--active {
    border-color: #0d6efd;
    background: #eff6ff;
    color: #1e293b;
}

.gf-btn--active:hover {
    background: #e2edff;
}

/* Single-select active trigger fused with its clear (x) segment: square off the shared
   corner so the pair reads as one control. */
.gf-btn--split {
    border-top-right-radius: 0;
    border-bottom-right-radius: 0;
}

/* Single selected label shown inside the trigger. It is the ordinary .label-pill (2026-09-16 — it
   was a fully rounded pill of its own until then, so a label changed shape between the list and
   the trigger); this only caps its width in the toolbar. */
.gf-btn-pill {
    max-width: 9rem;
}

/* OwnerFilter's clear (x) — the second segment of the active trigger's fused control:
   no left border (the shared edge with the trigger is the divider), right corners carry
   the group's radius, same tint + accent border as the active trigger. */
.gf-btn-clear {
    display: inline-flex;
    align-items: center;
    justify-content: center;
    width: 33px;
    height: 32px;
    padding: 0;
    border: 1px solid #0d6efd;
    border-left: 0;
    border-radius: 0 8px 8px 0;
    background: #eff6ff;
    color: #475569;
    cursor: pointer;
}

.gf-btn-clear:hover,
.gf-btn-clear:focus-visible {
    background: #e2edff;
    color: #1e293b;
}

/* Popover body. */
.gf-popover {
    width: 330px;
    max-width: calc(100vw - 16px);
    padding: 0;
    overflow: hidden;
}

/* The label form is WIDER than the list, as on the reference (352 against 334): ten 32px swatches
   plus 16px either side. The three panels a label popover lives in are all 330px — LabelFilter's
   .gf-popover, DataGrid's scoped .dg-cell-editor and the drawer's .rd-editor — and the scoped one
   loads after this file, hence the :is() list for specificity rather than a lone .dg-popover.
   The panel grows while the form shows and shrinks back with the list; profile-dropdown.js
   re-positions a panel whose size changes, so a bottom-end panel keeps its right edge.

   Until 2026-09-16 the form slid in beside the list inside a 200%-wide track, which needed both
   views at one width; the reference swaps them, and so does this app now. */
:is(.gf-popover, .dg-cell-editor, .rd-editor).dg-popover:has(.gf-edit) {
    width: 352px;
}

/* Search box — mirror of DataGrid's column-panel search (.dg-cp-search family). */
.gf-search-wrap {
    position: relative;
    padding: 0.6rem 0.6rem 0.45rem;
}

.gf-search-icon {
    position: absolute;
    left: calc(0.6rem + 10px);
    top: calc(0.6rem + 9px);
    width: 14px;
    height: 14px;
    color: #64748b;
    pointer-events: none;
}

.gf-search {
    width: 100%;
    height: 32px;
    padding: 0 32px 0 32px;
    font-size: 0.85rem;
    color: #1e293b;
    background: #fff;
    border: 1px solid #cbd5e1;
    border-radius: 8px;
    outline: none;
}

.gf-search:focus {
    border-color: #0d6efd;
    box-shadow: 0 0 0 3px rgba(13, 110, 253, 0.15);
}

.gf-search-clear {
    position: absolute;
    right: calc(0.6rem + 6px);
    top: calc(0.6rem + 6px);
    display: inline-flex;
    align-items: center;
    justify-content: center;
    width: 20px;
    height: 20px;
    padding: 0;
    border: none;
    border-radius: 50%;
    background: transparent;
    color: #64748b;
    cursor: pointer;
}

.gf-search-clear:hover {
    background: rgba(15, 23, 42, 0.06);
    color: #1e293b;
}

.gf-list {
    max-height: 320px;
    overflow-y: auto;
    overscroll-behavior: contain;
    padding: 0.15rem 0;
}

/* The ONE place the list is shortened: a label popover opened from a grid cell's pencil.
   Measured on the shared reference at ~178px against our 320 — about five and a half 32px
   rows, and the half row is the point: it says "there is more, scroll" without the popover
   growing tall enough to cover the rows the user is comparing against.

   Scoped by .dg-cell-editor, which DataGrid puts on the DismissiblePanel that hosts an
   inline cell editor and nothing else puts anywhere. That is why this needed no --compact
   modifier and no parameter on LabelAssignPopover: the same component keeps the roomy 320px
   everywhere it is NOT inside a grid cell, including on the detail pages when they adopt it
   (BACKLOG 98). It is also why the six other .gf-list consumers are untouched — an owner list
   of forty users and a container picker were never measured against the reference and have no
   reason to lose half their height (BACKLOG 94: option 3, narrowed to the one surface where
   the comparison actually applies).

   Global rather than component-scoped because the two halves are written in different files:
   .dg-cell-editor comes from DataGrid, .gf-list from LabelAssignPopover, and a scoped rule
   would match neither pairing. */
.dg-cell-editor .gf-list {
    max-height: 178px;
}

/* The one EXCEPTION to the 178px cap above: InlineVisibilityEditPopover's list is always
   exactly 4 rows (VisibilityLevel is a fixed enum, not an open-ended catalog) — measured live
   on the reference (2026-09-08): its own equivalent list never scrolls either, the panel just
   grows to fit all 4 (321px of rows in a 333px panel, no overflow). Capping it at 178px would
   force a scrollbar the reference never has and hide options the user needs to compare. Opt-in
   modifier rather than excluding InlineOwnerEditPopover too — an owner list can genuinely run
   long and keeps the cap on purpose (BACKLOG 94's original reasoning). */
.dg-cell-editor .gf-list--fit {
    max-height: none;
}

/* Selection rows: label pill or owner name left, check (persists while selected) and
   pencil (hover-only) right. Hover floods the row with the accent blue like the
   column panel's rows do. */
.gf-row {
    display: flex;
    align-items: center;
    justify-content: space-between;
    gap: 0.5rem;
    min-height: 32px;
    margin: 0 0.35rem;
    padding: 0.2rem 0.55rem;
    border-radius: 8px;
    font-size: 0.875rem;
    color: #1e293b;
    cursor: pointer;
    user-select: none;
}

.gf-row:hover {
    background: #0d6efd;
    color: #fff;
}

.gf-row-label {
    overflow: hidden;
    text-overflow: ellipsis;
    white-space: nowrap;
}

.gf-row-icons {
    display: inline-flex;
    align-items: center;
    gap: 0.25rem;
    flex-shrink: 0;
}

.gf-row-check {
    display: inline-flex;
    color: #0d6efd;
}

.gf-row:hover .gf-row-check {
    color: #fff;
}

/* Stacked rows — a title line plus a muted one/two-line explanation underneath (2026-09-08,
   reference-matched: a CRM's own visibility-level picker shows the same shape). Opt-in modifier
   on .gf-row rather than the default, since every other .gf-list consumer (owner names, label
   pills, plain filter values) is single-line and must keep vertically centering against the
   check icon. First consumer: InlineVisibilityEditPopover's 4 fixed options. */
.gf-row--stacked {
    align-items: flex-start;
    padding-top: 0.4rem;
    padding-bottom: 0.4rem;
}

.gf-row-text {
    display: flex;
    flex-direction: column;
    gap: 2px;
    min-width: 0;
}

.gf-row-description {
    font-size: 12px;
    line-height: 1.4;
    color: #667085;
    white-space: normal;
}

.gf-row--stacked:hover .gf-row-description {
    color: rgba(255, 255, 255, 0.85);
}

.gf-row--stacked .gf-row-icons {
    padding-top: 0.15rem;
}

/* Add-to-favorites star on ToolbarSelect rows (2026-09-02, reference-matched: outline star
   appears on row hover — white on the blue flood — and the favorited row keeps a filled
   GOLD star at all times, hover or not; the gold survives the flood on purpose, it is the
   one bit of the row that stays itself). Opacity, not display: the lane must not shift when
   the star appears next to the check. */
.gf-row-star {
    display: inline-flex;
    align-items: center;
    justify-content: center;
    width: 20px;
    height: 20px;
    padding: 0;
    border: 0;
    background: transparent;
    border-radius: 4px;
    color: #94a3b8;
    cursor: pointer;
    opacity: 0;
}

.gf-row:hover .gf-row-star,
.gf-row-star:focus-visible {
    opacity: 1;
    color: #fff;
}

.gf-row-star--on,
.gf-row:hover .gf-row-star--on {
    opacity: 1;
    color: #f0a30a;
}

/* A label row's drag handle and pencil (2026-09-16, reference-matched: two 24×24 ghost buttons,
   6px corners, white on the flooded row, in that order, then the check). The reference keeps both
   in the row at all times and only makes them visible while the row is hovered, so they hold their
   place and the check never shifts — hence opacity, not display. That also keeps them reachable
   from the keyboard: a focused handle or pencil shows itself, dark, since there is no flood. */
.gf-row-drag,
.gf-row-edit {
    display: inline-flex;
    align-items: center;
    justify-content: center;
    width: 24px;
    height: 24px;
    padding: 0;
    border: none;
    border-radius: var(--btn-radius-sm);
    background: transparent;
    color: #fff;
    cursor: pointer;
    opacity: 0;
}

.gf-row-drag {
    cursor: grab;
}

.gf-row:hover .gf-row-drag,
.gf-row:hover .gf-row-edit,
.gf-row-drag:focus-visible,
.gf-row-edit:focus-visible {
    opacity: 1;
}

.gf-row:not(:hover) .gf-row-drag:focus-visible,
.gf-row:not(:hover) .gf-row-edit:focus-visible {
    color: #1e293b;
}

.gf-row-drag:hover,
.gf-row-edit:hover {
    background: rgba(255, 255, 255, 0.22);
}

.gf-row-drag[aria-disabled="true"] {
    cursor: default;
}

.gf-row:hover .gf-row-drag[aria-disabled="true"] {
    opacity: 0.6;
}

/* Reordering by drag: the dragged row fades, and a 2px line marks where it will land — below the
   row it is over when moving down, above it when moving up (LabelReorder.DropClass). An inset
   shadow rather than a border, so no row changes height mid-drag. */
.gf-row--dragging {
    opacity: 0.4;
}

.gf-row--drop-before {
    box-shadow: inset 0 2px 0 #0d6efd;
}

.gf-row--drop-after {
    box-shadow: inset 0 -2px 0 #0d6efd;
}

/* ── Label pills ──────────────────────────────────────────────────────────────────────
   ONE pill for every label rendering (2026-09-16): the label popovers' rows, LabelFilter's
   trigger, LabelPillCell in grid cells and the drawer, and label-pills.js' hover preview. Until
   then there were three — .gf-pill, a fully rounded .gf-btn-pill and a Bootstrap .badge — at three
   sizes. Geometry measured on the reference CRM's own label: 16px tall, 0 4px padding, 6px
   corners, 600 weight; --fs-chip (10.5px) stands for its 10px, as the calendar already records.
   inline-block, not inline-flex, so a pill truncated by LabelPillCell still ends in an ellipsis.

   Colour is inline, always through LabelPalette.PillStyle: the background AND the text colour,
   which is dark on the light palette colours and white on the dark ones.

   A label ALWAYS displays uppercase, whatever casing it was typed and stored in (product
   decision 2026-08-29). This is `text-transform`, so it is display-only: the stored Name
   keeps the author's casing, search and sort keep working on it, and the edit input still
   shows what was actually typed. Do NOT uppercase on save — that would destroy the original
   and make the rule irreversible.

   Optical centring: the text sits in a 15px line under 1px of top padding instead of a 16px line
   (same 16px box). Measured 2026-09-16 on the optical-centring method below: the plain 16px line
   put the cap band +0.85px HIGH at zoom 1.0 and +0.60px at 0.8; shifted it measures −0.15px and
   −0.40px, and no zoom tried is worse than 0.40px either way.

   .count-chip shares the geometry and nothing else: a small status count beside a number in a grid
   cell ("1 overdue" in a storage-accounts grid's To do column, 2026-09-17), coloured by a Bootstrap
   text-bg-* class and NOT uppercased — it is not a label. It was a Bootstrap .badge with an inline
   font-size:10px, which the .badge optical correction (calibrated at 10.5px) pushed 1.1–1.35px LOW;
   on this geometry it measures −0.17px at zoom 1.0 and −0.42px at 0.8. */
.label-pill,
.count-chip {
    display: inline-block;
    height: 16px;
    padding: 1px 4px 0;
    border-radius: var(--btn-radius-sm);
    font-size: var(--fs-chip);
    font-weight: 600;
    line-height: 15px;
    white-space: nowrap;
    overflow: hidden;
    text-overflow: ellipsis;
    max-width: 100%;
    vertical-align: middle;
}

.label-pill {
    text-transform: uppercase;
}

/* The Cancel/Save row at the foot of a staged popover. It carried a second, wrong name —
   `.sa-label-actions`, where `sa-` meant "storage account" and stopped being true the moment a
   second page adopted it. The alias went with the three detail-page label panels on
   2026-08-31; `.gf-actions` is now the only name. */
/* The inline TEXT editor popover body (InlineTextEditPopover, 2026-09-01) — label +
   input padding matching the label popover's rhythm. Global for the same reason as every
   .gf-* class: the markup renders inside the DataGrid's panel, never under its own scope. */
.gf-inline-text {
    padding: 0.6rem 0.6rem 0.2rem;
    min-width: 260px;
}

.gf-inline-text-label {
    display: block;
    font-size: 0.75rem;
    color: #64748b;
    margin-bottom: 4px;
}

/* Two fields side by side in one inline editor — the activity list's Due date editor (date and
   time, 2026-09-19). The reference's own is 332px wide with two equal fields; --wide gives ours
   room for both without squeezing the date. */
.gf-inline-text--wide {
    min-width: 300px;
}

.gf-inline-pair {
    display: flex;
    gap: 8px;
}

.gf-inline-pair > .fm-input,
.gf-inline-date {
    flex: 1 1 0;
    min-width: 0;
}

.gf-inline-date {
    position: relative;
}

/* The browser's date picker behind a typed date field: present so showPicker() has something to
   open, invisible and unclickable otherwise. The activity form's .ac-date-native, which is scoped
   to the form and so cannot reach an editor rendered inside a grid's panel. */
.gf-date-native {
    position: absolute;
    left: 0;
    bottom: 0;
    width: 100%;
    height: 1px;
    padding: 0;
    border: 0;
    opacity: 0;
    pointer-events: none;
}

/* An unreadable typed value: the activity form's red (.ac-input--invalid), for fields outside it. */
.fm-input.gf-input--invalid,
.fm-input.gf-input--invalid:focus {
    border-color: #dc3545;
    box-shadow: 0 0 0 3px rgba(220, 53, 69, .12);
}

/* The closed trigger InlineVisibilityEditPopover shows before its option list opens
   (2026-09-08, reference-matched: a CRM's own visibility picker is a closed select that opens
   a picker on click, not a bare list). Plain .fm-select styling on a <button> — a button can
   carry that class exactly as a <select> can, so this needed no new visual, only the two
   button-vs-select resets .fm-select doesn't already cover. The activity list's inline Priority
   editor (InlinePriorityEditPopover, 2026-09-19) uses the same trigger. */
.gf-visibility-trigger {
    text-align: left;
    cursor: pointer;
}

.gf-visibility-trigger:disabled {
    cursor: not-allowed;
}

.gf-actions {
    display: flex;
    justify-content: flex-end;
    gap: 8px;
    padding: 0.6rem 0.6rem;
}

.gf-actions .fm-btn {
    height: 32px;
    padding: 0 0.85rem;
    font-size: 13px;
}

/* ONE separator in the app’s popovers: 1px, INSET 0.5rem from each edge so it reads as a
   rule between groups rather than a panel border. Product decision 2026-08-29 — the shared
   reference runs its separators edge to edge, and this app deliberately does not.

   Before this there were two mechanisms at once: this class, and .gf-footer-add drawing its
   OWN full-width border-top in a different colour, so the line under “All labels” and the one
   above “+ Add new label” did not match — and the storage-account dropdown, which already put
   a .gf-divider before that button, stacked both and drew two lines. .gf-footer-add no longer
   carries a border; put a .gf-divider before it instead. */
.gf-divider {
    height: 1px;
    margin: 0.25rem 0.5rem;
    background: rgba(15, 23, 42, 0.10);
}

/* "Add label" row under a label list (2026-09-16, reference-matched): a list row of its own — the
   same inset, radius and height as .gf-row, a plus icon, the accent text — that floods blue on
   hover exactly as the label rows above it do. It was a full-width text button with a grey hover
   until then. */
.gf-footer-add {
    display: flex;
    align-items: center;
    gap: 8px;
    width: calc(100% - 0.7rem);
    min-height: 32px;
    margin: 0 0.35rem 0.25rem;
    padding: 6px 8px;
    border: none;
    border-radius: 8px;
    background: transparent;
    color: #0d68c5;
    font-size: var(--fs-body);
    text-align: left;
    cursor: pointer;
}

.gf-footer-add:hover:not(:disabled) {
    background: #0d6efd;
    color: #fff;
}

.gf-footer-add:disabled {
    cursor: default;
    opacity: 0.6;
}

/* ── The "New label" / "Edit label" form (LabelEditForm) ─────────────────────────────────
   Measured on the reference's own label form (2026-09-16): a 48px title row, a 16px-padded body
   with dark 14px field labels over a plain 32px input, ten 32px swatches, and a 48px footer.
   Separators stay this app's inset .gf-divider rule (decision 2026-08-29), which is why the
   header and footer lines are inset while the reference runs them edge to edge. The panel is
   352px wide while the form shows (.gf-popover rule above). */
.gf-edit-header {
    display: flex;
    align-items: center;
    min-height: 48px;
    padding: 12px 8px;
    margin: 0 0.5rem;
    border-bottom: 1px solid rgba(15, 23, 42, 0.10);
}

.gf-edit-title {
    font-size: var(--fs-title);
    font-weight: 600;
    line-height: 24px;
    color: #1e293b;
}

/* 15px, not the reference's 16: the panel draws a 1px border the reference does not, so the
   body still starts 16px in from the outer edge and the ten 32px swatches fit its 320px exactly. */
.gf-edit-body {
    padding: 16px 15px;
}

.gf-field-label {
    display: block;
    margin-bottom: 4px;
    font-size: var(--fs-body);
    line-height: 21px;
    color: #1e293b;
}

.gf-field-label--spaced {
    margin-top: 16px;
}

/* The name field. Its own class: it used to borrow .gf-search, whose 32px left and right padding
   makes room for a search icon and a clear button this field does not have. */
.gf-input {
    width: 100%;
    height: 32px;
    padding: 4px 8px;
    font-size: var(--fs-body);
    color: #1e293b;
    background: #fff;
    border: 1px solid #cbd5e1;
    border-radius: 8px;
    outline: none;
}

.gf-input:focus {
    border-color: #0d6efd;
    box-shadow: 0 0 0 3px rgba(13, 110, 253, 0.15);
}

/* Ten swatches, one row, no gaps: each is a 32px button whose background is clipped to its
   content box, so the visible colour is a 24px dot with 4px of free space around it. That free
   space is where the selected swatch's halo shows — a lighter tint of the same colour (--label-halo)
   drawn by ::before BEHIND the dot, growing from nothing when the swatch is picked. isolation keeps
   that z-index:-1 halo above the popover's own background. */
.gf-swatches {
    display: flex;
    flex-wrap: nowrap;
    gap: 0;
    isolation: isolate;
}

.gf-swatch {
    position: relative;
    display: inline-flex;
    align-items: center;
    justify-content: center;
    flex-shrink: 0;
    width: 32px;
    height: 32px;
    padding: 4px;
    border: none;
    border-radius: 50%;
    background: var(--label-color);
    background-clip: content-box;
    color: var(--label-text);
    cursor: pointer;
}

.gf-swatch::before {
    content: "";
    position: absolute;
    inset: 0;
    z-index: -1;
    border-radius: 50%;
    background: var(--label-halo);
    transform: scale(0);
    transition: transform 0.2s;
}

.gf-swatch.selected {
    cursor: default;
}

.gf-swatch.selected::before {
    transform: scale(1);
    transition-duration: 0.3s;
}

.gf-swatch:focus-visible {
    outline: 2px solid #0d6efd;
    outline-offset: 0;
}

@media (prefers-reduced-motion: reduce) {
    .gf-swatch::before {
        transition: none;
    }
}

.gf-edit-body .gf-error {
    padding: 8px 0 0;
}

/* The tooltip a swatch and the trash carry (2026-09-16, reference-matched: dark, 26px, 8px corners,
   centred above the control). CSS-only — it lives inside its button and shows on hover or keyboard
   focus. aria-hidden in the markup: the button's own aria-label already says the same thing. */
.gf-tip {
    position: absolute;
    bottom: calc(100% + 6px);
    left: 50%;
    z-index: 1;
    display: flex;
    align-items: center;
    height: 26px;
    padding: 0 8px;
    border-radius: 8px;
    background: #21232c;
    color: #fff;
    font-size: var(--fs-meta);
    font-weight: 400;
    line-height: 1;
    white-space: nowrap;
    pointer-events: none;
    opacity: 0;
    transform: translateX(-50%);
    transition: opacity 120ms ease-out;
}

:is(.gf-swatch, .gf-trash):hover .gf-tip,
:is(.gf-swatch, .gf-trash):focus-visible .gf-tip {
    opacity: 1;
}

@media (prefers-reduced-motion: reduce) {
    .gf-tip {
        transition: none;
    }
}

/* Trash left, Cancel + Save right. justify-content: flex-end here plus margin-right: auto on
   .gf-trash, which needs no counting — the earlier `.fm-btn:first-of-type { margin-left: auto }`
   silently matched nothing once the trash (itself the first button) was present (fixed
   2026-09-01). */
.gf-edit-footer {
    display: flex;
    align-items: center;
    justify-content: flex-end;
    gap: 8px;
    min-height: 48px;
    padding: 8px;
    margin: 0 0.5rem;
    border-top: 1px solid rgba(15, 23, 42, 0.10);
}

.gf-edit-footer .fm-btn {
    height: 32px;
    padding: 0 0.85rem;
}

/* Delete trigger in the label form: a 32px ghost with 8px corners and a "Delete label" tooltip,
   measured on the reference. NEUTRAL — a destructive TRIGGER is neutral and the red is spent on
   the confirmation's own button, where the decision is actually taken (2026-09-01); hover is a
   neutral wash for the same reason: hover is not a decision.

   margin-right: auto pushes Cancel/Save to the right — see .gf-edit-footer. */
.gf-trash {
    position: relative;
    display: inline-flex;
    align-items: center;
    justify-content: center;
    width: 32px;
    height: 32px;
    padding: 0;
    margin-right: auto;
    border: none;
    border-radius: 8px;
    background: transparent;
    color: #1e293b;
    cursor: pointer;
}

.gf-trash:hover:not(:disabled) {
    background: rgba(33, 35, 44, 0.05);
}

.gf-trash:disabled {
    color: #94a3b8;
    cursor: default;
}

/* ===== Segmented dataset switch (.seg-switch) =====
   Two or more mutually exclusive buttons that swap WHAT a grid shows (not how it is
   filtered) — measured off the shared reference's Inbox/Archive pair: 32px tall, 8px outer
   corners, fused shared edges, active segment on a light accent tint. Those values are
   already this app's toolbar-control family (.gf-btn / .gf-btn--active use the very same
   32px / 8px / #cbd5e1 / #eff6ff + #0d6efd), so this reuses the palette rather than
   introducing the reference's own.
   Global (not component-scoped) because the fragment is authored in the calling page and
   handed to DataGrid's ToolbarStart, so it carries no component scope attribute. */
.seg-switch {
    display: inline-flex;
    align-items: center;
    gap: 0;
}

.seg-switch-btn {
    display: inline-flex;
    align-items: center;
    gap: 0.4rem;
    height: 32px;
    padding: 0 0.75rem;
    border: 1px solid #cbd5e1;
    border-radius: 0;
    background: #fff;
    color: #1e293b;
    font-size: 0.875rem;
    font-weight: 500;
    white-space: nowrap;
    cursor: pointer;
}

/* Overlap the shared edge instead of dropping one side's border: the pair then reads as a
   single control from BOTH directions, so whichever segment is active can paint its accent
   border over the neighbour's grey one (z-index below) without the group's outline shifting
   by a pixel as the selection moves. */
.seg-switch-btn + .seg-switch-btn {
    margin-left: -1px;
}

.seg-switch-btn:first-child {
    border-top-left-radius: 8px;
    border-bottom-left-radius: 8px;
}

.seg-switch-btn:last-child {
    border-top-right-radius: 8px;
    border-bottom-right-radius: 8px;
}

.seg-switch-btn:hover:not(.seg-switch-btn--active) {
    background: #f1f5f9;
}

.seg-switch-btn--active {
    position: relative;
    z-index: 1;
    border-color: #0d6efd;
    background: #eff6ff;
    color: #0d6efd;
    font-weight: 600;
}

/* A segment can be an <a> (a view SWITCH that navigates, e.g. the Activities List | Calendar
   pair) or a <span> (the current view). Bootstrap underlines every anchor, so the link form has
   to be told it is a control, not prose. */
a.seg-switch-btn,
a.seg-switch-btn:hover {
    text-decoration: none;
}

.seg-switch-btn svg {
    flex-shrink: 0;
}

/* ===== ToolbarSelect (.ts-*) — the rest of its chrome is the shared .gf-* family ===== */
/* A storage-account or container name can be long; the trigger must not stretch the
   toolbar row out of shape. */
.ts-trigger-text {
    display: inline-block;
    max-width: 12rem;
    overflow: hidden;
    text-overflow: ellipsis;
    white-space: nowrap;
    vertical-align: bottom;
}

.ts-row-suffix {
    margin-left: 0.4rem;
    font-size: 0.78rem;
    opacity: 0.7;
}

/* Non-interactive "nothing to choose from" row — same metrics as .gf-row, none of its
   hover/pointer affordances. */
.gf-row--empty {
    color: #64748b;
    cursor: default;
}

.gf-row--empty:hover {
    background: transparent;
    color: #64748b;
}

/* ===== Wildcard pattern query (grid toolbar) =====
   Takes the standard search box's place in the toolbar when a grid's search is a server-run
   query rather than a client-side substring filter, so the row's shape is unchanged and only
   the control's meaning differs. Monospace because the value is a pattern, not prose. */
.dg-pattern-wrap {
    display: flex;
    min-width: 0;
    flex: 1 1 20rem;
    max-width: 620px;
}

.dg-pattern-row {
    display: flex;
    align-items: center;
    gap: 0.4rem;
    flex: 1 1 auto;
    min-width: 0;
}

.dg-pattern-input {
    flex: 1 1 auto;
    min-width: 0;
    height: 32px;
    padding: 0 0.65rem;
    border: 1px solid #cbd5e1;
    border-radius: 8px;
    background: #fff;
    color: #1e293b;
    font-family: var(--app-font-mono);
    font-size: 0.875rem;
}

.dg-pattern-input:focus {
    outline: none;
    border-color: #0d6efd;
    box-shadow: 0 0 0 2px rgba(13, 110, 253, 0.15);
}

.dg-pattern-input::placeholder {
    color: #94a3b8;
}

/* Explains how the pattern above was parsed — always visible, because the single-level vs
   recursive rule is decided by the pattern's own shape and would otherwise be invisible. */
/* Status strip between a dataset switch and its grid's toolbar. Its height is load-bearing:
   it sits directly above the toolbar, so ANY variation here moves the whole grid down when
   the user switches datasets — which reads as the page jumping under them. Hence exactly one
   line, always, in every dataset (a dataset with nothing to explain still renders the strip
   so the geometry matches), and ellipsis instead of wrapping. Ellipsis is safe here because
   the part that must never be missed — "Single-level match" / "Recursive match" — is at the
   START of the text, and the full string stays available as the element's title. */
.dg-pattern-note {
    font-size: 0.78rem;
    line-height: 1.35;
    color: #64748b;
    min-height: 1.35em;
    white-space: nowrap;
    overflow: hidden;
    text-overflow: ellipsis;
}

.dg-pattern-note--error {
    color: #dc3545;
}

/* A warning that does NOT disable its control: the app cannot verify the condition (e.g.
   an Azure-side RBAC role it has no cheap way to read), so the user may proceed — informed. */
.dg-pattern-note--warn {
    color: #b45309;
}

/* StatusLine (2026-09-17) — the ONE outcome line, a member of the status-strip family above.
   Its shape was /settings/modules' (.mod-status, 2026-09-14) and /azuregw/azure-resources'
   (.azr-status, a copy of it); both now use this. It replaced every Bootstrap success alert:
   muted text, no box, one line with the full wording in title, at --fs-meta on an 18px line. */
.status-line {
    margin: 8px 0 0;
    font-size: var(--fs-meta);
    line-height: 18px;
    min-height: 18px;
}

/* Beside a control in a flex row (a grid toolbar): no top margin, and allowed to shrink so it
   ellipsizes rather than wrapping the row. */
.status-line--inline {
    margin: 0;
    min-width: 0;
    flex: 0 1 auto;
}

.status-line-icon {
    width: 14px;
    height: 14px;
    margin-right: 6px;
    vertical-align: -3px;
}

.status-line--success .status-line-icon {
    color: #198754;
}

.status-line strong {
    font-weight: 600;
    color: #21232c;
}

/* A failure or a warning reads in its colour end to end, headline included. */
.status-line--error strong,
.status-line--warn strong {
    color: inherit;
}

.status-line-sep {
    margin: 0 6px;
}

.status-line-link {
    margin-left: 4px;
    font-weight: 600;
    color: #1b6ec2;
    text-decoration: none;
}

.status-line-link:hover,
.status-line-link:focus-visible {
    text-decoration: underline;
}

/* Keeps a multi-control ToolbarStart together as ONE cluster. DataGrid renders that
   fragment's elements as direct children of the toolbar flex row, which is
   `justify-content: space-between` — so two or more of them get spread across the whole row
   instead of sitting side by side. Anything richer than a single button in ToolbarStart
   belongs in this wrapper. */
.dg-toolbar-group {
    display: flex;
    align-items: center;
    flex-wrap: wrap;
    gap: 0.5rem;
    min-width: 0;
}

/* ===== Info button (.info-btn) =====
   The "(i) explains this" affordance for anything that is NOT a DataGrid column header —
   a page title, a toolbar control, a form field label. Column headers keep using
   DataGridColumn.HeaderTooltip, which renders its own button; this is the shared button for
   everywhere else, so a second consumer never hand-rolls a variant
   (06-patterns/info-popover-pattern.md). Tokens are that pattern's: transparent at rest with
   a #6c757d glyph, tinting to rgba(15,23,42,.06) / #1b6ec2 on hover, focus, or while its
   card is open. Promoted to app.css 2026-08-27 when FileOps File Search became the second
   consumer; StorageAccountLifecycle's page-title button (previously .lm-info-btn) uses it too.
   Use it for an explanation too long for a one-sentence popover: click opens a guide modal. */
.info-btn {
    display: inline-flex;
    align-items: center;
    justify-content: center;
    width: 24px;
    height: 24px;
    border: none;
    border-radius: 50%;
    background: transparent;
    color: #6c757d;
    cursor: pointer;
    padding: 0;
    flex-shrink: 0;
    transition: background 120ms ease, color 120ms ease;
}

.info-btn:hover,
.info-btn:focus-visible,
.info-btn[aria-expanded="true"] {
    background: rgba(15, 23, 42, 0.06);
    color: #1b6ec2;
}

/* Toolbar-sized variant: matches the 32px control row's optical weight without breaking its
   height, for an info button sitting among .gf-btn siblings. */
.info-btn--toolbar {
    width: 28px;
    height: 28px;
}

/* ── Optical vertical centring: cap band vs box (2026-08-31) ──────────────────────────
   A box that wraps its text tightly — a chip, a button — is read on where the CAP BAND
   sits inside it, not on where the line box sits. Those are not the same thing, and the
   gap between them is what makes a label look like it is riding high in its pill.

   MECHANISM, and it is NOT "the typeface is off-centre". Inter's own metrics are already
   balanced: measured at 1000px, its cap band leaves 0.2415em above it and 0.2410em below
   it inside a line-height: normal box. The offset comes from Chrome SNAPPING the ascent
   and the descent to whole pixels at the used size — a 100px Inter block with
   line-height 150px puts its baseline at 131px, where the unrounded metrics predict
   131.4px. The snapping error therefore varies with the font size as a sawtooth, so NO
   single em constant corrects every control. Measured, + meaning the cap band sits high:

     | used size | offset  | where                                       | corrected |
     |-----------|---------|---------------------------------------------|-----------|
     | 10.5px    | +0.85px | .label-pill (16px box, 16px line)           | yes       |
     | 10.5px    | +1.07px | .badge                                      | yes       |
     | 11.2px    | +0.48px | .cu-alpha-btn                               | no        |
     | 12.25px   | +0.65px | .btn-sm, .gf-btn, .seg-switch-btn, .cu-tab  | yes       |
     | 13px      | +0.48px | .fm-btn                                     | no        |
     | 14px      | −0.40px | a plain .btn (Bootstrap's own 1rem default) | no        |

   Note the sign change between 12.25px and 14px: the same class, .btn, needs opposite
   corrections at its two sizes, which is why the rules below key off .btn-sm rather than
   off .btn.

   Anything under ~0.5px is left alone: that is inside the device-pixel rounding the
   snapping introduced in the first place, so a correction there lands as noise rather
   than as a visible improvement.

   HOW TO MEASURE, because three earlier attempts got this wrong. Canvas fontBoundingBox
   metrics and Range client rects both describe the FONT, not what Chrome painted; and a
   zero-height baseline probe appended straight into a chip becomes a sibling FLEX ITEM
   aligned by align-items: center instead of sitting on the text baseline. What works:
   wrap the element's own text node in an inline <span>, put the zero-height inline-block
   probe INSIDE that span so its bottom edge is the real baseline, and compare that
   against a cap height rasterised from the same font at the same weight.

   FONT-SPECIFIC — re-measure if the typeface ever changes. The same 32px control in
   Segoe UI measures −0.53px, cap band LOW: the correction is not merely a different
   number for a different face, it is a different sign.

   Rejected on the way, both tried and measured rather than reasoned away:
   text-box: trim-both cap alphabetic (Chrome parses it, but it does not affect a flex
   container's anonymous text item, which is exactly what these boxes hold), and an
   integer height with align-items: center (Bootstrap's .badge is inline-block, so
   align-items does nothing to it and its text jumped to the top of the box). */
:root {
    /* .badge at 0.75rem (10.5px): +1.07px. 0.10em = 1.05px, landing it at +0.02px. */
    --chip-optical-shift: 0.10em;
    /* Every 0.875rem (12.25px) control: +0.65px. In px, not em — see the table above. */
    --ctl-optical-shift: 0.65px;
}

/* The two paddings move in opposite directions, so the box height is unchanged (they
   still sum to 0.7em): no row grows and no saved column width shifts.

   No label pill is in this rule. Labels have their own .label-pill (2026-09-16), corrected
   inside its own rule because it is a FIXED 16px box, not a padded one. The old .gf-pill
   (0.72rem, 0.2em padding, +0.22px) was listed here alongside .badge until 2026-08-31,
   which handed it .badge's 0.35em padding AND .badge's correction and left it 3px taller
   than designed and 0.78px LOW. A correction calibrated on one chip does not
   transfer to a chip of a different size and padding. */
.badge {
    padding-top: calc(0.35em + var(--chip-optical-shift));
    padding-bottom: calc(0.35em - var(--chip-optical-shift));
}

/* Buttons. Two mechanics, because a one-sided padding does not do the same thing to a
   fixed-height control as it does to an auto-height one:

   - AUTO height (.btn, .gf-footer-add): the two sides trade the shift, the height stays
     put, and the content moves down by the full amount.
   - FIXED height (.gf-btn, .seg-switch-btn): the height cannot move, so the content box
     is re-centred inside it and only HALF of a one-sided padding lands — hence the 2x.

   Only the LABEL should move. An icon is a geometric shape that was already centred, so
   every svg is pushed back by the same amount, as is .gf-btn-pill (a chip standing in
   for the label, not text). Correcting in CSS rather than by wrapping every label in its
   own element is deliberate: a consumer written later is corrected without anyone having
   to remember to wrap anything. */
/* .btn-sm, NOT .btn: the correction belongs to the SIZE, not to the class. A .btn-sm is
   0.875rem and sits +0.65px high; a plain .btn is 1rem and sits 0.40px LOW, so the same
   rule applied to both took the full-size button from -0.40px to -1.05px. Measured on
   "List subscriptions from Azure" (/azure/azsubscriptions) after doing exactly that. */
.btn-sm {
    padding-top: calc(var(--bs-btn-padding-y) + var(--ctl-optical-shift));
    padding-bottom: calc(var(--bs-btn-padding-y) - var(--ctl-optical-shift));
}

/* Bootstrap sets the shorthand padding on a button inside a sized input-group, at a
   specificity the plain .btn rule above cannot reach and without going through
   --bs-btn-padding-y — so the grid's Search button needs its own line with that rule's
   own 0.25rem base. Only .input-group-sm is used in this app (DataGrid's search box). */
.input-group-sm > .btn {
    padding-top: calc(0.25rem + var(--ctl-optical-shift));
    padding-bottom: calc(0.25rem - var(--ctl-optical-shift));
}

.gf-btn,
.seg-switch-btn {
    padding-top: calc(var(--ctl-optical-shift) * 2);
}

.btn-sm > svg,
.input-group-sm > .btn > svg,
.gf-btn > svg,
.seg-switch-btn > svg,
.gf-btn-pill {
    position: relative;
    top: calc(var(--ctl-optical-shift) * -1);
}

/* Toolbar count note + Sync icon button (2026-09-02, job detail modal — reference-matched:
   the deals page's "12 deals · sync" top-right anatomy, measured live: count 14px/450
   #21232c, sync a 34×32 bordered 8px-radius icon button with a 16px circular-arrows icon.
   .gf-btn already IS that chrome; .dg-sync-btn only squares it up for an icon-only body). */
.dg-count-note {
    font-size: 14px;
    font-weight: 450;
    color: #21232c;
    white-space: nowrap;
    align-self: center;
}

.dg-sync-btn {
    width: 34px;
    padding: 0;
    justify-content: center;
    flex-shrink: 0;
}

@keyframes dg-sync-spin {
    from { transform: rotate(0deg); }
    to { transform: rotate(360deg); }
}

.dg-sync-btn--busy svg {
    animation: dg-sync-spin 0.9s linear infinite;
}

/* Left-aligned note in a ModalForm footer (first use 2026-09-02: the job detail's failure
   summary, moved out of the grid body because FillBody makes every banner above the grid
   cost rows). margin-right:auto pushes the footer's buttons back to their right edge;
   ellipsis + the title attribute carry the full text. Global on purpose — Footer
   fragments never match ModalForm.razor.css's scope. */
.fm-footer-note {
    margin-right: auto;
    min-width: 0;
    font-size: 0.8125rem;
    line-height: 1.3;
    text-align: left;
    overflow: hidden;
    text-overflow: ellipsis;
    white-space: nowrap;
    color: #475569;
}

.fm-footer-note--danger {
    color: #b02a37;
}

/* The display-only "Rehydrating…" status override (2026-09-02) — the app's amber, the
   same #b45309 the warn pattern-note uses: in-between, not failed, not fine yet. */
.dg-status-rehydrating {
    color: #b45309;
}

/* Amber footer-note variant (2026-09-02): the failure summary while its remedy is already
   RUNNING — in-between, matching .dg-status-rehydrating's #b45309. */
.fm-footer-note--warn {
    color: #b45309;
}

/* ── Auth pages (.auth-* family) ─────────────────────────────────────────────
   The shared card + form chrome for /login, /setup and /password-expired
   (2026-09-02/03, reference-style redesign; the shell around it is AuthLayout).
   Global like fm-*: three pages share these, and layout @Body content never
   carries a scoped file's isolation attribute anyway.

   SIZING SPEC (settled 2026-09-03): the reference's LITERAL CSS measurements.
   A 1.11x "physical parity" scale shipped briefly on 2026-09-02 — the owner's
   browser turned out to be zoomed in on the reference site (their own catch),
   so the scale was reverted; CSS pixels are compared at equal zoom. Radii stay
   the reference's 4px (deliberate override of the app radius tokens on these
   pages only) and the button is #3C30A6 (owner's pick: the rail hue, lifted).

   INPUT STATES (measured off the reference with real click+type): WHITE at
   rest with a 1px #E4E6E9 frame and a #656E7A icon; on focus the frame AND the
   field's icon darken to #192435 — a plain 1px line, NO glow/ring/shadow (an
   earlier focus ring here read as a gray halo and was reported as a defect). */

.auth-card {
    width: 100%;
    max-width: 500px;
    background: #fff;
    border: 2px solid #e4e6e9;
    border-radius: 4px;
    padding: 40px;
}

@media (max-width: 560px) {
    .auth-card { padding: 24px; border-width: 1px; }
}

.auth-title {
    margin: 0 0 8px;
    font-size: 30px;
    font-weight: 700;
    color: #1f2937;
    text-align: center;
}

.auth-subtitle {
    margin: 0 0 32px;
    font-size: 14px;
    color: #6c757d;
    text-align: center;
}

.auth-field {
    position: relative;
    margin-bottom: 40px;
}

.auth-field-icon {
    position: absolute;
    left: 21px;
    top: 28px;
    transform: translateY(-50%);
    width: 24px;
    height: 24px;
    color: #656E7A;
    pointer-events: none;
    transition: color 120ms ease;
}

/* The field's own icon darkens with its frame while the field is focused. */
.auth-field:focus-within .auth-field-icon { color: #192435; }

.auth-input {
    display: block;
    width: 100%;
    height: 56px;
    padding: 21px 20px 5px 55px;
    font-size: 18px;
    color: #212529;
    background: #fff;
    border: 1px solid #e4e6e9;
    border-radius: 4px;
    transition: border-color 120ms ease;
}

.auth-input:focus {
    outline: none;
    border-color: #192435;
    box-shadow: none;
}

.auth-input:disabled {
    background: #f1f3f5;
    color: #6c757d;
    cursor: not-allowed;
}

/* Floating label: the input carries placeholder=" " so :placeholder-shown flips the
   moment it has a value; the label is the visible text in both states. */
.auth-label {
    position: absolute;
    left: 55px;
    top: 28px;
    transform: translateY(-50%);
    font-size: 18px;
    color: #6c757d;
    pointer-events: none;
    transition: top 120ms ease, font-size 120ms ease;
}

.auth-input:focus + .auth-label,
.auth-input:not(:placeholder-shown) + .auth-label {
    top: 15px;
    font-size: 12px;
}

.auth-hint {
    margin: 6px 2px 0;
    font-size: 12px;
    color: #6c757d;
}

.auth-btn {
    display: flex;
    width: 100%;
    height: 56px;
    align-items: center;
    justify-content: center;
    margin-top: 40px;
    border: 0;
    border-radius: 4px;
    background: #3C30A6;
    color: #fff;
    font-size: 22px;
    font-weight: 700;
    cursor: pointer;
    transition: background 120ms ease;
}

.auth-btn:hover { background: #332990; }

.auth-btn:focus-visible {
    outline: none;
    box-shadow: 0 0 0 3px rgba(60, 48, 166, 0.35);
}

/* The in-card notes, on the reference's error-banner anatomy: leading warning
   triangle, tinted ground, DARK text (the tint carries the severity, the text stays
   readable). Measured off the reference's real incorrect-password response. */
.auth-note {
    display: flex;
    align-items: flex-start;
    gap: 12px;
    margin: 0 0 24px;
    padding: 14px 16px;
    border: 1px solid transparent;
    border-radius: 4px;
    font-size: 14px;
    line-height: 1.5;
    color: #192435;
}

.auth-note-icon { flex: 0 0 24px; width: 24px; height: 24px; }

.auth-note--danger { background: #FEF2F0; border-color: #FB8B80; }
.auth-note--danger .auth-note-icon { color: #FF5550; }
.auth-note--warn   { background: #fff8e6; border-color: #ffe08a; }
.auth-note--warn .auth-note-icon { color: #d97706; }
.auth-note--info   { background: #eff6ff; border-color: #bfdbfe; }
.auth-note a       { color: inherit; font-weight: 600; }

/* Show/hide password — suffix control inside the password field (owner's ask; the
   page is static SSR, so the toggle is a plain inline onclick, not Blazor). The
   reference's control is TEXT ("Show"/"Hide") and only exists while the field has
   content — the :placeholder-shown sibling rule reproduces that without any script. */
.auth-input--suffixed { padding-right: 76px; }

.auth-eye {
    position: absolute;
    right: 20px;
    top: 28px;
    transform: translateY(-50%);
    padding: 4px 2px;
    border: 0;
    background: transparent;
    font-size: 14px;
    color: #192435;
    cursor: pointer;
}

.auth-eye:hover { text-decoration: underline; }
.auth-input--suffixed:placeholder-shown ~ .auth-eye { display: none; }
.auth-eye .auth-eye-hide { display: none; }
.auth-eye--on .auth-eye-hide { display: inline; }
.auth-eye--on .auth-eye-show { display: none; }

/* Remember me — reference position: left-aligned under the primary button. */
.auth-check {
    display: flex;
    align-items: center;
    gap: 12px;
    margin: 40px 0 0;
    font-size: 14px;
    color: #212529;
    cursor: pointer;
    user-select: none;
    position: relative;
}

/* Custom-drawn checkbox (the reference draws its own): the native input stays for the
   form POST and keyboard/focus, the span is the visual. */
.auth-check input {
    position: absolute;
    width: 24px;
    height: 24px;
    margin: 0;
    opacity: 0;
}

.auth-check-box {
    flex: 0 0 24px;
    width: 24px;
    height: 24px;
    border: 2px solid #c6cad0;
    border-radius: 4px;
    background: #fff;
    position: relative;
    transition: background 120ms ease, border-color 120ms ease;
}

.auth-check input:checked + .auth-check-box {
    background: #211C52;
    border-color: #211C52;
}

.auth-check input:checked + .auth-check-box::after {
    content: '';
    position: absolute;
    left: 7px;
    top: 2px;
    width: 6px;
    height: 12px;
    border: solid #fff;
    border-width: 0 3px 3px 0;
    transform: rotate(45deg);
}

.auth-check input:focus-visible + .auth-check-box {
    box-shadow: 0 0 0 3px rgba(33, 28, 82, 0.25);
}

/* Secondary way out (e.g. "Back to login" on /password-expired). */
.auth-alt {
    display: block;
    margin-top: 18px;
    text-align: center;
    font-size: 14px;
    color: #6c757d;
    text-decoration: none;
}

.auth-alt:hover { color: #211C52; text-decoration: underline; }

/* A pattern-note that hosts a CONTROL (e.g. /fileops/jobs' "Show all" toggle): flex so the
   control and its text centre on one axis instead of baseline-drifting. */
.dg-pattern-note--control {
    display: inline-flex;
    align-items: center;
    gap: 8px;
    overflow: visible;
}

/* THE STANDARD gap between a page's tab strip and what follows it (owner's decision,
   2026-09-03): 25px, matched to the distance the grid toolbar keeps to the grid
   (.data-grid-toolbar), so strip → toolbar → grid is one rhythm. Applied on every tab
   strip: SettingsTabs/CompanySettingsTabs default to it, and the page-local strips
   (/fileops/files, /fileops/jobs, /fileops/instances, instance detail, /azure/service-principals)
   carry it in markup. New tab strips take this class, never an ad-hoc mb-*. */
.tab-strip-gap {
    margin-bottom: 25px;
}

/* =====================================================================================
   Record detail drawer (Components/Drawer/RecordDrawer, 2026-09-08, ADR-023)
   =====================================================================================
   GLOBAL on purpose: the Header / Body / Footer / Right slots are authored in the record
   body's .razor file, so a class scoped to RecordDrawer.razor.css would never match them
   (the CSS-isolation slot trap, 07-guard-rails/anti-patterns.md). Everything the drawer and
   its field rows wear lives here, in one place.

   Geometry mirrors DrawerGeometry.cs exactly — change the numbers in BOTH or the unit test
   is lying. Measured off the shared reference: 75vw clamped 960–1440 px, floor 910 px at
   ≤1280 px, docked to the content edge at ≤1024 px. --rd-content-left is written by
   record-drawer.js (the rail + the secondary nav, expanded or collapsed); the KeepVisible cap
   (200 px = the 32 px selection lane + a readable first column) is ours, requirement B2.
   min-width beats max-width in CSS, so the floor wins over the cap — by design.

   z-index 950: above the sticky top-row (900) and the secondary nav (880), below the primary
   rail (1020), the notes panel's dialog (1050), every popover (1100) and every ModalForm (1300)
   — a popover or a modal opened FROM the drawer must sit over it.

   NO transform at rest. The slide is a keyframe animation that finishes; a persistent
   translateX would make this element the containing block of every position:fixed popover
   inside it (profile-dropdown.js fixedContainingBlock) and the scrolling columns would clip
   them. Docked to the viewport's right edge, so no radius (docked-edge rule, ui-patterns.md). */
.rd-drawer {
    --rd-rule: rgba(33, 35, 44, 0.14);
    --rd-text-secondary: #65686f;
    --rd-hover: #f5f5f6;
    --rd-right-bg: #f5f5f6;
    position: fixed;
    top: 56px;
    right: 0;
    bottom: 0;
    z-index: 950;
    width: 75vw;
    min-width: 960px;
    max-width: min(1440px, calc(100vw - var(--rd-content-left, 296px) - 200px));
    display: flex;
    flex-direction: column;
    background: #fff;
    color: #212529;
    box-shadow: var(--elev-overlay);
    outline: none;
    overflow: visible; /* the edge controls float outside the box */
}

@media (max-width: 1280px) {
    .rd-drawer { min-width: 910px; }
}

@media (max-width: 1024px) {
    .rd-drawer {
        right: auto;
        left: var(--rd-content-left, 85px);
        width: auto;
        min-width: 0;
        max-width: none;
    }
}

.rd-drawer--open {
    animation: rd-open 300ms cubic-bezier(0.25, 0.46, 0.45, 0.94);
}

.rd-drawer--closing {
    animation: rd-close 300ms cubic-bezier(0.25, 0.46, 0.45, 0.94) forwards;
    pointer-events: none;
}

@keyframes rd-open {
    from { transform: translateX(100%); }
    to   { transform: translateX(0); }
}

@keyframes rd-close {
    from { transform: translateX(0); visibility: visible; }
    to   { transform: translateX(100%); visibility: hidden; }
}

@media (prefers-reduced-motion: reduce) {
    .rd-drawer--open { animation: none; }
    .rd-drawer--closing { animation: none; visibility: hidden; }
}

/* Outer-edge controls — a small vertical pill floating just left of the drawer, over the
   grid (the reference's placement). Popover chrome: white, rule border, --popover-radius,
   the .dg-popover shadow. */
.rd-edge {
    position: absolute;
    top: 8px;
    left: -44px;
    display: flex;
    flex-direction: column;
    gap: 2px;
    padding: 4px;
    background: #fff;
    border: 1px solid #dbe2ea;
    border-radius: var(--popover-radius);
    box-shadow: 0 10px 28px rgba(15, 23, 42, 0.18), 0 2px 6px rgba(15, 23, 42, 0.08);
}

/* At ≤1024 the drawer starts at the content edge, so there is nothing to float over: the
   controls move INSIDE, as a horizontal row above the header, which makes room for them. */
@media (max-width: 1024px) {
    .rd-edge {
        left: 8px;
        top: 8px;
        flex-direction: row;
        z-index: 1;
    }

    .rd-drawer .rd-header {
        padding-top: 52px;
    }
}

.rd-edge-btn {
    display: inline-flex;
    align-items: center;
    justify-content: center;
    width: 28px;
    height: 28px;
    padding: 0;
    border: 0;
    border-radius: var(--btn-radius-sm);
    background: transparent;
    color: #475569;
    cursor: pointer;
    transition: background 120ms ease, color 120ms ease;
}

.rd-edge-btn:hover:not(:disabled) {
    background: #f1f5f9;
    color: #1e293b;
}

.rd-edge-btn:disabled {
    color: #b7bcc4;
    cursor: default;
}

.rd-edge-btn:focus-visible {
    outline: 2px solid #0d68c5;
    outline-offset: 1px;
}

/* Two columns, 35 / 65, each scrolling on its own; header and footer of the left column are
   fixed by being outside its scroll box. min-height: 0 on every flex level so the scroll boxes
   actually get a bound. */
.rd-columns {
    display: flex;
    flex: 1 1 auto;
    min-height: 0;
}

.rd-col {
    display: flex;
    flex-direction: column;
    min-height: 0;
    min-width: 0;
}

.rd-col--left {
    flex: 0 0 35%;
    background: #fff;
    border-right: 1px solid var(--rd-rule);
}

/* 24px on every side: the reference's own record drawer insets its right column's content by
   24px and draws no card inside it, so this padding IS the area's only gutter (2026-09-12 —
   NotesActivityFilesPanel dropped its own inner padding in the same pass). */
.rd-col--right {
    flex: 1 1 65%;
    background: var(--rd-right-bg);
    overflow-y: auto;
    overscroll-behavior: contain;
    padding: 24px;
}

.rd-header {
    flex: 0 0 auto;
    padding: 16px 20px 12px;
    border-bottom: 1px solid var(--rd-rule);
}

.rd-scroll {
    flex: 1 1 auto;
    min-height: 0;
    overflow-y: auto;
    overscroll-behavior: contain;
    padding: 8px 12px 16px;
}

/* Title: 18px/700 like the permission-set drawer's, focus target on open (tabindex=-1 from the
   record body's markup). No focus ring for a programmatic focus — the row highlight and the
   slide already say where the user is; the ring appears only for keyboard focus. */
.rd-title {
    margin: 0;
    font-size: 18px;
    font-weight: 700;
    line-height: 1.3;
    color: #212529;
    overflow-wrap: anywhere;
    outline: none;
}

.rd-title:focus-visible {
    outline: 2px solid #0d68c5;
    outline-offset: 2px;
    border-radius: 2px;
}

.rd-header-row {
    display: flex;
    align-items: flex-start;
    gap: 8px;
}

.rd-header-row > .rd-title {
    flex: 1 1 auto;
    min-width: 0;
}

.rd-meta {
    margin-top: 4px;
    font-size: 12px;
    color: var(--rd-text-secondary);
    line-height: 1.5;
}

.rd-meta a {
    color: #006bb7;
}

.rd-badges {
    display: flex;
    align-items: center;
    flex-wrap: wrap;
    gap: 8px;
    margin-top: 6px;
    font-size: 12px;
    color: var(--rd-text-secondary);
}

/* Sections (SUMMARY / DETAILS / RELATED). */
.rd-section {
    padding: 12px 8px 4px;
}

.rd-section + .rd-section {
    border-top: 1px solid var(--rd-rule);
    margin-top: 8px;
}

.rd-section-head {
    display: flex;
    align-items: center;
    justify-content: space-between;
    gap: 8px;
    padding: 0 8px 6px;
}

.rd-section-title {
    margin: 0;
    font-size: 12px;
    font-weight: 600;
    letter-spacing: 0.04em;
    text-transform: uppercase;
    color: #21232c;
}

.rd-section-actions {
    display: flex;
    align-items: center;
    gap: 4px;
}

/* Field rows: icon | value · label | pencil. 32 px tall at rest, hover tint only where there
   is something to edit (the pencil is the affordance; the tint is its stage). */
.rd-field {
    display: flex;
    align-items: center;
    gap: 10px;
    min-height: 32px;
    padding: 4px 8px;
    border-radius: var(--btn-radius-sm);
    position: relative;
}

.rd-field--editable:hover,
.rd-field--editing {
    background: var(--rd-hover);
}

.rd-field-icon {
    flex: 0 0 16px;
    width: 16px;
    height: 16px;
    display: inline-flex;
    align-items: center;
    justify-content: center;
    color: #6c757d;
}

.rd-field-icon svg {
    width: 16px;
    height: 16px;
}

.rd-field-main {
    flex: 1 1 auto;
    min-width: 0;
    display: flex;
    align-items: center;
    flex-wrap: wrap;
    gap: 0 6px;
    line-height: 1.4;
}

.rd-field-value {
    min-width: 0;
    overflow-wrap: anywhere;
    color: #212529;
}

.rd-field-value a {
    color: #006bb7;
}

.rd-field-value.rd-mono,
.rd-mono {
    font-family: var(--app-font-mono);
    font-size: 12.5px;
}

.rd-field-label {
    font-size: 12px;
    color: var(--rd-text-secondary);
    white-space: nowrap;
}

.rd-field-trailing {
    display: inline-flex;
    align-items: center;
}

/* Empty: the field name in grey — never hidden, never "—". Editable-empty: the "+ Add" link. */
.rd-field-placeholder {
    color: #6c757d;
}

.rd-field-add {
    padding: 0;
    border: 0;
    background: transparent;
    font: inherit;
    font-weight: 500;
    color: #1b6ec2;
    cursor: pointer;
}

.rd-field-add:hover {
    text-decoration: underline;
}

.rd-field-add:focus-visible {
    outline: 2px solid #0d68c5;
    outline-offset: 2px;
    border-radius: 2px;
}

/* The pencil — DataGrid's cell pencil, re-declared here because that one is CSS-isolated.
   Hidden by opacity so the 24 px stays reserved (no reflow on hover) and the button stays in
   the tab order. */
.rd-field-edit-btn {
    display: inline-flex;
    flex: 0 0 auto;
    align-items: center;
    justify-content: center;
    width: 24px;
    height: 24px;
    padding: 0;
    border: 1px solid rgba(33, 35, 44, 0.14);
    border-radius: var(--btn-radius-sm);
    background: #fff;
    box-shadow: 0 1px 2px rgba(42, 54, 71, 0.05);
    color: #475569;
    cursor: pointer;
    opacity: 0;
}

.rd-field--editable:hover .rd-field-edit-btn,
.rd-field--editing .rd-field-edit-btn,
.rd-field-edit-btn:focus-visible {
    opacity: 1;
}

.rd-field-edit-btn:hover {
    background: #f8f9fa;
    border-color: rgba(33, 35, 44, 0.24);
    color: #1e293b;
}

.rd-field-edit-btn:focus-visible {
    outline: 2px solid #0d68c5;
    outline-offset: 1px;
}

/* A small card for RELATED records. Same border/radius as the notes composer box. */
.rd-card {
    display: block;
    margin: 0 8px;
    padding: 10px 12px;
    border: 1px solid #dfe3e8;
    border-radius: var(--popover-radius);
    background: #fff;
    color: inherit;
    text-decoration: none;
}

.rd-card:hover {
    background: var(--rd-hover);
    text-decoration: none;
}

.rd-card-title {
    font-weight: 600;
}

.rd-card-meta {
    font-size: 12px;
    color: var(--rd-text-secondary);
}

/* Footer: a fixed 49 px bar. ⋯ on the left, secondary buttons in the middle, the primary on
   the right — the order is the rule (requirement §4), the record decides the buttons. A
   container query narrows the secondary buttons to icon-only (the record body wraps their text
   in .rd-btn-label and gives each a title) when the left column gets tight. */
.rd-footer {
    flex: 0 0 auto;
    display: flex;
    align-items: center;
    gap: 8px;
    height: 49px;
    padding: 0 12px;
    border-top: 1px solid var(--rd-rule);
    background: #fff;
    container-type: inline-size;
}

.rd-footer-start {
    display: flex;
    align-items: center;
    gap: 6px;
}

.rd-footer-mid {
    display: flex;
    align-items: center;
    gap: 6px;
    margin-left: auto;
}

.rd-footer-end {
    display: flex;
    align-items: center;
    gap: 6px;
    flex-shrink: 0; /* the primary action is never the one that gives way */
}

.rd-footer .btn {
    white-space: nowrap;
}

.rd-footer .btn .rd-btn-icon {
    display: none;
    width: 16px;
    height: 16px;
}

@container (max-width: 470px) {
    .rd-footer-mid .btn .rd-btn-label { display: none; }
    .rd-footer-mid .btn .rd-btn-icon { display: inline-block; }
    .rd-footer-mid .btn { padding-left: 8px; padding-right: 8px; }
}

/* A field editor popover hosted by the drawer. DataGrid gives ITS editor panels the 330 px
   width through a ::deep rule in DataGrid.razor.css, which only matches under a DataGrid
   ancestor — a drawer-hosted DismissiblePanel needs the same measure from here. Carries
   .dg-popover (chrome) and .dg-cell-editor (the .gf-list height caps above) alongside. */
.rd-editor {
    width: 330px;
    max-width: calc(100vw - 16px);
    padding: 0;
}

/* The footer's ⋯ trigger — the grid kebab's look, standalone. */
.rd-kebab-btn {
    display: inline-flex;
    align-items: center;
    justify-content: center;
    width: 32px;
    height: 32px;
    padding: 0;
    border: 1px solid #ced4da;
    border-radius: var(--btn-radius);
    background: #fff;
    color: #495057;
    font-size: 16px;
    line-height: 1;
    cursor: pointer;
}

.rd-kebab-btn:hover {
    background: #f8f9fa;
}

.rd-kebab-btn:focus-visible {
    outline: 2px solid #0d68c5;
    outline-offset: 1px;
}

/* A transient FAILURE notice ("Could not copy — clipboard unavailable.", a failed delete) —
   floats above the footer inside the drawer, so the reader who just clicked sees it; the page
   behind gets nothing. Success notices ("staged in the Evaluation…", "Linked to FileOps",
   "Copied to clipboard.") were removed on 2026-09-17: the storage account's changes are in its
   Changelog (ADR-028) and Copy shows "Copied!" on the item itself. */
.rd-notice {
    position: absolute;
    left: 16px;
    bottom: 60px;
    max-width: 30%;
    margin: 0;
    font-size: 13px;
    box-shadow: 0 6px 20px rgba(15, 23, 42, 0.14);
    z-index: 1;
}

/* Error state inside the drawer (never a page-level alert — the reader is looking here). */
.rd-error {
    margin: 16px 8px;
    padding: 12px 14px;
    border: 1px solid #f1aeb5;
    border-radius: var(--popover-radius);
    background: #f8d7da;
    color: #58151c;
    display: flex;
    flex-direction: column;
    gap: 10px;
    align-items: flex-start;
}

.rd-error-text {
    font-size: 13px;
}

/* Skeleton — shimmer bars where the record will be. */
.rd-skeleton {
    padding: 8px 8px 0;
}

.rd-skel {
    height: 12px;
    border-radius: 4px;
    background: linear-gradient(90deg, #eef0f3 25%, #f6f7f9 50%, #eef0f3 75%);
    background-size: 200% 100%;
    animation: rd-shimmer 1.2s ease-in-out infinite;
}

.rd-skel--title { height: 20px; width: 55%; }
.rd-skel--meta { height: 10px; width: 85%; margin-top: 10px; }
.rd-skel--section { height: 10px; width: 80px; margin: 14px 8px 10px; }
.rd-skel--icon { flex: 0 0 16px; height: 16px; }
.rd-skel--value { height: 12px; }
.rd-skel--line { height: 10px; }

.rd-skel-row {
    display: flex;
    align-items: center;
    gap: 10px;
    min-height: 32px;
    padding: 4px 8px;
}

.rd-skel-card {
    display: flex;
    flex-direction: column;
    gap: 10px;
    padding: 14px 16px;
    margin-bottom: 12px;
    border: 1px solid #dfe3e8;
    border-radius: var(--popover-radius);
    background: #fff;
}

@keyframes rd-shimmer {
    from { background-position: 200% 0; }
    to   { background-position: -200% 0; }
}

@media (prefers-reduced-motion: reduce) {
    .rd-skel { animation: none; }
}

/* ===== Activities toolbar + chip chrome (list AND week calendar) =====
   Lived in ActivitiesIndex.razor.css until 2026-09-12, when /activities/calendar became a second
   page rendering the same toolbar. CSS isolation stamps each component with its own scope
   attribute, so an isolated rule silently never matches another component's markup — the same
   failure the .fm-btn family hit, and the same fix: shared chrome belongs in app.css.

   Icon-only List | Calendar segments on the standard .seg-switch chrome (reference: two 48x32
   icon links), the "+ Activity" primary button at the toolbar family's 32px control height, and
   the "N activities" counter that sits before the assignee filter (reference: counter, then
   Filter, at the row's right end).

   Chip row, measured off the reference 2026-09-11: 24px tall, 12px/600, 6px radius, NO border,
   transparent at rest, 2px between chips; the selected chip on the light accent tint, hover on an
   unselected chip a 5% ink wash and on a selected one the deeper tint. Palette is this app's own
   (#eff6ff / #e1eeff / #0d6efd are already the grid's tick, open-row and accent colours) - the
   reference's values are only the geometry. The status chips (To-do . Overdue . Completed) are one
   fused segment: outer corners rounded, inner ones square, still 2px apart. */
.act-view-btn {
    width: 48px;
    padding: 0;
    justify-content: center;
}

.act-add-btn {
    display: inline-flex;
    align-items: center;
    gap: 6px;
    height: 32px;
    padding: 0 12px 0 10px;
    font-size: var(--fs-body);
    font-weight: 600;
    border-radius: 8px;
    white-space: nowrap;
}

.act-count {
    font-size: var(--fs-body);
    color: #1e293b;
    white-space: nowrap;
}

.act-chip-group {
    display: flex;
    align-items: center;
    flex-wrap: wrap;
    gap: 2px;
}

.act-chip {
    display: inline-flex;
    align-items: center;
    gap: 4px;
    height: 24px;
    padding: 0 8px;
    border: 0;
    border-radius: 6px;
    background: transparent;
    color: #1e293b;
    font-size: var(--fs-meta);
    font-weight: 600;
    line-height: 1;
    cursor: pointer;
    white-space: nowrap;
}

.act-chip:hover:not(.act-chip--active) {
    background: rgba(33, 35, 44, .05);
}

.act-chip--active {
    background: #eff6ff;
    color: #0d6efd;
}

.act-chip--active:hover {
    background: #e1eeff;
}

.act-chip:disabled {
    opacity: .5;
    cursor: default;
}

.act-chip-seg {
    display: inline-flex;
    align-items: center;
    gap: 2px;
}

.act-chip-seg .act-chip {
    border-radius: 0;
}

.act-chip-seg .act-chip:first-child {
    border-radius: 6px 0 0 6px;
}

.act-chip-seg .act-chip:last-child {
    border-radius: 0 6px 6px 0;
}

.act-chip-sep {
    width: 1px;
    height: 16px;
    background: #e2e8f0;
    margin: 0 6px;
}

.act-period-popover {
    min-width: 220px;
}

/* ===== Activities central list — row due-state colouring (ADR-025, step e) =====

   Applied via DataGrid.RowClass (a plain string appended to the row's own <tr class>) — these
   elements are rendered by DataGrid.razor, not by the Activities page, so a page-scoped
   .razor.css rule could never carry the matching scope attribute; these MUST be global. The
   `!important` and the (class, class, 3-element) selector both mirror the one other rule that
   already overrides `.dg-table td`'s own colour (the built-in row hover, above) — needed to
   beat DataGrid.razor.css's own scoped specificity. Colours match ActivityCard's own due-state
   reading (ActivityCard.razor.css) so a card and a grid row agree. */
.dg-table tbody tr.act-row--overdue > td {
    color: #b42318 !important;
}

.dg-table tbody tr.act-row--today > td {
    color: #16a34a !important;
}

.dg-table tbody tr.act-row--done > td {
    color: #98a2b3 !important;
    text-decoration: line-through;
}

.dg-table tbody tr.act-row--done .activity-done-toggle {
    text-decoration: none;
}

/* Links inside a due-state row. DataGrid's own in-cell link colour (#21232c, scoped via
   ::deep, specificity (0,3,2)) beats the row rules above, which left the Subject and record
   links dark on a red/green/grey row — the reference colours EVERY cell, links included, so
   these inherit the row colour. !important for the same reason the row rules carry it. */
.dg-table tbody tr.act-row--overdue > td a,
.dg-table tbody tr.act-row--today > td a,
.dg-table tbody tr.act-row--done > td a,
.dg-table tbody tr.act-row--overdue > td .dg-cell-link,
.dg-table tbody tr.act-row--today > td .dg-cell-link,
.dg-table tbody tr.act-row--done > td .dg-cell-link {
    color: inherit !important;
}

/* ===== Activity done toggle (o / check) — ActivityCard AND the central grid's Done cell =====
   Global on purpose: the card is its own component while the grid cell is authored in
   ActivitiesIndex.razor's Cell delegate, so a rule scoped to either file leaves the other's
   button as a bare native button (the grid's Done cell shipped as a bordered box round the
   circle for exactly that reason; moved here 2026-09-11). */
.activity-done-toggle {
    display: inline-flex;
    align-items: center;
    justify-content: center;
    width: 1.4rem;
    height: 1.4rem;
    padding: 0;
    border: 0;
    background: transparent;
    color: #98a2b3;
    flex-shrink: 0;
}

.activity-done-toggle:hover,
.activity-done-toggle:focus-visible {
    color: #16a34a;
}

/* The open circle's ring is NEUTRAL on every row, including a red overdue one and a green
   "today" one (re-measured on the reference 2026-09-12, correcting the earlier reading that said
   the circle takes the row colour). The reference draws the ring as a fixed
   `box-shadow: rgba(33,35,44,.24) 0 0 0 1px inset` that does not depend on the row's text colour
   at all — only the ROW goes red/green/grey, never the control in it. Our ring is an SVG stroke,
   so it must be given that colour explicitly: `currentColor` is exactly what made it inherit the
   row and read as a red or green checkbox.
   Kept as its own rule rather than a `stroke` on the element so the hover state below can
   override one value. */
.activity-done-toggle .adt-ring {
    stroke: rgba(33, 35, 44, 0.24);
}

/* Hover/focus previews completing it: the RING turns green, the fill stays white — the
   reference's own hover, measured. The (0,3,1) selector repeats so this still wins inside a
   coloured row. */
.dg-table tr .activity-done-toggle:hover .adt-ring,
.dg-table tr .activity-done-toggle:focus-visible .adt-ring,
.activity-done-toggle:hover .adt-ring,
.activity-done-toggle:focus-visible .adt-ring {
    stroke: #16a34a;
}

/* The grid's Done cell centres its control under the header — the reference's Done lane is a
   flex row with `justify-content: center` (measured: a 49px cell, the 16px circle dead centre),
   and it is the only Activities column that is not left-aligned. Done HERE, inside the cell,
   rather than as a DataGrid per-column alignment knob: that knob was deliberately removed
   (2026-08-27) and the grid's only other centred lane, the selection checkbox, is grid-owned. */
.act-done-cell {
    display: flex;
    align-items: center;
    justify-content: center;
    width: 100%;
}

/* ===== Activity form (ActivityComposer, 2026-09-11) — geometry that lives on elements this app's
   shared components own, so it cannot be scoped to the composer =====
   .fm-modal.ac-modal: ModalForm's element. The reference's "Add activity" modal measured 850px
   wide and ~706px tall (a 553px form column + a 297px day calendar), docked to the top with the
   large modal's shadow; the body carries no padding because both columns pad and scroll
   themselves. */
/* .fm-backdrop prefix on purpose: ModalForm's own width rule is scoped (.fm-modal[b-…], same
   specificity as .fm-modal.ac-modal) and its bundle loads after app.css, so an unprefixed rule
   silently lost and the form rendered at the 500px default (found live, 2026-09-11). */
/* GROWS with its content (owner, 2026-09-16): a note typed line by line lengthens the modal, as the
   reference's does, instead of scrolling inside a fixed 706px box. Measured on the reference the same
   day: content-driven height from a floor (661px there; 706px kept here, the size this form already
   opened at), up to the full viewport, where the form column scrolls, the action bar stays pinned and
   the calendar scrolls itself. The ceiling is ModalForm's own max-height, calc(100vh - 16px). The
   calendar column must not size the modal: see .ac-shell--modal .ac-cal in ActivityComposer.razor.css. */
.fm-backdrop .fm-modal.ac-modal {
    width: 850px;
    max-width: calc(100vw - 24px);
    height: auto;
    min-height: min(706px, calc(100vh - 16px));
    border: none;
    box-shadow: var(--elev-overlay);
}

.fm-backdrop .fm-modal.ac-modal .fm-body {
    display: flex;
    flex: 1 1 auto;
    min-height: 0;
    padding: 0;
    overflow: hidden;
}

/* Link and participant rows (ActivityLinkField, ActivityUserChipsField): the reference's 32px link
   inputs with the icon inside the left edge (padding 4px 26px 4px 32px) and ✕ at the right. Global
   because two components share the look. */
.alf {
    position: relative;
    display: flex;
    flex-wrap: wrap;
    align-items: center;
    gap: 4px;
    min-height: 32px;
    padding: 3px 28px 3px 32px;
    border: 1px solid rgba(33, 35, 44, 0.14);
    border-radius: 8px;
    background: #fff;
    cursor: text;
}

.alf:focus-within,
.alf--open {
    border-color: #1b6ec2;
    box-shadow: 0 0 0 3px rgba(27, 110, 194, 0.15);
}

.alf--invalid {
    border-color: #dc3545;
}

.alf--disabled {
    background: #f8f9fa;
    cursor: default;
}

.alf-icon {
    position: absolute;
    top: 7px;
    left: 8px;
    display: inline-flex;
    color: #21232c;
}

/* ActivityFormIcon Name="azure" (2026-09-15): the Azure add-on's own mark — the rail's white-on-
   transparent PNG used as a MASK, so the glyph takes currentColor like the stroke icons beside it
   instead of the rail's white. It marks an activity's linked record while Azure records are the only
   ones that can be linked; a generic record icon replaces this name when another kind arrives.
   Global because the icon renders inside other components (ActivityLinkField, ActivityDrawerPanel). */
.afi-mask {
    display: inline-block;
    flex-shrink: 0;
    background-color: currentColor;
}

/* ActivityFormIcon's OTHER branch — the stroke icons. Same guarantee as .afi-mask above and as
   ActivityTypeIcon's .ati-icon: an <svg> is a shrinkable flex item, so a long value beside it in
   a form row would squeeze its box and scale the glyph down with it (2026-09-17). */
.afi-icon {
    flex-shrink: 0;
}

.afi-azure {
    -webkit-mask: url("/assets/icons/nav/azure.png") center / contain no-repeat;
    mask: url("/assets/icons/nav/azure.png") center / contain no-repeat;
}

/* The activity form's gutter tooltip (2026-09-18), measured on the reference's own: #21232c, white,
   12px / 18px at weight 450, 4px 8px, radius 8px, no arrow, no shadow. activity-composer.js
   (installGutterTips) places it 2px above the hovered icon, centred. Global because it lives on
   <body>, outside every component's CSS scope; above ModalForm (1300) and the drawer (950). */
.ac-gutter-tip {
    position: fixed;
    z-index: 1400;
    padding: 4px 8px;
    border-radius: 8px;
    background: #21232c;
    color: #fff;
    font-size: var(--fs-meta);
    font-weight: 450;
    line-height: 18px;
    white-space: nowrap;
    pointer-events: none;
}

.alf-value {
    flex: 1 1 auto;
    min-width: 0;
    overflow: hidden;
    color: #21232c;
    font-size: var(--fs-body);
    line-height: 24px;
    text-overflow: ellipsis;
    white-space: nowrap;
}

.alf-input {
    flex: 1 1 80px;
    min-width: 60px;
    height: 24px;
    padding: 0;
    border: 0;
    outline: none;
    background: transparent;
    color: #21232c;
    font-family: inherit;
    font-size: var(--fs-body);
}

.alf-input::placeholder,
.alf-placeholder {
    color: rgba(33, 35, 44, 0.48);
    font-size: var(--fs-body);
    line-height: 24px;
}

.alf-chip {
    display: inline-flex;
    align-items: center;
    gap: 4px;
    max-width: 100%;
    height: 22px;
    padding: 0 4px 0 8px;
    border: 1px solid rgba(33, 35, 44, 0.14);
    border-radius: 4px;
    background: #f5f5f6;
    color: #21232c;
    font-size: var(--fs-meta);
    font-weight: 600;
}

.alf-chip-text {
    min-width: 0;
    overflow: hidden;
    text-overflow: ellipsis;
    white-space: nowrap;
}

.alf-chip-x {
    display: inline-flex;
    align-items: center;
    justify-content: center;
    width: 16px;
    height: 16px;
    padding: 0;
    border: 0;
    border-radius: 3px;
    background: transparent;
    color: #21232c;
    cursor: pointer;
}

.alf-chip-x:hover {
    background: rgba(33, 35, 44, 0.1);
}

.alf-clear {
    position: absolute;
    top: 5px;
    right: 5px;
    display: inline-flex;
    align-items: center;
    justify-content: center;
    width: 20px;
    height: 20px;
    padding: 0;
    border: 0;
    border-radius: 4px;
    background: transparent;
    color: #21232c;
    cursor: pointer;
}

.alf-clear:hover {
    background: rgba(33, 35, 44, 0.08);
}

.alf-option-label {
    display: inline-flex;
    align-items: center;
    gap: 8px;
    min-width: 0;
}

.dg-popover.ac-field-popover {
    min-width: 320px;
    max-width: 480px;
}

/* The activity form's record list shows TEN rows before it scrolls (owner, 2026-09-16). The shared
   .gf-list cap of 320px holds only 9.87 of them once the list's own 0.15rem padding is counted, so
   the tenth row was always cut. 10 × the 32px row minimum + that padding, nothing more. */
.dg-popover.ac-field-popover .gf-list {
    max-height: calc(10 * 32px + 0.3rem);
}

/* Record drawer, single-column mode (RecordDrawer SingleColumn — the activity drawer): the body
   takes the full width and scrolls itself; the header holds a flush tab strip. */
.rd-columns--single .rd-col--left {
    flex: 1 1 100%;
    border-right: 0;
}

.rd-columns--single .rd-header {
    padding: 0;
}

.rd-scroll--bare {
    display: flex;
    flex-direction: column;
    padding: 0;
    overflow: hidden;
}

/* The activity drawer's top tabs, after the reference's own: the activity itself (active: light
   tint and an accent underline) and its linked record, equal widths, divided by rules. */
.rd-act-tabs {
    display: flex;
    align-items: stretch;
    min-height: 40px;
}

.rd-act-tab {
    display: inline-flex;
    flex: 1 1 0;
    align-items: center;
    gap: 8px;
    min-width: 0;
    padding: 0 16px;
    border-right: 1px solid var(--rd-rule);
    color: #65686f;
    font-size: var(--fs-body);
    text-decoration: none;
    white-space: nowrap;
}

.rd-act-tab:last-child {
    border-right: 0;
}

.rd-act-tab--active {
    background: #eff6ff;
    color: #1b6ec2;
    box-shadow: inset 0 -2px 0 #1b6ec2;
}

/* The title is the drawer's focus target on open; inside the active tab the tab itself already
   says where you are, so the focus box around the words would only look like a stray border. */
.rd-act-tab .rd-title:focus-visible {
    outline: none;
}

.rd-act-tab .rd-title,
.rd-act-tab-text {
    min-width: 0;
    overflow: hidden;
    color: inherit;
    font-size: var(--fs-body);
    font-weight: 450;
    text-overflow: ellipsis;
    white-space: nowrap;
}

a.rd-act-tab:hover {
    background: #f5f5f6;
    color: #21232c;
}

.rd-act-tab--muted {
    color: #98a2b3;
}

/* The week calendar's two popovers (quick add, entry details — 2026-09-14, BACKLOG 144). This is
   only the DismissiblePanel SHELL: its element is rendered by DismissiblePanel, so no component's
   scoped stylesheet can reach it, and all it needs is the popover layer. Everything visible is the
   component's own inner element (CalendarQuickAddPopover / CalendarEntryPopover .razor.css). */
.cal-popover {
    z-index: 1100;
}
