/* ═══════════════════════════════════════════════════════════════
   CorpComPrint Theme — matches Figma design language
   Primary: #256F95 (StyleLibrary Primary-600, --brand-primary) — teal retired 2026-07-11
   (Brian ruling); hue value updated to the client's authored StyleLibrary ramp 2026-09-01
   (Duane, architecture/DESIGN-SYSTEM.md) — structure unchanged, still one primary app-wide.
   Secondary: #002C56 (navy, --brand-secondary)
   ═══════════════════════════════════════════════════════════════ */

body { font-family: var(--brand-font-family); color: var(--brand-text); background: var(--brand-surface); margin: 0; }

/* ── Focused page heading (O-7, 2026-09-19) ──────────────────────────────────
   Every app's Routes.razor carries <FocusOnNavigate Selector="h1" /> — an accessibility feature: on every
   navigation the new page's <h1> is given a programmatic tabindex="-1" focus so a screen reader announces it.
   Left unstyled, the browser's default focus ring draws around the heading after every page change (a dark
   box around the title, 02-customer-account-management.draft.md O-7). Reused verbatim from the one place this was
   already fixed, src/CCP.Shell/wwwroot/app.css's own h1:focus rule (now removed there — this is its one home).
   Placed here, not in a shared-*.css layer file: SHARED-CSS-LAYER-SPLIT.md's own split rule only routes an
   `.app-*` DESIGN-SYSTEM class into a shared-*.css file by JOB (page chrome, dialogs, grids, …); a bare `h1`
   element selector fixing a cross-app accessibility default fits none of those seven jobs, and every one of
   the three apps already links theme.css (App.razor in each), so it is the one file already reaching all three
   without inventing an eighth shared file for a single two-line rule.
   Scope is inherently the programmatically-focused case ONLY, never an interactive control: a plain <h1> is not
   in the tab order and cannot receive focus any other way, so this can never suppress a button/link/input's own
   focus-visible ring (those all use :focus-visible above, deliberately left alone). No token needed — `none` is
   not a colour. */
h1:focus {
    outline: none;
}

/* ── Utility classes ─────────────────────────────────────────── */

.text-brand-primary { color: var(--brand-primary) !important; }

/* ── Shared list-filter search box (DESIGN-SYSTEM.md §4.32 unification, Francie 2026-07-14) ────
   ONE pattern for an in-page "filter what you're looking at" search box — Work Kanban and Quotes Inbox
   both had their own bespoke input styling before this; converged on the same Bootstrap input-group +
   icon chrome the header's global HeaderSearch.razor already uses (icon span + form-control input), so
   there is exactly one search-box look site-wide. This is NOT the global cross-entity HeaderSearch
   widget itself (that one debounces against a search index and shows a results dropdown) — it's just the
   shared VISUAL treatment for a synchronous client-side list filter; each screen keeps its own filter
   logic. Reused via the `.list-search` wrapper class; no Blick should define its own search-input CSS. */
.list-search .input-group-text { background: var(--brand-surface); }
.list-search input:focus-visible { outline: 2px solid var(--brand-primary); outline-offset: 2px; }

/* ── Shared "count" badge (DESIGN-SYSTEM.md §4.32 unification, Francie 2026-07-14) ─────────────
   Bootstrap's own .badge + bg-* utilities (already theme-mapped below) are the ONE badge treatment reused
   everywhere a small status/count pill is needed (Executive Dashboard alert counts, PricingRules' version
   "Current" marker, Work Kanban's per-column counts) — no per-Blick bespoke badge CSS. */

/* DESIGN-SYSTEM.md §4.3b (Duane, 2026-09-07, ledger L-54): which label colour a solid `badge bg-*` fill needs
   depends on the fill's VALUE, and the value is the host's alone (a Blick may assume token NAMES, never
   VALUES) — so the pairing lives here, not as a `text-dark`/`text-white` a consumer writes beside `bg-warning`.
   White (Bootstrap's own .badge default) on --brand-warning #CE8B27 measures ~2.8:1, below the 4.5:1 floor;
   --brand-text #222729 on it measures ~5.4:1.
   §4.3b's owed one-line check, recorded 2026-09-07 (Francie) — a measurement, not a ruling: white on
   --brand-success #197649 (bg-success) measures ~5.6:1; white on --brand-primary #256F95 (bg-info and
   bg-primary both resolve to this same value) measures ~5.5:1. Both already clear the 4.5:1 floor (§6) via
   --brand-text-on-brand (§6), so bg-success/bg-info/bg-primary need no swap — only bg-warning did. */
.badge.bg-warning {
    color: var(--brand-text);
}

/* ── Bootstrap 5.3 theme mapping: every Bootstrap component that reads its own CSS vars
   (badges, alerts' subtle/emphasis vars it does NOT use directly — see the .alert-* blocks below —
   links, body text/bg, borders) picks up brand colors automatically once these are set. Components
   Bootstrap compiled with literal hex/rgba (.form-control:focus, .form-select:focus) get their own
   blocks further down since a :root var can't reach into a hardcoded declaration. ── */
:root {
    --bs-primary: var(--brand-primary);
    --bs-primary-rgb: var(--brand-primary-rgb);
    --bs-success: var(--brand-success);
    --bs-success-rgb: var(--brand-success-rgb);
    --bs-danger: var(--brand-danger);
    --bs-danger-rgb: var(--brand-danger-rgb);
    --bs-warning: var(--brand-warning);
    --bs-warning-rgb: var(--brand-warning-rgb);
    --bs-info: var(--brand-info);
    --bs-info-rgb: var(--brand-info-rgb);

    --bs-link-color: var(--brand-primary);
    --bs-link-color-rgb: var(--brand-primary-rgb);
    --bs-link-hover-color: var(--brand-primary-hover);
    --bs-link-hover-color-rgb: var(--brand-primary-hover-rgb);

    --bs-body-color: var(--brand-text);
    --bs-body-bg: var(--brand-surface);
    --bs-body-font-family: var(--brand-font-family);
    --bs-border-color: var(--brand-border);
    --bs-border-radius-sm: var(--brand-radius-sm);
    --bs-border-radius: var(--brand-radius-md);
    --bs-border-radius-lg: var(--brand-radius-lg);

    --b3-grid-focus-ring: var(--brand-primary);
}

/* Alert variants actually used in markup (alert-danger/-warning/-info/-secondary) — Bootstrap's
   .alert-* rules set --bs-alert-color/bg/border-color from *-text-emphasis/*-bg-subtle/*-border-subtle
   vars we haven't touched, so each used variant needs its own override rather than a :root one. */
.alert-danger { --bs-alert-color: var(--brand-danger-text); --bs-alert-bg: var(--brand-danger-bg); --bs-alert-border-color: var(--brand-danger); }
.alert-warning { --bs-alert-color: var(--brand-warning-text); --bs-alert-bg: var(--brand-warning-bg); --bs-alert-border-color: var(--brand-warning); }
.alert-info { --bs-alert-color: var(--brand-info-text); --bs-alert-bg: var(--brand-info-bg); --bs-alert-border-color: var(--brand-info); }
.alert-secondary { --bs-alert-color: var(--brand-neutral-text); --bs-alert-bg: var(--brand-neutral-bg); --bs-alert-border-color: var(--brand-border-strong); }

/* form-control/form-select compiled their focus ring as literal hex/rgba, not vars — override directly. */
.form-control:focus, .form-select:focus {
    border-color: var(--brand-primary);
    box-shadow: 0 0 0 0.25rem color-mix(in srgb, var(--brand-primary) 25%, transparent);
}

/* ── Buttons: re-skin Bootstrap's own .btn-primary / .btn-outline-primary via their
   per-button CSS vars, instead of the old .btn-ccc-primary / .btn-outline-ccc classes. ── */

/* Solid fill = Primary-700 (--brand-primary-hover), hover/active = Primary-800
   (--brand-primary-active, NEW token) — DESIGN-SYSTEM.md §4.1. Clicked matches hover
   (the sheet shows identical hover/clicked treatment). Disabled = Primary-50, no border.
   --bs-btn-disabled-color was left at Bootstrap's default white (both inspectors flagged this 2026-09-01):
   white text is unreadable on Primary-50 at ANY colour choice once Bootstrap's own
   --bs-btn-disabled-opacity (0.65) fades the whole button toward the page background — measured, every
   design-system dark-text token still lands under 4:1 once that fade is included (--brand-text only reaches
   3.94:1, --brand-primary-hover 2.61:1; the disabled state has no candidate that clears 4.5:1 with the fade
   left on). The fade is redundant here anyway: Primary-50 is already visually distinct from the Primary-700
   solid fill on its own, so disabling the fade for THIS button only (not a global Bootstrap change) and
   setting the label to Primary-700 gets 5.09:1 — comfortably over the floor — while the button still reads as
   disabled purely from its own (unfaded) fill colour, which is the part both inspectors said to keep. */
.btn-primary {
    --bs-btn-bg: var(--brand-primary-hover);
    --bs-btn-border-color: var(--brand-primary-hover);
    --bs-btn-hover-bg: var(--brand-primary-active);
    --bs-btn-hover-border-color: var(--brand-primary-active);
    --bs-btn-active-bg: var(--brand-primary-active);
    --bs-btn-active-border-color: var(--brand-primary-active);
    --bs-btn-disabled-bg: var(--brand-primary-light);
    --bs-btn-disabled-border-color: transparent;
    --bs-btn-disabled-color: var(--brand-primary-hover);
    --bs-btn-disabled-opacity: 1;
}

.btn-outline-primary {
    --bs-btn-color: var(--brand-primary);
    --bs-btn-border-color: var(--brand-primary);
    --bs-btn-hover-bg: var(--brand-primary);
    --bs-btn-hover-border-color: var(--brand-primary);
    --bs-btn-active-bg: var(--brand-primary);
    --bs-btn-active-border-color: var(--brand-primary);
    --bs-btn-disabled-color: var(--brand-primary);
    --bs-btn-disabled-border-color: var(--brand-primary);
}

.btn-primary, .btn-outline-primary {
    --bs-btn-border-radius: var(--brand-radius-md);
}

.btn-primary:focus-visible, .btn-outline-primary:focus-visible {
    box-shadow: 0 0 0 0.25rem color-mix(in srgb, var(--brand-primary) 50%, transparent);
}

/* ── One control height (Olga, 2026-10-07; architecture/DESIGN-SYSTEM.md §4.18f) ──────────────
   Every single-line control has the same outer height, in forms and table rows alike, so a row of controls reads as one
   line. min-height, never height: an overlong label wraps instead of clipping. -sm variants keep their smaller font and
   horizontal padding and lose only the shorter height. Excluded on purpose: <textarea> (multi-row), checkboxes and radios,
   .btn-check segmented tracks (their own 32px + track padding is already 40px overall), the pager buttons (32px, §4.18a),
   .btn-link, and the Syncfusion grid pager (its page-size box stays level with the 32px page buttons, §4.18a). A glyph-only .btn
   is NOT excluded: it lands on 40px, which is the same 40px §4.18a already asks of a glyph-only action, so the two rules agree.
   Selector shape: :where() keeps every exclusion at zero specificity, so each rule stays as specific as Bootstrap's own
   single class and a Blick's own class rule can still adjust the control. No bare input/select element selectors
   (shared-forms.css records why they out-rank class selectors on already-styled controls). Loads after bootstrap.min.css. */
.form-control:where(:not(textarea)),
.form-select,
.form-control-sm:where(:not(textarea)),
.form-select-sm,
.input-group-text,
.input-group-sm > * {
    min-height: var(--brand-control-height);
}

.btn:where(:not(.btn-link, .list-pager__page, .btn-check + .btn)) {
    min-height: var(--brand-control-height);
    display: inline-flex;
    align-items: center;
    justify-content: center;
    /* inline-flex drops the literal space between an icon and its text ("<i …></i> New Agreement"); the gap puts it back. */
    gap: var(--brand-space-1);
}

/* Syncfusion: the group sets the height; the inner input inside it must not add its own on top. */
.e-input-group:not(.e-multi-line-input),
.e-input-group.e-control-wrapper:not(.e-multi-line-input),
.e-ddl.e-input-group,
.e-date-wrapper,
.e-datetime-wrapper,
.e-numeric.e-input-group {
    min-height: var(--brand-control-height);
}

input.e-input {
    min-height: var(--brand-control-height);
}

/* The grid pager's page-size dropdown sits in the pager strip beside 32px page buttons, so it keeps Syncfusion's own height. */
.e-pager .e-input-group,
.e-pager .e-ddl.e-input-group,
.e-pager input.e-input {
    min-height: 0;
}

.e-input-group input.e-input,
.e-input-group.e-control-wrapper input.e-input {
    height: auto;
    min-height: 0;
}

/* ── KPI Cards ───────────────────────────────────────────────── */
/* .kpi-card/.kpi-label/.kpi-value/.kpi-change are GONE (Story 16's tile rider, D-0183 route): the Executive
   Dashboard and the Dev State page ("Home") converged onto the Shell's shared `.app-stat-tile` family
   (`CCP.Shell/wwwroot/css/shared.css`), the same widget the Finance Blick's `.fin-summary` already was. */

/* Shared demoted content-section header — a sub-header WITHIN a page, below that page's own full-size <h1>.
   Brian, 2026-08-19 (narrowed 2026-09-02): page titles are plain <h1>s styled by app.css's `.content h1`, so
   no page title carries this class; the remaining consumer is VendorDetails' genuine <h2> sub-header
   (ProcurementRequestForQuoteDetail's title became an <h1> 2026-09-02). */
.content-section-title { font-size: var(--brand-font-size-lg); font-weight: var(--brand-font-weight-semibold); color: var(--brand-text); margin-bottom: var(--brand-space-3); }

/* VendorDetails.razor's Purchase Orders tab hosts BD.VendorContext.Blick's VendorPurchaseOrders component
   through IBlick.GetWidgets() + DynamicComponent — a different assembly's markup, not written in any .razor
   file in this repo, so it can't carry a Shell scoped-CSS attribute. Its Status column links out to
   /purchase-orders/{id}, a different section of the app, and needs the same leaves-this-page cue
   DeepLinkButton.razor uses elsewhere (a trailing arrow icon) — global, not scoped, and deliberately coupled to
   the Blick's own ".vendor-purchase-orders__table" class name, the only hook this cross-repo boundary offers
   without editing BD.Vendor's code (Will's UX review, 2026-08-01). */
.vendor-purchase-orders__table a::after {
    font-family: "Font Awesome 6 Free";
    font-weight: 900;
    content: "\f061";
    font-size: 0.7em;
    margin-left: var(--brand-space-1);
}

/* ── Badges ──────────────────────────────────────────────────── */

.badge-status { display: inline-block; white-space: nowrap; font-size: var(--brand-font-size-xs); padding: var(--brand-space-1) var(--brand-space-2); border-radius: var(--brand-radius-pill); font-weight: var(--brand-font-weight-medium); }

/* Catalog Product lifecycle (Active/Draft/Archived) — DESIGN-SYSTEM.md §4.3a, ledger row L-43. The status word
   stays inside the pill; the colour is a second reading of a fact the text already carries, never the only
   carrier of it. */
.badge-status--active { background: var(--brand-status-active-bg); color: var(--brand-status-active-text); }
.badge-status--draft { background: var(--brand-status-draft-bg); color: var(--brand-status-draft-text); }
.badge-status--archived { background: var(--brand-status-archived-bg); color: var(--brand-status-archived-text); }
.badge-status--pending-invitation { background: var(--brand-status-pending-invitation-bg); color: var(--brand-status-pending-invitation-text); }

/* Address Book "Default Shipping/Billing Address" label (01-address-book-shipment-tracking.draft.md AB.2 row 2); the storefront "Kit" label shares it. */
.badge-status--default-address,
.badge-status--kit { background: var(--brand-status-default-address-bg); color: var(--brand-status-default-address-text); }

/* Vendor dashboard row badges (DESIGN-SYSTEM.md §4.3a, vendor-portal U4/U5) — same modifier shape as the three
   above, consuming the reference-only token pairs declared beside .badge-status--in-production's tokens in
   brand.css. The in-production token pair already existed; only its modifier line was missing. */
.badge-status--pending-quote { background: var(--brand-status-pending-quote-bg); color: var(--brand-status-pending-quote-text); }
.badge-status--pending-review { background: var(--brand-status-pending-review-bg); color: var(--brand-status-pending-review-text); }
.badge-status--approved { background: var(--brand-status-approved-bg); color: var(--brand-status-approved-text); }
.badge-status--assigned { background: var(--brand-status-assigned-bg); color: var(--brand-status-assigned-text); }
.badge-status--in-production { background: var(--brand-status-in-production-bg); color: var(--brand-status-in-production-text); }
/* U1 (invoice-submission-payment-tracking mockup, wave 2 F7): the vendor-facing "Paid" status pill. */
.badge-status--paid { background: var(--brand-status-paid-bg); color: var(--brand-status-paid-text); }

/* mockup-spec-vendor-rfq-response.md §3.2's status -> badge-variant table (Quoted/Awarded/Not Awarded); Pending
   Quote already has .badge-status--pending-quote above. */
.badge-status--quoted { background: var(--brand-status-quoted-bg); color: var(--brand-status-quoted-text); }
.badge-status--awarded { background: var(--brand-status-awarded-bg); color: var(--brand-status-awarded-text); }
.badge-status--not-awarded { background: var(--brand-status-not-awarded-bg); color: var(--brand-status-not-awarded-text); }
/* A vendor's own "Decline to quote" (02-rfq-response-job-management.draft.md Story 3 "Screen criteria") -- the neutral pair by reference. */
.badge-status--declined { background: var(--brand-status-declined-bg); color: var(--brand-status-declined-text); }

/* Staff Service RFQ request status (02-vendor-management-rfq.draft.md Story 3 §S3.3, G-4) -- Issued is new;
   Awarded reuses .badge-status--awarded above, Draft reuses .badge-status--draft, Cancelled is added here (its
   token pair already existed, unconsumed by any modifier until now). */
.badge-status--issued { background: var(--brand-status-issued-bg); color: var(--brand-status-issued-text); }
.badge-status--cancelled { background: var(--brand-status-cancelled-bg); color: var(--brand-status-cancelled-text); }

/* QuickBooks Online connection lifecycle (story_quickbooks-intuit-standard-spec.md §9). Revoked reuses
   .badge-status--cancelled above -- no fourth modifier -- the word "Revoked" stays in the pill, only the
   class is shared. */
.badge-status--connected { background: var(--brand-status-connected-bg); color: var(--brand-status-connected-text); }
.badge-status--not-connected { background: var(--brand-status-not-connected-bg); color: var(--brand-status-not-connected-text); }
.badge-status--expired { background: var(--brand-status-expired-bg); color: var(--brand-status-expired-text); }

/* Payment, stock, quote and invoice-notice lifecycles (DESIGN-SYSTEM.md §4.3, Olga 2026-10-07): one modifier per named status. */
.badge-status--unpaid { background: var(--brand-status-unpaid-bg); color: var(--brand-status-unpaid-text); }
.badge-status--partly-paid { background: var(--brand-status-partly-paid-bg); color: var(--brand-status-partly-paid-text); }
.badge-status--in-stock { background: var(--brand-status-in-stock-bg); color: var(--brand-status-in-stock-text); }
.badge-status--low-stock { background: var(--brand-status-low-stock-bg); color: var(--brand-status-low-stock-text); }
.badge-status--out-of-stock { background: var(--brand-status-out-of-stock-bg); color: var(--brand-status-out-of-stock-text); }
.badge-status--sent { background: var(--brand-status-sent-bg); color: var(--brand-status-sent-text); }
.badge-status--revision-requested { background: var(--brand-status-revision-requested-bg); color: var(--brand-status-revision-requested-text); }
.badge-status--converted { background: var(--brand-status-converted-bg); color: var(--brand-status-converted-text); }
.badge-status--rejected { background: var(--brand-status-rejected-bg); color: var(--brand-status-rejected-text); }
.badge-status--notice-due { background: var(--brand-status-notice-due-bg); color: var(--brand-status-notice-due-text); }
.badge-status--overdue { background: var(--brand-warning-bg); color: var(--brand-warning-text); }

/* Work order lifecycle pills on the Accounts Payable bill rows (finance Story 3) -- the token pairs already exist in
   brand.css (declared for the Kanban stage badges), only the modifiers were missing. */
.badge-status--created { background: var(--brand-status-created-bg); color: var(--brand-status-created-text); }
.badge-status--print-ready { background: var(--brand-status-print-ready-bg); color: var(--brand-status-print-ready-text); }
.badge-status--production-completed { background: var(--brand-status-production-completed-bg); color: var(--brand-status-production-completed-text); }
.badge-status--rework-qa-failed { background: var(--brand-status-rework-qa-failed-bg); color: var(--brand-status-rework-qa-failed-text); }
.badge-status--ready-for-shipment { background: var(--brand-status-ready-for-shipment-bg); color: var(--brand-status-ready-for-shipment-text); }
.badge-status--shipped { background: var(--brand-status-shipped-bg); color: var(--brand-status-shipped-text); }
.badge-status--delivered { background: var(--brand-status-delivered-bg); color: var(--brand-status-delivered-text); }
.badge-status--on-hold { background: var(--brand-status-on-hold-bg); color: var(--brand-status-on-hold-text); }

/* ── Misc ─────────────────────────────────────────────────────── */

.clickable { cursor: pointer; }
.section-title { font-size: var(--brand-font-size-sm); font-weight: var(--brand-font-weight-bold); text-transform: uppercase; letter-spacing: 0.05em; color: var(--brand-text-muted); margin-bottom: var(--brand-space-3); }

/* L-168 (Will, 2026-09-16): the bare .tabular-nums class is used 47 times across the repo (tile figures, grid
   date/amount cells, dialog dates) and had NO stylesheet rule anywhere -- every one of those digit columns
   rendered with proportional figures despite spec §2.5's "tabular-nums on every column of digits". Token-free
   by design, so it holds no matter which of the seven shared-*.css layers or theme.css/brand.css a caller's
   markup lives in -- this is a REPO-WIDE utility, not scoped to one screen or one layer. */
.tabular-nums { font-variant-numeric: tabular-nums; }

/* story_quickbooks-intuit-standard-spec.md §9: the request log's Path cell is monospaced so a PASS/FAIL read
   (`?code&realmId&state` vs `?code=...`) is read character by character -- a proportional face makes an `=`
   next to a long token easy to skim past. Same shape of token-free, single-declaration utility as
   .tabular-nums immediately above; --brand-font-mono already exists (shell-chrome.css's own credential-value
   consumer). */
.font-mono { font-family: var(--brand-font-mono); }

/* story_quickbooks-intuit-standard-spec.md §9: an unbroken long address (a request path with a long token) wraps inside its table cell instead of scrolling the table sideways. Token-free single declaration, same shape as .font-mono above. */
.wrap-anywhere { overflow-wrap: anywhere; }

/* Template Editor rules removed 2026-09-06 as unreachable (Will's rendered-truth inspection): nothing under
   src/ emits .canvas-document, .zone-label, .resize-handle, their sibling editor-* / template-zone / zone-*
   classes, or the Properties Panel set (.editor-properties, .prop-*, .zone-list-item) -- only
   demo_poc/CorpComPrint.Web used this markup, and it is not in the solution and does not ship. These come
   back only if a Template Editor screen is built inside src/. */
/* The shared list pager (.list-pager*) lives in CCP.UI.Components/wwwroot/list-pager.css, linked by each host that renders it. */

/* ── Shared hand-built table treatment (DESIGN-SYSTEM.md §4.32 unification, Francie 2026-09-02) ────────────────────────────
   DESIGN-SYSTEM-GRIDS-AND-DIALOGS.md §4b, step 2 of Brian's 2026-09-01 Finance-grid ruling: makes a hand-built
   <table> visually indistinguishable from the platform DataGrid's own row/header treatment (§4 of the same
   spec) without becoming one — no toolbar, no per-column filter row, no built-in Add/Delete. Every hand-built
   Finance table swaps `table table-striped fin-grid` for `table app-table`; the Blick-local `.fin-grid*` family
   this replaces (CCP.FinanceContext.Blick/wwwroot/blick.css) is deleted outright, not aliased (DESIGN-SYSTEM.md §4.32 / D-0183
   §3: the replacement is Shell-owned). Shared vocabulary, so any future hand-built table adopts it too. */
.app-table { width: 100%; }
/* table.app-table, not just .app-table, so this reliably outranks app.css's pre-existing `table.table th`
   (same specificity, loaded later, so it was winning the tie and painting every header navy/semibold instead
   of this spec's muted grey/medium -- caught 2026-09-02 by measuring the rendered header's computed style). */
table.app-table thead th {
    color: var(--brand-text-muted);
    font-weight: var(--brand-font-weight-medium);
    font-size: var(--brand-font-size-sm);
    border-bottom: 1px solid var(--brand-border-strong);
}
table.app-table tbody td, table.app-table tbody th {
    padding-top: var(--brand-space-3);
    padding-bottom: var(--brand-space-3);
    border-bottom: 1px solid var(--brand-border);
    color: var(--brand-text);
}
/* No striping: no background-color override here, on purpose -- Bootstrap 5.3 already gives every <td> its own
   background-color: var(--bs-table-bg) (default transparent), so leaving it alone is what removes the striping
   the `table-striped` class used to add. A literal `background: transparent` here would win the cascade over
   that mechanism and permanently block --bs-table-bg from ever painting anything -- including the warning tint
   below and hover, which both depend on it (caught 2026-09-02: an overdue row's tint silently never rendered). */
.app-table tbody tr:hover {
    --bs-table-bg: var(--brand-primary-light);
}
.app-table__cell--number { text-align: right; font-variant-numeric: tabular-nums; }

/* Olga, 2026-09-24: "date columns should be right-aligned on all grids, just like money columns." Same rule,
   same treatment, as a sibling class rather than folded into --number -- a date is not a quantity, and this
   keeps a future reader free to give the two different rules again without splitting one class in half. */
.app-table__cell--date { text-align: right; font-variant-numeric: tabular-nums; }

/* A subordinate fact inside one cell -- a terminal-state marker, a "written off" note -- its own line, its own
   muted small type, never a coloured badge (D-0076). Ported 1:1 from the deleted .fin-grid__marker. */
.app-table__marker {
    display: block;
    font-size: var(--brand-font-size-sm);
    color: var(--brand-neutral-text);
}

/* mockup-spec-vendor-rfq-response.md U3 -- a genuinely NEW variant, not a reuse of .app-table__marker above:
   that one is deliberately colourless (D-0076), and the deadline urgency mark is specified as a soft-danger
   pill sitting inline right after the date, in the same cell. Existing semantic tokens only, no new ones. */
.app-table__urgency {
    display: inline-block;
    margin-left: var(--brand-space-2);
    padding: var(--brand-space-1) var(--brand-space-2);
    border-radius: var(--brand-radius-pill);
    font-size: var(--brand-font-size-xs);
    font-weight: var(--brand-font-weight-medium);
    background: var(--brand-danger-bg);
    color: var(--brand-danger-text);
}

/* A live figure/entry control inside a number column that needs its own fixed sizing beyond plain right-aligned
   text (e.g. an amount-entry input). Ported 1:1 from the deleted .fin-grid__amount. */
.app-table__amount {
    min-width: 8rem;
    text-align: right;
    display: inline-block;
    font-variant-numeric: tabular-nums;
}

/* A real <button> sortable column header, reachable by keyboard. Ported from the deleted .fin-grid__sort. The
   button's own label text stays color: inherit -- it must keep matching the muted header-label colour every
   other <th> already carries (--brand-text-muted, from .app-table thead th above) -- but §4b names the CARET
   as its own, separate colour pair (rest --brand-text-subtle, active --brand-primary), which color: inherit
   alone can never produce since it paints the whole button one colour. That gap (Carrie, 2026-09-02) is why the
   caret span gets its own rule below, keyed off aria-sort on the parent <th> for the active state (AriaSort()
   in each screen's code-behind sets "ascending"/"descending" only on the sorted column, "none" everywhere
   else) rather than a class on the button -- the same fact assistive tech reads is what drives the colour. */
.app-table__sort {
    background: none;
    border: 0;
    padding: 0;
    font: inherit;
    color: inherit;
    cursor: pointer;
}
.app-table__sort span[aria-hidden="true"] {
    color: var(--brand-text-subtle);
}
th[aria-sort="ascending"] .app-table__sort span[aria-hidden="true"], th[aria-sort="descending"] .app-table__sort span[aria-hidden="true"] {
    color: var(--brand-primary);
}
.app-table__sort:focus-visible {
    outline: 2px solid var(--brand-primary);
    outline-offset: 2px;
}

/* ── Icon-only row action for a hand-built table (.app-table__action) ────────────────────────────────────
   git show 56bad265:backlog/MOCKUP-SPEC-S85-DIALOG-CHROME-AND-PAGER.md §5.1 / DESIGN-SYSTEM-GRIDS-AND-DIALOGS.md §4b-i, Difference 1:
   the platform grid's own row actions paint a bare icon outline, no button surface -- Finance's hand-built
   tables drew a grey filled rounded button around the glyph instead. "Icon-only" means the glyph is the only
   thing PAINTED, not merely the only content, so Finance drops the resting fill/border here; the platform
   grid is unchanged. 40px is a literal, not a --brand-* token, matching how .app-table__sort is written --
   the spacing scale has no 40px step (DESIGN-SYSTEM.md §4.18a, the hit-target floor). No transition: §2.7,
   no motion is added. */
.app-table__action {
    display: inline-flex;
    align-items: center;
    justify-content: center;
    min-width: 40px;
    min-height: 40px;
    background: transparent;
    border: 0;
    border-radius: var(--brand-radius-md);
    padding: 0;
    color: var(--brand-text-muted);
    cursor: pointer;
}

.app-table__action:hover,
.app-table__action:focus-visible {
    color: var(--brand-primary);
}

.app-table__action:focus-visible {
    box-shadow: 0 0 0 4px color-mix(in srgb, var(--brand-primary) 30%, transparent);
}

.app-table__action:disabled {
    color: var(--brand-text-subtle);
    cursor: default;
}

/* Row-level warning tint (e.g. an overdue bill) -- sets --bs-table-bg so Bootstrap 5.3's own per-cell
   background-color inherits it, the same mechanism .fin-grid__row--overdue used. Ported 1:1. */
.app-table__row--warning {
    --bs-table-bg: var(--brand-warning-bg);
}

/* Hover on a warning row needs its own rule, and must come after both: the plain hover rule above is the
   more specific selector, so without this an overdue row LOSES its amber the instant the pointer lands on it --
   row state beaten by pointer position, at the one moment the state matters most (Carrie, 2026-09-02). A mid
   warning tone keeps both signals: still visibly warm, still visibly hovered. */
.app-table tbody tr.app-table__row--warning:hover {
    --bs-table-bg: var(--brand-warning-mid-bg);
}

/* The words inside a warning-tinted cell (e.g. "Days Overdue") pair with --brand-warning-text, never with
   Bootstrap's own .text-warning (--brand-warning) -- that solid value measures ~3.0:1 against
   --brand-warning-bg, below the 4.5:1 floor DESIGN-SYSTEM.md §4.3b already measured against it (L-101). */
.app-table__cell--warning-text {
    color: var(--brand-warning-text);
}

/* ── Pinned Actions column for a hand-built table (git show 7f0616da:backlog/MOCKUP-SPEC-NARROW-WIDTH-GRIDS.md §3.2) ──────────────────
   The scroll region is what stops a wide table widening the PAGE -- the .app-table twin of shared.css's
   `.content .e-grid { max-width: 100% }`, which keys off .e-grid and so never covered hand-built tables.
   The pin is the load-bearing half: card #234 measured a scroll-only fix on Inventory Stock and found the
   recovery scrollbar landed below the fold, so the actions were scrollable in theory and unreachable in fact.

   STATUS 2026-09-30: .app-table-scroll is wrapped around hand-built tables in 17 .razor files (Finance Dunning
   Queue among them), so it is live and must not be deleted on an unreferenced-class sweep. A scroll container
   that sets only overflow-x computes overflow-y as auto too, so any small bottom overflow (a focus outline, the
   table's own margin-bottom) gave the wrapper a second vertical scrollbar beside the page's: overflow-y is
   hidden here, and the small bottom padding keeps the last row's focus ring from being clipped. */
.app-table-scroll {
    max-width: 100%;
    overflow-x: auto;
    overflow-y: hidden;
    overscroll-behavior-x: contain;
    padding-bottom: var(--brand-space-1);
}

/* An opaque backing, set through --bs-table-bg and NEVER through `background`/`background-color`.
   theme.css's own comment above .app-table records why: Bootstrap 5.3 paints every cell
   `background-color: var(--bs-table-bg)`, and a literal background here would win the cascade and permanently
   block that variable -- killing both the row hover tint and .app-table__row--warning's amber, exactly the
   regression caught on 2026-09-02. Setting the variable on the TABLE (not the row, not the cell) keeps every
   existing rule working untouched: a row's own --bs-table-bg (hover, warning) is nearer the cell than the
   table's is, so it still wins by plain inheritance with no specificity contest to lose.
   --brand-surface-page-bg, not --brand-surface: an .app-table sits directly on the page carpet (#F9F9FB) with
   no card behind it, so this value is what those rows already paint today and the declaration changes nothing
   visible. A future .app-table placed on a white card overrides --bs-table-bg on that one table. */
table.app-table {
    --bs-table-bg: var(--brand-surface-page-bg);
}

/* Will's FAIL, git show 7f0616da:backlog/MOCKUP-SPEC-NARROW-WIDTH-GRIDS.md §3.1a recheck (2026-09-04): the pinned cell's own natural
   width is reserved in the table's/grid's total scroll width, but `position: sticky` VISUALLY relocates it to
   whatever real column happens to sit at the scroll container's right edge -- at rest, that is always some
   real data cell (Ageing measured at 1920/1280), and the pinned cell's opaque paint sliced its text mid-word
   with no ellipsis and no signal anything was cut. Widening the pinned cell's own reserved space cannot fix
   this generically: `.app-grid--pinned-actions` is shared by every consumer
   (CustomerDirectory's three commands and InventoryAllocations'
   own row measured 214px vs. Accounts Receivable's 100px) -- a fixed width or gutter sized for one consumer's
   command count would starve or overflow another's. A `mask-image` fade over the pinned cell's OWN leading
   24px needs no knowledge of that width: at its 0px edge the pinned cell paints nothing, so the real column
   underneath (Ageing, or whatever else the current scroll position exposes) shows through at full opacity
   instead of being sliced; by 24px the pinned cell is fully opaque again, still with an unbroken hit target for
   its own button(s), which sit well clear of that leading sliver in every consumer measured. This is graphical
   masking, not a `background`/`background-color` declaration -- the `--bs-table-bg` inheritance and the
   `--brand-surface` rule below are both untouched, so the row hover tint and the amber warning row still win by
   the same plain inheritance the comment above records. */
.app-table__cell--actions {
    position: sticky;
    right: 0;
    z-index: 1;
    border-left: 1px solid var(--brand-border);
    -webkit-mask-image: linear-gradient(to right, transparent, black 24px);
    mask-image: linear-gradient(to right, transparent, black 24px);
}

/* ── Pinned Actions column for a platform grid (git show 7f0616da:backlog/MOCKUP-SPEC-NARROW-WIDTH-GRIDS.md §3.3) ─────────────────────
   Opt-in via a wrapper class, because the pin is only correct where the LAST column holds row actions.
   --brand-surface here, not --brand-surface-page-bg as the hand-built table uses: §4's grid rows are flat
   white on the grid's own surface, and the pinned cell's backing must match the rows it belongs to. */
/* Same leading-edge mask as .app-table__cell--actions above, and for the same reason: this class is shared by
   Accounts Receivable (one command, ~100px), CustomerDirectory (three commands, measured 214px) and
   InventoryAllocations, so the fix cannot assume any one consumer's pinned-column width. */
/* The filter-row cell over the command column is a <td class="e-filterbarcell"> (Syncfusion renders a TD there, not a
   TH), so the th:last-child rule in shell-chrome.css never matched it. Left static, it stayed at its natural x while
   the header and body cells above and below it were pinned right, shifting every filter box one column right at
   narrow widths. Same pin, same backing, same leading-edge mask as its column. */
.app-grid--pinned-actions .e-grid .e-headercontent tr > th:last-child,
.app-grid--pinned-actions .e-grid tr > td.e-filterbarcell:last-child,
.app-grid--pinned-actions .e-grid .e-content tr > td:last-child {
    position: sticky;
    right: 0;
    z-index: 2;
    background-color: var(--brand-surface);
    border-left: 1px solid var(--brand-border);
    -webkit-mask-image: linear-gradient(to right, transparent, black 24px);
    mask-image: linear-gradient(to right, transparent, black 24px);
}

.app-grid--pinned-actions .e-grid .e-content tr:hover > td:last-child {
    background-color: var(--brand-primary-light);
}