/* Portal composition for the authenticated interactive surface (MainLayout + pages).
   Mint-only classes layered on the design system's chrome/component vocabulary (README,
   "Design system") — ds/tokens/components.css owns .portal-*, .nav-item, .data-table,
   .status-pill, …; this sheet adds what the User & Role reference screens compose on top.
   Tokens only, no raw hex; the dark theme comes from the aliases remapping underneath. */

/* ── Chrome extras ───────────────────────────────────────────────────────── */
.nav-rail {
    display: flex;
    flex-direction: column;
    min-height: 100%;
}

.nav-rail .nav-item {
    text-decoration: none;
}

.nav-item-btn {
    width: 100%;
    border: 0;
    background: none;
    font-family: inherit;
    text-align: left;
}

.nav-logout {
    margin-top: auto;
    padding-top: var(--space-5);
}

.portal-user {
    display: flex;
    align-items: center;
    gap: var(--space-4);
    min-width: 0;
}

.portal-user-name {
    font-size: var(--fs-label);
    font-weight: var(--fw-semibold);
    color: var(--text-primary);
    overflow: hidden;
    text-overflow: ellipsis;
    white-space: nowrap;
    max-width: 260px;
}

.nav-burger {
    display: none;
}

/* Theme toggle — one glyph per choice; the button carries data-choice (set by site.js). */
.theme-toggle .i-moon,
.theme-toggle .i-system {
    display: none;
}

.theme-toggle[data-choice="dark"] .i-sun,
.theme-toggle[data-choice="system"] .i-sun {
    display: none;
}

.theme-toggle[data-choice="dark"] .i-moon,
.theme-toggle[data-choice="system"] .i-system {
    display: inline-flex;
}

@media (max-width: 880px) {
    .nav-burger {
        display: inline-flex;
    }

    .portal-sidebar {
        position: fixed;
        top: var(--top-nav-height);
        bottom: 0;
        left: 0;
        z-index: 30;
        transform: translateX(-100%);
        transition: transform var(--transition-fast);
        box-shadow: var(--shadow-drawer);
    }

    .portal-sidebar.is-open {
        transform: none;
    }
}

/* ── Page canvas: every screen lines up with every other ─────────────────────
   The centred column (.page-wrap, max-width 1180px, margin: 0 auto) is the design
   system's own layout and is kept as-is. What made screens disagree was the SCROLLBAR:
   .portal-main scrolls, so a long page lost ~15px of width to the scrollbar and its
   centred content sat left of a short page's. Reserving the gutter permanently makes
   a scrolling page and a short one start at the identical x-position.

   Any new screen inherits this by rendering inside MainLayout — pages must not set
   their own outer padding, margin, or max-width. */
.portal-main {
    scrollbar-gutter: stable;
}

/* ── Page header (reference: .page-title / .page-sub) ────────────────────── */
.page-title {
    font-family: var(--font-display);
    font-weight: var(--fw-medium);
    font-size: var(--fs-page-title);
    letter-spacing: var(--ls-display);
    color: var(--text-primary);
}

.page-sub {
    font-size: 13.5px;
    color: var(--text-secondary);
    margin-top: 3px;
    max-width: 62ch;
}

/* Sub-heading inside a page body (below .page-title). */
.section-title {
    font-family: var(--font-display);
    font-weight: var(--fw-semibold);
    font-size: var(--fs-card-title);
    color: var(--text-primary);
    margin: var(--space-11) 0 var(--space-4);
}

/* ── List toolbar (search + count) ───────────────────────────────────────── */
.list-toolbar {
    display: flex;
    align-items: center;
    gap: var(--space-4);
    margin-bottom: var(--space-5);
}

.list-toolbar .input-search {
    width: 300px;
}

.count-label {
    font-family: var(--font-mono);
    font-size: 11px;
    letter-spacing: var(--ls-chip);
    color: var(--text-label);
}

.list-toolbar.wrap {
    flex-wrap: wrap;
    align-items: flex-end;
}

/* A form control placed in a TOOLBAR sizes to its content, not to the row.
   .form-input / .form-select / .form-textarea are width:100% in the design system, which is
   right inside a form column and wrong inside a flex toolbar: unconstrained, one select takes
   the whole row and pushes everything after it onto the next line. .list-toolbar already
   constrains .input-search; this extends the same reasoning to the plain controls.

   THIS RULE LIVES HERE BECAUSE FIVE SCREENS WROTE IT SEPARATELY. F3 recorded the cause
   (Lessons.md §1.22) and the fix stayed at rung 3 — prose in a lessons file — so BusinessCoa,
   CoaTemplateDetail, CoaTemplates, Engagements and then F7's Workflows each rediscovered it,
   which is precisely the "needs a per-screen override at its THIRD use" signal (§1.45, README
   rule 14c corollary). App-wide composition belongs in portal.css, never in a sixth
   .razor.css (Screen-Protocol.md §5). A screen that wants a different width still overrides
   it locally. */
.list-toolbar .form-select,
.list-toolbar .form-input:not(.input-search .form-input) {
    width: auto;
    min-width: 170px;
}

/* The count reads as a summary of the filters, so it sits at the far end of the row rather
   than butting against the last control. Also written separately by several screens. */
.list-toolbar .count-label {
    margin-left: auto;
}

/* A RADIO wearing .chk. The design system styles .chk as a checkbox — square radius, a tick
   pseudo-element — and has no radio variant, so a radio carrying that class renders as a
   checkbox and the "pick exactly one" affordance is lost. Round the box and swap the tick for a
   dot; everything else (size, border, checked colours, transition) is inherited.

   App-wide composition rather than a page's scoped file (Screen-Protocol.md §5): the next screen
   with a radio group would otherwise write this again, which is the pattern that put the
   toolbar-select fix in five places (Lessons.md §1.22, §1.45). */
input[type="radio"].chk {
    border-radius: 50%;
}

input[type="radio"].chk:checked::after {
    content: '';
    position: absolute;
    inset: 0;
    margin: auto;
    width: 8px;
    height: 8px;
    border: 0;
    border-radius: 50%;
    background: var(--brand-dark);
    transform: none;
}

/* A bare DS primitive carries no outer margin by design — the page placing it owns its
   spacing — while composed blocks like .list-toolbar carry their own margin-bottom. Mixing
   the two gives normal space BELOW an element and none ABOVE it, which is the asymmetry
   Screen-Protocol.md §6.2 calls "the spacing trap" and F1 shipped between its KPI band and
   toolbar. Give a KPI strip the page rhythm explicitly wherever it is followed by content. */
.kpi-strip {
    margin-bottom: var(--space-10);
}

/* PAGE-LEVEL NOTICES CARRY THE PAGE RHYTHM BELOW THEM.
   .info-box ships with no outer margin by design — the page that places it owns its spacing —
   and .kpi-strip has none ABOVE (the rule above fixed only the gap below, at F1). So a banner
   followed immediately by a KPI strip glues to it: Screen-Protocol §6.2's spacing trap, and F1's
   original defect in a new place.

   PROMOTED HERE at F14 because THREE screens had each written it independently — BusinessDetail
   (`.info-box[role]`, same value, same reasoning in its comment), ExtractionWizard (a tighter
   value for the wizard's own rhythm), and this phase's reconciliation landing, found by the
   step-6a read. §1.22/F7's counting rule: a rung-3 rule rediscovered N times is telling you its
   real home is one level up, and the evidence was sitting in the repo.

   [role] scopes it to page-level alert/status boxes and NOT to an .info-box inside a modal or a
   card, which is BusinessDetail's own refinement and the reason its version is the one promoted. */
.info-box[role] {
    margin-bottom: var(--space-10);
}

/* THE SEPARATOR CARRIES ITS OWN SPACING, EVERYWHERE EXCEPT WHERE A GAP ALREADY DOES IT.

   The mechanism, which is the part worth writing down: `.crumbs` is a flex container with
   `gap: 7px`, so ITS separators are spaced by the container and `.crumbs .sep` only needs to set
   opacity. Everywhere else `.sep` sits in normal flow with no margin at all, and Razor collapses
   the whitespace around the element — so a literal space in the markup cannot hold the two apart
   (Lessons.md §1.87). It renders "1010· Bank account".

   FIXED TWICE, and the first fix was scoped wrong. At the step-6a checkpoint this was corrected as
   `.feed-card-sub .sep` — the context the defect was FOUND in rather than the cause — and it
   promptly recurred on the next screen inside `.page-sub`. That is §1.44's meta-lesson exactly:
   when recording a mechanical trap, record the MECHANISM, or the rule does not generalise to the
   next element that has it. */
.sep {
    margin: 0 var(--space-1);
}

.crumbs .sep {
    /* The container's own gap already spaces these; a margin on top of it double-spaces. */
    margin: 0;
}

.filter-field {
    display: flex;
    flex-direction: column;
}

.filter-field .form-select,
.filter-field .form-input {
    min-width: 170px;
}

/* ── Utilities the reference leans on ────────────────────────────────────── */
/* .row / .row-between / .stack are the design system's own flex utilities
   (ds/tokens/components.css). Only the wrap modifier is added here.
   Historical note: these were temporarily renamed .zrow while Bootstrap was still
   loaded — its grid owned .row, and its `.row > *` rule (width: 100%) outranked a bare
   .row, stretching every chip, tag and icon button to full cell width. Bootstrap is
   gone, so the DS-native names are back. */
.row.wrap {
    flex-wrap: wrap;
}

/* Modal helpers */
.modal-title {
    font-family: var(--font-display);
    font-weight: var(--fw-semibold);
    font-size: var(--fs-drawer-title);
    color: var(--text-primary);
}

.delete-head {
    align-items: flex-start;
    gap: var(--space-6);
}

.delete-head .empty-icon {
    margin-bottom: 0;
}

.empty-icon.is-danger {
    background: var(--status-danger-soft);
    color: var(--status-danger-text);
}

.blocked-note {
    margin-top: var(--space-6);
}

/* Danger-toned ghost icon button (row delete actions). */
.btn.is-danger-tone {
    color: var(--text-danger);
}

.btn.is-danger-tone:hover:not(:disabled) {
    background: var(--status-danger-soft);
    color: var(--status-danger-text);
}

.mono {
    font-family: var(--font-mono);
    font-variant-numeric: tabular-nums;
}

.muted {
    color: var(--text-label);
}

.eyebrow {
    font: var(--type-label);
    letter-spacing: var(--ls-eyebrow);
    text-transform: uppercase;
    color: var(--text-label);
}

/* Card wrapper for a .data-table. A wide table (the audit log's seven columns) scrolls
   HORIZONTALLY INSIDE its own card — the page itself must never scroll sideways, which
   would drag the header and rail out of view. overflow-x on the card both enables that
   scroll and clips the table's corners to the radius. */
.table-card {
    background: var(--surface-primary);
    border: var(--rule);
    border-radius: var(--radius-lg);
    overflow-x: auto;
    overflow-y: hidden;
}

/* The minimum is the table's own natural content width (the audit log's seven columns
   measure ~810px). Below that the columns would squeeze instead of scrolling; at or above
   it the card simply fits and shows no scrollbar. Never guess this number lower than the
   content — a too-small minimum silently disables the scroll it was meant to provide. */
.table-card .data-table {
    min-width: 810px;
}

.cell-title {
    font-size: 13.5px;
    font-weight: var(--fw-semibold);
    color: var(--text-primary);
}

.cell-sub {
    font-family: var(--font-mono);
    font-size: 11px;
    color: var(--text-label);
}

/* ── Role editor: grouped permission accordions ──────────────────────────── */
.field-grid {
    display: grid;
    grid-template-columns: repeat(auto-fit, minmax(240px, 1fr));
    gap: var(--space-7);
}

.perm-search {
    width: 260px;
}

.feat-title {
    flex: 1;
    min-width: 0;
    gap: 1px;
}

.summary-count {
    align-items: baseline;
    margin: var(--space-3) 0 var(--space-5);
}

/* Page-level notices between the header and the content (RoleEdit's cascade + system-role
   banners). Without this they sat flush against both, breaking the 22px page rhythm every
   other screen keeps. */
.banner {
    margin-bottom: var(--space-5);
}

.page-head + .banner {
    margin-top: calc(-1 * var(--space-2));
}

.role-grid {
    display: grid;
    grid-template-columns: minmax(0, 1fr) 320px;
    gap: var(--space-10);
    align-items: start;
    margin-top: var(--space-10);
}

.role-summary {
    position: sticky;
    top: calc(var(--top-nav-height) + var(--space-7));
}

/* A stat tile that is also a link (D166). The design system already resets `button.kpi`; an
   anchor needs the same treatment plus its own underline and colour, and it lives here rather
   than in ds/ because the served design system is installed byte-for-byte from DesignSystem/
   and pinned by DesignSystemInstallParityTests. */
.kpi-link {
    display: block;
    text-decoration: none;
    color: inherit;
    cursor: pointer;
    transition: var(--transition-control);
}

.kpi-link:hover {
    border-color: var(--brand-primary);
}

/* ── Permission picker: one component, three hosts (D164) ─────────────────
   Grouped by AREA, not by feature. Every chip carries its label, its sentence and its scope
   badge; the clipped wire key the old editor printed under each label is gone, and
   `min-width: 0` on the chip body is what stops a long label overflowing its box the way the
   150px grid did (FA6-1). */
.perm-picker {
    display: grid;
    gap: var(--space-4);
}

.perm-toolbar {
    display: flex;
    align-items: center;
    gap: var(--space-4);
    flex-wrap: wrap;
}

.perm-toolbar-count {
    flex: 1;
    font-size: var(--text-xs);
}

.perm-area {
    border: var(--rule);
    border-radius: var(--radius-md);
    overflow: hidden;
}

.perm-area-head {
    display: flex;
    align-items: center;
    gap: var(--space-4);
    padding: var(--space-5) var(--space-6);
    background: var(--surface-secondary);
}

.perm-area-toggle {
    display: flex;
    align-items: center;
    gap: var(--space-4);
    flex: 1;
    min-width: 0;
    padding: 0;
    border: 0;
    background: none;
    font-family: inherit;
    text-align: left;
    cursor: pointer;
}

.perm-area-toggle .caret {
    transition: transform var(--transition-fast);
    color: var(--text-label);
    flex: none;
}

.perm-area.is-open .perm-area-toggle .caret {
    transform: rotate(90deg);
}

.perm-area-title {
    font-size: 13.5px;
    font-weight: var(--fw-semibold);
    color: var(--text-primary);
}

.perm-area-body {
    display: grid;
    grid-template-columns: repeat(auto-fill, minmax(250px, 1fr));
    gap: var(--space-3);
    padding: var(--space-5) var(--space-6);
    background: var(--surface-primary);
}

.perm-chip {
    display: flex;
    align-items: flex-start;
    gap: var(--space-3);
    padding: var(--space-4) var(--space-5);
    border: var(--rule);
    border-radius: var(--radius-md);
    cursor: pointer;
    transition: var(--transition-control);
}

.perm-chip:hover {
    border-color: var(--border-strong);
}

.perm-chip.is-checked {
    border-color: var(--brand-primary);
    background: var(--brand-primary-soft);
}

.perm-chip.is-baseline {
    border-style: dashed;
}

/* The fix for the clipping the owner's screenshot showed: a grid child will not shrink below
   its content unless told to, so a long label pushed past its own border. */
.perm-chip-body {
    display: grid;
    gap: 2px;
    min-width: 0;
}

.perm-chip-title {
    display: flex;
    align-items: center;
    gap: var(--space-3);
    flex-wrap: wrap;
    font-size: var(--fs-body-sm);
    font-weight: var(--fw-medium);
    color: var(--text-primary);
}

.perm-chip-desc {
    font-size: var(--text-xs);
    color: var(--text-secondary);
    overflow-wrap: anywhere;
}

.perm-chip-scope {
    font-size: var(--text-xs);
    color: var(--text-label);
}

/* The invite drawer's role row. Was .perm-check + .role-pick borrowed from the permission grid;
   it is its own thing -- a checkbox, a name, a count, and a button BESIDE the label that opens a
   read-only picker without assigning anything. .perm-check went with its last consumer. */
.role-pick {
    display: flex;
    align-items: center;
    gap: var(--space-4);
    padding: var(--space-4) var(--space-5);
    border: var(--rule);
    border-radius: var(--radius-md);
    transition: var(--transition-control);
}

.role-pick:hover {
    border-color: var(--border-strong);
}

.role-pick.is-checked {
    border-color: var(--brand-primary);
    background: var(--brand-primary-soft);
}

.role-pick-main {
    display: flex;
    align-items: flex-start;
    gap: var(--space-3);
    flex: 1;
    min-width: 0;
    cursor: pointer;
}

.perm-loading {
    padding: var(--space-5) var(--space-6);
    border: var(--rule);
    border-radius: var(--radius-md);
    font-size: var(--fs-body-sm);
    color: var(--text-label);
}

.perm-empty {
    display: flex;
    align-items: center;
    gap: var(--space-4);
    padding: var(--space-6);
    border: var(--rule);
    border-radius: var(--radius-md);
    background: var(--surface-secondary);
}

/* Compact: a drawer has less room, so the scope line is dropped and the columns narrow. */
.perm-picker.is-compact .perm-area-body {
    grid-template-columns: repeat(auto-fill, minmax(200px, 1fr));
}

.perm-picker.is-compact .perm-chip {
    padding: var(--space-3) var(--space-4);
}

.pill-count {
    font-family: var(--font-mono);
    font-size: 10px;
    font-weight: var(--fw-semibold);
    padding: 2px 8px;
    border-radius: var(--radius-pill);
    background: var(--surface-tertiary);
    color: var(--text-secondary);
    white-space: nowrap;
}

/* One row per granted area. The names read as prose, not as a 10px mono column: this is the
   answer to "what does this role actually do", and it is read far more often than it is
   scanned. */
.perm-summary-row {
    display: grid;
    grid-template-columns: 1fr auto;
    gap: var(--space-3) var(--space-4);
    padding: var(--space-4) 0;
    border-top: var(--rule);
}

.perm-summary-names {
    grid-column: 1 / -1;
    font-size: 12.5px;
    color: var(--text-secondary);
    overflow-wrap: anywhere;
}

/* ── User detail: layout + effective access + overrides ──────────────────── */
.detail-grid {
    display: grid;
    grid-template-columns: repeat(auto-fit, minmax(380px, 1fr));
    gap: var(--space-9);
    align-items: start;
}

.col-stack {
    gap: var(--space-9);
}

.card-pad {
    padding: var(--space-6) var(--space-8);
}

.card-pad > .page-sub {
    margin: 0 0 var(--space-5);
}

.overflow-visible {
    overflow: visible;
}

.click-catcher {
    position: fixed;
    inset: 0;
    z-index: 39;
}

.acc-text {
    font-size: var(--fs-body-sm);
    color: var(--text-secondary);
}

.acc-expiry {
    font-family: var(--font-mono);
    font-size: 10.5px;
    color: var(--text-label);
}

.mode-chip {
    font-family: var(--font-mono);
    font-size: 9px;
    font-weight: var(--fw-semibold);
    text-transform: uppercase;
    letter-spacing: .06em;
    padding: 2px 7px;
    border-radius: var(--radius-pill);
    background: var(--brand-primary-soft);
    color: var(--brand-on-soft);
    white-space: nowrap;
}

.mode-chip.is-block {
    background: var(--status-danger-soft);
    color: var(--status-danger-text);
}


.acc-line {
    display: flex;
    align-items: baseline;
    gap: var(--space-4);
    padding: var(--space-4) 0;
    border-top: var(--rule);
    font-size: var(--fs-body-sm);
}

.acc-line:first-of-type {
    border-top: 0;
}

.acc-feature {
    font-weight: var(--fw-semibold);
    color: var(--text-primary);
    min-width: 110px;
}

.acc-badge {
    font-family: var(--font-mono);
    font-size: 9px;
    font-weight: var(--fw-semibold);
    text-transform: uppercase;
    letter-spacing: .06em;
    padding: 2px 7px;
    border-radius: var(--radius-sm);
    white-space: nowrap;
}

.acc-badge.via-role {
    background: var(--brand-primary-soft);
    color: var(--brand-on-soft);
}

.acc-badge.via-override {
    background: var(--brand-accent-soft);
    color: var(--tint-violet-fg);
}

.acc-badge.via-none {
    background: var(--surface-tertiary);
    color: var(--text-label);
}

.res-card {
    border: var(--rule);
    border-radius: var(--radius-md);
    padding: var(--space-5) var(--space-6);
    background: var(--surface-primary);
}

.res-card + .res-card {
    margin-top: var(--space-4);
}

.res-head {
    display: flex;
    align-items: center;
    justify-content: space-between;
    gap: var(--space-4);
}

/* One line per resource instead of a twenty-seven-checkbox grid per resource (D165): what it
   inherits, and the way out of inheriting it. */
.res-caps {
    display: flex;
    align-items: center;
    gap: var(--space-3);
    flex-wrap: wrap;
    margin-top: var(--space-4);
    font-size: var(--fs-body-sm);
}

/* The revocation warning (R-019). Danger-toned because the save it guards takes access away,
   and until FA6-B it took it away in silence. */
.override-diff {
    display: grid;
    gap: var(--space-3);
}

.override-diff-list {
    font-size: var(--fs-body-sm);
    color: var(--text-primary);
    overflow-wrap: anywhere;
}

/* Was declared identically in two scoped stylesheets, so neither drawer could see the other's
   copy and a third would have made three. One definition, in the design system. */
.section-rule {
    margin: var(--space-6) 0 var(--space-5);
}

/* A real <button> since FA6-B: it was a clickable <div>, which is the same defect as a
   <span class="btn"> -- no focus ring, no Enter key, nothing for a screen reader to announce.
   The resets below are what a button needs to keep looking like a card. */
.intent-card {
    display: flex;
    align-items: flex-start;
    gap: var(--space-4);
    width: 100%;
    padding: var(--space-5) var(--space-6);
    border: var(--rule);
    border-radius: var(--radius-md);
    background: var(--surface-primary);
    font-family: inherit;
    text-align: left;
    cursor: pointer;
    transition: var(--transition-control);
}

.intent-card:hover {
    border-color: var(--border-strong);
}

.intent-card.is-selected {
    border-color: var(--brand-primary);
    background: var(--brand-primary-soft);
}

.search-result {
    display: flex;
    align-items: center;
    justify-content: space-between;
    gap: var(--space-4);
    width: 100%;
    padding: var(--space-4) var(--space-5);
    border: 0;
    border-radius: var(--radius-md);
    background: none;
    font-family: inherit;
    font-size: var(--fs-body-sm);
    text-align: left;
    cursor: pointer;
}

.search-result:hover {
    background: var(--surface-secondary);
}

/* .workspace-nav was F9's route-tab strip. D62 replaced it with the bookkeeping RAIL, so the
   rule is gone rather than left behind — dead CSS outlives the markup that explained it. The
   rail's own composition is .nav-rail-escape and .nav-count below. */

/* ── The bookkeeping rail (D62 / FA-1 FA-049…FA-053) ──────────────────────────────────────
   App-wide composition, not a scoped copy: the rail is chrome, and chrome that needs a
   per-screen override is the wrong component (Lessons.md §1.22, the F7 precedent). */

/* The Firm-Portal escape sits at the rail's foot, above the logout block, with a rule to
   separate "leave this workspace" from "surfaces inside it". */
.nav-rail-escape {
    margin-top: var(--space-5);
    padding-top: var(--space-4);
    border-top: 1px solid var(--border-hairline);
}

/* A nav count badge. Pushed to the rail's trailing edge so the numbers line up in a column
   whatever the label length. NOTHING renders for zero or for a non-computable count — see
   NavCountBadge.razor; this rule only styles a number that exists. */
.nav-count {
    margin-left: auto;
    min-width: 20px;
    padding: 1px 6px;
    border-radius: var(--radius-pill);
    background: var(--surface-tertiary);
    color: var(--text-secondary);
    font-family: var(--font-mono);
    font-size: 10.5px;
    font-weight: 600;
    line-height: 1.5;
    text-align: center;
}

.nav-item.is-active .nav-count {
    background: var(--surface-primary);
    color: var(--brand-on-soft);
}

.nav-count.is-attention {
    background: var(--status-warning-soft);
    color: var(--status-warning-text);
}

/* The header business-context chip. Rev 2.0 keeps the client identifiable on every bookkeeping
   screen; the mint named the business nowhere until FA-2. Separated from the brand by a rule
   rather than a gap, so it reads as a different kind of thing from the product name. */
.portal-context {
    display: flex;
    flex-direction: column;
    gap: 1px;
    margin-left: var(--space-4);
    padding-left: var(--space-4);
    border-left: 1px solid var(--border-hairline);
    min-width: 0;
}

.portal-context-name {
    font-size: 13px;
    font-weight: 600;
    color: var(--text-primary);
    overflow: hidden;
    text-overflow: ellipsis;
    white-space: nowrap;
}

.portal-context-sub {
    font-family: var(--font-mono);
    font-size: 9.5px;
    letter-spacing: .06em;
    text-transform: uppercase;
    color: var(--text-label);
    overflow: hidden;
    text-overflow: ellipsis;
    white-space: nowrap;
}

/* ── Transactions grid, restored regions (FA-1 §B) ────────────────────────────────────────
   App-wide composition rather than scoped copies: the chip axis and the lock glyph are grid
   vocabulary that Staging and Staging-detail reuse in the same phase (Lessons.md §1.22). */

/* The STATUS / REVIEW labels in front of each chip group. Rev 2.0 labels its two chip axes
   so the row does not read as one undifferentiated strip of eight pills (FA-009). */
.chip-axis-label {
    align-self: center;
    margin-right: calc(var(--space-2) * -1);
    color: var(--text-label);
}

/* The count inside a filter chip. Tabular so the numbers do not jitter as they change, and
   dimmer than the label because the label is what you click (FA-009). */
.chip-count {
    margin-left: var(--space-2);
    font-family: var(--font-mono);
    font-size: 10.5px;
    font-weight: 600;
    opacity: .65;
}

.segmented button.is-active .chip-count {
    opacity: 1;
}

/* The lock glyph that REPLACES a locked row's checkbox (FA-013). Sized to the checkbox it
   stands in for, so the column does not shift between locked and unlocked rows. */
.row-lock {
    display: inline-block;
    width: 18px;
    font-size: 11px;
    line-height: 18px;
    text-align: center;
    color: var(--text-label);
    cursor: default;
}

/* A posted row inside a locked period — the pipeline's terminal stage. Neutral rather than
   green: closed is finished, not successful. */
.status-pill.closed {
    background: var(--surface-tertiary);
    color: var(--text-secondary);
}

/* The transaction drawer's editing half (FA-005): the categorization form under the record
   rows. Spaced away from the list above so the two read as record-then-form, not one run of
   fields. */
.drawer-edit {
    margin-top: var(--space-6);
    display: flex;
    flex-direction: column;
    gap: var(--space-4);
}

/* A cell that must not break — dates and single-word pills. The transactions grid grew to nine
   columns when FA-001 restored the second pill axis, and the first thing to give was the date
   wrapping onto three lines. Found by READING the capture, not by the region walk. */
.td-nowrap {
    white-space: nowrap;
}

/* A full-width banner row INSIDE a table body — the staging landing's per-job overdue notice
   (FA-025). Rev 2.0 puts the banner under the row it is about, which is what makes it
   actionable; a page-level count says a problem exists but not which job to open. The cell
   carries no padding of its own so the info-box inside owns the rhythm. */
.row-banner > td {
    padding: 0 0 var(--space-3) 0;
    border-top: 0;
}

.row-banner-body {
    display: block;
    margin: 0;
}

/* ── Staging detail: the sticky commit bar (FA-039) ───────────────────────────────────────
   Rev 2.0 pins the job stats and its two commit actions to the bottom of the viewport, so
   Post stays reachable however long the job is. F9 put Post in the page head, where it
   scrolls away exactly when you have finished the work it commits. */
.staging-foot {
    position: sticky;
    bottom: 0;
    z-index: 5;
    display: flex;
    align-items: center;
    justify-content: space-between;
    gap: var(--space-5);
    flex-wrap: wrap;
    margin-top: var(--space-6);
    padding: var(--space-4) var(--space-5);
    background: var(--surface-primary);
    border: 1px solid var(--border-hairline);
    border-radius: var(--radius-md);
    box-shadow: var(--shadow-raised, 0 -2px 8px rgb(0 0 0 / 6%));
}

.staging-foot-stats {
    font-size: 13px;
    color: var(--text-secondary);
}

.staging-foot-actions {
    display: flex;
    align-items: center;
    gap: var(--space-3);
}

/* A chip promoted into a page title — the staging job's account identity (FA-045). Aligned to
   the title baseline rather than its box, so it sits with the name instead of over it. */
.title-chip {
    margin-left: var(--space-3);
    vertical-align: middle;
}

/* A sortable column header rendered as a button (FA-043): the DS has no th-button, and the
   header must stay a header for screen readers while being pressable. */
.th-sort {
    padding: 0;
    font: inherit;
    font-weight: inherit;
    color: inherit;
    text-transform: inherit;
    letter-spacing: inherit;
}

/* A KPI strip carrying more than four tiles. The DS strip auto-fits at a 180px minimum, which
   is right for four and wraps the fifth and sixth onto a lonely second row — the staging detail
   restored Rev 2.0's six (FA-035) and the last tile ended up orphaned under the others. Found
   by READING the capture, which is the whole point of step 6a.

   Six equal tracks that may shrink, rather than a smaller auto-fit minimum, because the tiles
   should stay a single rhythm rather than re-flowing per screen. */
.kpi-strip.is-six {
    grid-template-columns: repeat(6, minmax(0, 1fr));
}

/*  Seven, for the staging landing once F12 fills its two reserved slots.

    THE SAME DEFECT RECURRED, which is the interesting part. The comment above records the six-tile
    orphan being found by reading a capture — and the fix was tied to the COUNT, so adding a
    seventh tile reproduced it exactly: the last tile alone on a second row, left-aligned, under
    the others. A count-named modifier is a fix with an expiry date, and this is its second
    expiry.

    Kept count-named anyway rather than generalised here, because `grid-auto-flow: column` on the
    base class would change every strip in the product and that is not a change to make while
    fixing a screen. Recorded in DriftRegister.md as the generalisation the next strip should
    force.  */
.kpi-strip.is-seven {
    grid-template-columns: repeat(7, minmax(0, 1fr));
}

@media (max-width: 1200px) {
    .kpi-strip.is-six,
    .kpi-strip.is-seven {
        grid-template-columns: repeat(3, minmax(0, 1fr));
    }
}

@media (max-width: 720px) {
    .kpi-strip.is-six,
    .kpi-strip.is-seven {
        grid-template-columns: repeat(2, minmax(0, 1fr));
    }
}

/* ── FA-2 §E: the engagements list's restored columns and its filtered banner ─────────────
   All three land here rather than in a scoped file because more than one screen needs them:
   the banner is FA-061 on Engagements AND FA-106 on Customers, and a progress bar is the
   shape every list with a task board will want. A rule written twice is a rule that drifts
   (Lessons.md §1.22, and §1.45's ".kv at its third use" signal). */

/* The amber "you are looking at a subset" band. `.info-box` supplies the tone and the box; this
   only lays the message out against its Clear button. */
.filter-banner {
    display: flex;
    align-items: center;
    justify-content: space-between;
    gap: var(--space-4);
    flex-wrap: wrap;
    margin-bottom: var(--space-10);
}

/* A completion bar beside its own figure. The FILL's width is a runtime value, so it arrives as
   the sanctioned `style="--progress:…"` custom property (InlineStyleGuardrailTests) and the
   declaration itself stays here. */
.progress-cell {
    display: inline-flex;
    align-items: center;
    gap: var(--space-3);
    min-width: 140px;
}

.progress-bar {
    flex: 1;
    min-width: 72px;
    height: 6px;
    border-radius: var(--radius-pill);
    background: var(--surface-tertiary);
    overflow: hidden;
}

.progress-bar-fill {
    display: block;
    width: var(--progress, 0%);
    height: 100%;
    border-radius: var(--radius-pill);
    background: var(--brand-primary);
}

.progress-pct {
    font-variant-numeric: tabular-nums;
    font-size: 12.5px;
    color: var(--text-label);
}

/* The due date over its attention chip. A column, so a long "5th of following month" phrase and
   a chip do not fight for one line. */
.due-cell {
    display: inline-flex;
    flex-direction: column;
    align-items: flex-start;
    gap: 4px;
}

/* A count badge inside a tab (FA-068's Blockers, FA-095's Stages & tasks). The DS `.tab` is a
   button with text; this is the number that turns it from a place to look into a place to check.
   App-wide from the first use, because the second one is already in this phase. */
.tab-count {
    display: inline-flex;
    align-items: center;
    justify-content: center;
    min-width: 18px;
    height: 18px;
    margin-left: 6px;
    padding: 0 5px;
    border-radius: var(--radius-pill);
    background: var(--surface-tertiary);
    color: var(--text-secondary);
    font-size: 10.5px;
    font-weight: 600;
    font-variant-numeric: tabular-nums;
}

.tab-count.is-danger {
    background: var(--status-danger-soft);
    color: var(--status-danger-text);
}

/* The removable-group row's count line: "19 open · 2 resolved (kept) · 1 in locked periods".
   A wrapping inline run rather than a grid — the three facets read as one sentence. */
.workflow-group-counts {
    display: inline-flex;
    align-items: center;
    flex-wrap: wrap;
    gap: 6px;
    color: var(--text-label);
    font-size: 12.5px;
}

/* A numbered step inside the onboarding dossier (FA-100). Rev 2.0 labels the proposal "02b"
   between steps 02 and 03; the label is what makes the section read as part of a sequence rather
   than as a card that happens to sit there. */
.dossier-step {
    display: block;
    margin-bottom: var(--space-10);
}

.dossier-step-label {
    display: block;
    margin-bottom: var(--space-3);
}

/* A step whose content is an EMPTY STATE still has to read as a step. Every other section in the
   dossier is a card; an unboxed empty state left "STEP 02B · PROPOSAL" floating above a centred
   paragraph with a card-sized gap between them, which reads as a rendering fault rather than as
   "no proposal yet". Found by reading the capture (step 6a), not by walking the regions. */
.dossier-step > .empty-state {
    background: var(--surface-primary);
    border: 1px solid var(--border-default);
    border-radius: var(--radius-lg);
}

/* A wrapping run of chips inside one table cell or record row (FA-082/FA-083/FA-089). App-wide
   from the first use because the second was in the same phase: the workflows list and the
   workflow detail both need it, and a rule written twice is a rule that drifts (Lessons.md
   §1.22). Flexed on a SPAN inside the cell, never on the cell — a td that stops being a
   table-cell escapes its column (TableCellLayoutTests). */
.chip-row {
    display: inline-flex;
    flex-wrap: wrap;
    align-items: center;
    gap: 4px;
}

/* A chip for something the record does NOT have. The reference strikes the unstaffed roles
   through rather than omitting them, because the absence is the information: a template that
   expects no supervisor is a different template, and a list of what IS staffed cannot say so.
   Struck AND dimmed — a strike alone is invisible at chip size, and dimming alone reads as
   "loading" (the §1.30 lesson: never let a state rest on opacity in one theme only). */
.meta-chip.is-off {
    background: transparent;
    border: 1px dashed var(--border-default);
    color: var(--text-label);
    text-decoration: line-through;
}

/* A column header that IS its sort control (FA-118). A bare button that inherits the header's
   own type, so the column still reads as a header and not as a control bolted into one — the
   grid idiom the reference uses on every table. */
.th-sort {
    appearance: none;
    padding: 0;
    border: 0;
    background: none;
    font: inherit;
    color: inherit;
    letter-spacing: inherit;
    text-transform: inherit;
    text-align: inherit;
    cursor: pointer;
}

.th-sort:hover {
    color: var(--text-primary);
}

/* The bulk bar: a toolbar band above a table, appearing with the first checked row.
   PROMOTED FROM SCOPED CSS at its third use (FA-114's chart of accounts) — Transactions and the
   staging detail had each written it separately, which is exactly the signal README rule 14a's
   corollary names and the reason `.kv` was retired (Lessons.md §1.45). One declaration, so a
   selection looks the same wherever it is made. */
.bulk-bar {
    display: flex;
    align-items: center;
    flex-wrap: wrap;
    gap: var(--space-3);
    padding: var(--space-3) var(--space-4);
    margin-bottom: var(--space-4);
    background: var(--surface-tertiary);
    border-radius: var(--radius-md);
}

/* A select inside the bar must not stretch to the DS's 100% field width. */
.bulk-bar .bulk-select {
    width: auto;
    min-width: 220px;
}

/* A row-selection column: as narrow as a checkbox, centred on it. Promoted from the customers
   list at its second use (FA-114's chart of accounts), because a selection column that is 36px
   on one screen and content-width on another is two screens, not one product. The DS already owns
   `.col-action` for the mirror case at the other end of the row. */
.col-select {
    width: 36px;
    text-align: center;
}

/* A table card holding only a few rows CANNOT CONTAIN A ROW MENU. `.table-card` scrolls
   horizontally, and a box that scrolls on one axis becomes a clipping box on the other — so the
   menu is cut off below the last row, and cut off by the header if it flips up. On a long table
   there is room either way and nobody notices; FA-058's restored menu on a one-row filtered list
   is where it shows (step 6a).

   The escape is WIDTH-GATED, not unconditional: below the width where the widest table (~810px,
   the audit log) fits, the horizontal scroll is genuinely needed and clipping is the price of it.
   Above that width no table is scrolling anyway, so a shallow card can stop clipping and let the
   menu out. */
@media (min-width: 1100px) {
    .table-card.is-shallow {
        overflow: visible;
    }
}

/* ── Bank connections (F10) ──────────────────────────────────────────────── */

/* A feed that failed and needs a person. The DS ships its danger tone as
   `.status-pill.is-expired`, whose NAME describes a licence, not a sync — using it here would
   read as a lie at every call site. Same tokens, honest name; the same reason `.closed` was
   added above rather than borrowing `.deactivated`. */
.status-pill.error {
    background: var(--status-danger-soft);
    color: var(--status-danger-text);
}

/* The seven-day sync-health strip. One cell per day, oldest first. A day with NO run is
   `none` and stays neutral: "we did not look" and "we looked and it was fine" are different
   facts, and only one of them may read green. */
.feed-health {
    display: flex;
    gap: var(--space-1);
    align-items: center;
}

.feed-health-day {
    inline-size: var(--space-5);
    block-size: var(--space-2);
    border-radius: var(--radius-pill);
    background: var(--surface-tertiary);
}

.feed-health-day.healthy { background: var(--status-success-text); }
.feed-health-day.partial { background: var(--status-warning-text); }
.feed-health-day.failed { background: var(--status-danger-text); }

/* The connection card grid. Sized so three cards sit on a desktop row and collapse to one
   on a phone without a breakpoint of their own. */
.feed-grid {
    display: grid;
    grid-template-columns: repeat(auto-fill, minmax(20rem, 1fr));
    gap: var(--space-7);
}

/* The freshness facts on a card and on the per-account header: a label above a value, in a
   row that wraps rather than scrolls. */
.feed-facts {
    display: flex;
    flex-wrap: wrap;
    gap: var(--space-7) var(--space-9);
}

.feed-fact-value {
    font-family: var(--font-mono);
    font-size: var(--text-sm);
    color: var(--text-primary);
}

/* Behind-text tones. Deliberately a COLOUR and not an opacity: opacity inverts perceptually
   between the two grounds this product ships (Lessons.md §1.30). */
.feed-behind.is-fresh { color: var(--status-success-text); }
.feed-behind.is-warn { color: var(--status-warning-text); }
.feed-behind.is-danger { color: var(--status-danger-text); }

/* The connections page head's action cluster. Banking.razor has an equivalent in its own
   SCOPED sheet, which is unreachable from here by design — copying a scoped class into a
   second page is what CssClassResolutionTests exists to catch, so this one is shared. */
.feed-head-actions {
    display: flex;
    gap: var(--space-5);
    align-items: center;
}

/* A typed section head — "Bank accounts" / "Credit cards" — with its count beside it. */
.section-head {
    display: flex;
    align-items: baseline;
    gap: var(--space-5);
    margin-block: var(--space-9) var(--space-5);
}

/* One connection card. */
.feed-card {
    display: flex;
    flex-direction: column;
    gap: var(--space-5);
    padding: var(--space-7);
}

.feed-card-head {
    display: flex;
    justify-content: space-between;
    align-items: flex-start;
    gap: var(--space-5);
}

.feed-card-title {
    font-size: var(--text-md);
    font-weight: var(--weight-semibold);
    color: var(--text-primary);
    margin: 0;
}

.feed-card-sub {
    font-family: var(--font-mono);
    font-size: var(--text-xs);
    color: var(--text-secondary);
    margin: 0;
}

/* Badges wrap rather than squeeze: a card can carry three at once (Active + Backfilling +
   Consent expiring), which the reference capture shows and a single row would clip. */
.feed-card-badges {
    display: flex;
    flex-wrap: wrap;
    justify-content: flex-end;
    gap: var(--space-1);
}

.feed-card-actions {
    display: flex;
    flex-wrap: wrap;
    gap: var(--space-2);
    align-items: center;
    margin-block-start: auto;
    padding-block-start: var(--space-5);
    border-block-start: var(--border-w) solid var(--border-default);
}

/* ── Feed dialogs ────────────────────────────────────────────────────────── */

.feed-modal-head {
    display: flex;
    justify-content: space-between;
    align-items: center;
    gap: var(--space-5);
    border-block-end: var(--border-w) solid var(--border-default);
}

.consent-rows {
    display: flex;
    flex-direction: column;
    gap: var(--space-5);
    margin-block: var(--space-7);
    padding: 0;
    list-style: none;
}

.consent-rows li {
    display: flex;
    flex-direction: column;
    gap: var(--space-1);
}

.consent-rows span {
    font-size: var(--text-sm);
    color: var(--text-secondary);
}

.consent-picker {
    display: flex;
    flex-direction: column;
    gap: var(--space-2);
}

/* A button that fills its container — the consent picker's two choices, and the dialog feet. */
.btn-block {
    inline-size: 100%;
    justify-content: center;
}

/* ── F15 · the T-account card ────────────────────────────────────────────────────────────────
   No DS component exists for this shape, and Screen-Protocol §5 says to say so rather than bend
   one. It is two columns of reference/amount pairs sharing a centre rule — the visual convention
   an accountant reads a ledger account in — built on the DS's own surface and border tokens so it
   sits in the same product as the tables above it.

   Promoted to portal.css rather than scoped to the ledger panel because the General Ledger will
   want it the moment F17's drill-downs land, and §1.22 is the record of what per-screen copies
   cost: four screens had each written the same scoped fix, two of them with conflicting rules. */
.t-account {
    display: grid;
    grid-template-columns: 1fr 1fr;
    border: 1px solid var(--border-default);
    border-radius: var(--radius-md);
    overflow: hidden;
}

.t-account-col { padding: 12px 14px; }
.t-account-col + .t-account-col { border-left: 1px solid var(--border-default); }

.t-account-line {
    display: flex;
    justify-content: space-between;
    gap: 12px;
    padding: 4px 0;
    font-size: 12.5px;
}

/* The editable line grid. Lessons.md §1.77: put form controls in a data table and the columns
   stop sizing from their content, because an input carries its own intrinsic width. So the row is
   sized as a WHOLE here rather than one column at a time — F11 fixed that grid a column at a time
   and each fix took space from a neighbour. */
.journal-lines th:nth-child(1), .journal-lines td:nth-child(1) { width: 30%; }
.journal-lines th:nth-child(2), .journal-lines td:nth-child(2) { width: 34%; }
.journal-lines th:nth-child(3), .journal-lines td:nth-child(3) { width: 14%; }
.journal-lines th:nth-child(4), .journal-lines td:nth-child(4) { width: 14%; }
.journal-lines th:nth-child(5), .journal-lines td:nth-child(5) { width: 8%; }
.journal-lines .form-input, .journal-lines .form-select { width: 100%; }

/* ── The page-head action cluster ────────────────────────────────────────────────────────────
   SIX screens had each written this rule in their own scoped stylesheet under six different
   names — .ai-head-actions, .banking-head-actions, .coa-head-actions, .page-head-actions,
   .staging-head-actions, .coa-template-head-actions — and every one is the same three
   declarations. That is Lessons.md §1.22 verbatim: a DS gap that five screens each patched
   separately, which F7 found and promoted rather than copying a sixth time.

   F15 needed a seventh. Promoted instead, under the generic name Staging already used, so the
   scoped copies keep winning inside their own components (scoped CSS carries the higher
   specificity) and nothing existing changes today. Collapsing the six is recorded as a finding
   rather than done here — it touches six shipped screens and belongs to a pass that re-captures
   them, not to a feature phase. */
.page-head-actions {
    display: flex;
    gap: var(--space-3);
    align-items: center;
    flex-shrink: 0;
    flex-wrap: wrap;
}

/* ── F17 · Reports ────────────────────────────────────────────────────────────────────────────
   The reporting surfaces: statement cards, the twelve-month matrices, the inline-SVG charts, the
   report index and the owner-report preview.

   PROMOTED TO portal.css RATHER THAN SCOPED, deliberately. Nine surfaces share the statement
   card, and both matrix tabs share the flag colours; §1.22 is the record of what per-screen
   copies cost — four screens had each written the same scoped fix, two of them with conflicting
   rules. Everything here is built on DS tokens so both themes work, which is the other half of
   D111: Rev 2.0's charts are canvas-rendered and predate dark mode entirely.
   ──────────────────────────────────────────────────────────────────────────────────────────── */

/* ── the statement card ── */
.statement-card {
    background: var(--surface-primary);
    border: 1px solid var(--border-default);
    border-radius: var(--radius-lg);
    overflow: hidden;
    margin-bottom: 20px;
}

.statement-head {
    display: flex;
    align-items: flex-start;
    justify-content: space-between;
    gap: 16px;
    padding: 20px 22px 16px;
    border-bottom: 1px solid var(--border-default);
}

.statement-entity {
    font-size: 17px;
    font-weight: 600;
    color: var(--text-primary);
    margin: 0;
}

.statement-title {
    font-size: 13.5px;
    color: var(--text-secondary);
    margin: 2px 0 0;
}

.statement-period {
    font-family: var(--font-mono, monospace);
    font-size: 11.5px;
    color: var(--text-tertiary);
    margin: 6px 0 0;
}

.statement-foot {
    display: flex;
    align-items: center;
    justify-content: space-between;
    gap: 12px;
    flex-wrap: wrap;
    padding: 12px 22px;
    border-top: 1px solid var(--border-default);
    font-size: 11.5px;
    color: var(--text-tertiary);
}

.statement-table .col-code {
    font-family: var(--font-mono, monospace);
    font-size: 11.5px;
    color: var(--text-tertiary);
    width: 72px;
}

.statement-section > th,
.statement-section > td {
    font-size: 10.5px;
    letter-spacing: .08em;
    text-transform: uppercase;
    color: var(--text-tertiary);
    background: var(--surface-secondary);
    font-weight: 600;
    text-align: left;
}

.statement-subtotal > td {
    font-weight: 600;
    color: var(--text-primary);
    border-top: 1px solid var(--border-default);
}

.statement-total > td {
    font-weight: 700;
    color: var(--text-primary);
    background: var(--surface-secondary);
    border-top: 2px solid var(--border-strong);
}

.statement-total.is-danger > td {
    color: var(--status-danger);
}

/* A negative figure on a statement. Parenthesised, which is the accounting convention and what
   the reference prints; the minus sign the default currency format produces reads as an edit
   rather than a balance. */
.is-negative {
    color: var(--status-danger);
}

/* ── the twelve-month matrices ── */
.table-scroll {
    overflow-x: auto;
}

.matrix-table {
    /* Seventeen columns - a code, an account, a cadence, TWELVE months, a sparkline and an
       average. 1040px pushed TREND and AVERAGE off a 1440px capture entirely, and the sparkline
       column exists to be read. The cells below are sized so the whole row fits the workspace at
       1440; it still scrolls on a narrow screen, which is what .table-scroll is for. */
    min-width: 1060px;
    table-layout: fixed;
}

.matrix-table th,
.matrix-table td {
    padding-left: 5px;
    padding-right: 5px;
    font-size: 11.5px;
}

.matrix-table td:nth-child(1),
.matrix-table th:nth-child(1) {
    width: 52px;
}

.matrix-table td:nth-child(2),
.matrix-table th:nth-child(2) {
    width: 132px;
    white-space: nowrap;
    overflow: hidden;
    text-overflow: ellipsis;
}

.matrix-table td:nth-child(3),
.matrix-table th:nth-child(3) {
    width: 74px;
}

/* TREND and AVERAGE, the last two columns. The sparkline is 80px wide by default and was
   overflowing a narrower cell onto the average beside it. */
.matrix-table td:nth-last-child(2),
.matrix-table th:nth-last-child(2) {
    width: 76px;
}

.matrix-table td:last-child,
.matrix-table th:last-child {
    width: 78px;
}

.matrix-table .sparkline {
    width: 68px;
}

.matrix-table .num {
    font-family: var(--font-mono, monospace);
    font-size: 11.5px;
    white-space: nowrap;
}

.matrix-note > td {
    font-size: 12px;
    color: var(--text-secondary);
    background: var(--surface-secondary);
    border-top: 0;
}

/* A flagged month, in FOUR kinds rather than Rev 2.0's one. Colour is never the only signal —
   every flagged cell carries a title attribute saying what is unusual about it. */
.is-flag-high {
    background: var(--status-danger-soft);
    color: var(--status-danger);
    font-weight: 700;
}

.is-flag-low {
    background: var(--status-warning-soft);
    color: var(--status-warning-text);
    font-weight: 700;
}

.is-flag-reversed {
    background: var(--brand-accent-soft);
    color: var(--brand-accent);
    font-weight: 700;
}

.is-flag-missing {
    background: var(--status-warning-soft);
    color: var(--status-warning-text);
    font-weight: 700;
}

/* ── the narrative card and the report index ── */
.narrative-card {
    background: var(--surface-secondary);
    border: 1px solid var(--border-default);
    border-radius: var(--radius-lg);
    padding: 16px 20px;
    margin-bottom: 18px;
}

.narrative-label {
    font-size: 10.5px;
    letter-spacing: .08em;
    text-transform: uppercase;
    color: var(--text-tertiary);
    margin: 0 0 6px;
}

.narrative-text {
    font-size: 14.5px;
    line-height: 1.5;
    color: var(--text-primary);
    margin: 0;
}

.section-label {
    font-size: 10.5px;
    letter-spacing: .08em;
    text-transform: uppercase;
    color: var(--text-tertiary);
    margin: 22px 0 8px;
}

.report-index {
    list-style: none;
    margin: 0 0 20px;
    padding: 0;
    border: 1px solid var(--border-default);
    border-radius: var(--radius-lg);
    overflow: hidden;
}

.report-index-row {
    display: flex;
    align-items: center;
    justify-content: space-between;
    gap: 16px;
    padding: 14px 20px;
    border-bottom: 1px solid var(--border-default);
    text-decoration: none;
    color: inherit;
    background: var(--surface-primary);
}

.report-index li:last-child .report-index-row {
    border-bottom: 0;
}

.report-index-row:hover {
    background: var(--surface-secondary);
}

.report-index-main {
    display: flex;
    flex-direction: column;
    gap: 2px;
    min-width: 0;
}

.report-index-title {
    font-size: 14px;
    font-weight: 600;
    color: var(--text-primary);
}

.report-index-desc {
    font-size: 12.5px;
    color: var(--text-secondary);
}

.report-index-meta {
    display: flex;
    align-items: center;
    gap: 10px;
    flex-shrink: 0;
}

.report-index-scope {
    font-family: var(--font-mono, monospace);
    font-size: 11px;
    color: var(--text-tertiary);
}

/* ── the What-Changed digest ── */
.digest-grid {
    display: grid;
    grid-template-columns: repeat(auto-fit, minmax(240px, 1fr));
    gap: 12px;
    margin-bottom: 18px;
}

.digest-card {
    background: var(--surface-primary);
    border: 1px solid var(--border-default);
    border-radius: var(--radius-lg);
    padding: 16px 18px;
}

.digest-head {
    display: flex;
    align-items: center;
    justify-content: space-between;
    gap: 10px;
    margin-bottom: 10px;
}

.digest-title {
    font-size: 10.5px;
    letter-spacing: .08em;
    text-transform: uppercase;
    color: var(--text-tertiary);
}

.digest-count {
    font-size: 18px;
    font-weight: 700;
    color: var(--text-tertiary);
}

.digest-count.is-warn {
    color: var(--status-danger);
}

.digest-empty {
    font-size: 12.5px;
    color: var(--text-tertiary);
    margin: 0;
}

.digest-list {
    list-style: none;
    margin: 0;
    padding: 0;
    display: flex;
    flex-direction: column;
    gap: 8px;
}

.digest-item {
    display: flex;
    flex-direction: column;
    gap: 2px;
    width: 100%;
    text-align: left;
    background: transparent;
    border: 0;
    padding: 0;
    cursor: pointer;
    color: inherit;
    font: inherit;
}

.digest-item-title {
    font-size: 13px;
    font-weight: 600;
    color: var(--text-primary);
}

.digest-item-code {
    font-family: var(--font-mono, monospace);
    font-size: 10.5px;
    font-weight: 500;
    color: var(--text-tertiary);
}

.digest-item-text {
    font-size: 12.5px;
    line-height: 1.45;
    color: var(--text-secondary);
}

/* ── charts (D111): inline SVG, DS tokens, both themes ── */
.chart-grid {
    display: grid;
    grid-template-columns: repeat(auto-fit, minmax(320px, 1fr));
    gap: 14px;
    margin-bottom: 20px;
}

.chart-card {
    background: var(--surface-primary);
    border: 1px solid var(--border-default);
    border-radius: var(--radius-lg);
    padding: 18px 20px 14px;
}

.chart-card.is-wide {
    grid-column: 1 / -1;
}

.chart-card-head {
    margin-bottom: 12px;
}

.chart-card-title {
    font-size: 14.5px;
    font-weight: 600;
    color: var(--text-primary);
    margin: 0;
}

.chart-card-sub {
    font-size: 12px;
    color: var(--text-tertiary);
    margin: 2px 0 0;
}

.chart {
    margin: 0;
}

.chart-svg {
    width: 100%;
    height: auto;
    overflow: visible;
}

.chart-empty {
    font-size: 13px;
    color: var(--text-tertiary);
    padding: 28px 0;
    text-align: center;
}

.chart-grid line,
.chart-grid {
    stroke: var(--border-default);
}

line.chart-grid {
    stroke: var(--border-default);
    stroke-width: 1;
}

.chart-zero {
    stroke: var(--border-strong);
    stroke-width: 1.25;
}

.chart-tick,
.chart-axis {
    font-family: var(--font-mono, monospace);
    font-size: 10px;
    fill: var(--text-tertiary);
}

.chart-bar.is-first {
    fill: var(--brand-primary);
}

.chart-bar.is-second {
    fill: var(--status-warning);
}

.chart-line {
    stroke: var(--brand-primary);
    stroke-width: 2;
    stroke-linecap: round;
    stroke-linejoin: round;
}

.chart-line.is-projected {
    stroke-dasharray: 5 4;
    opacity: .75;
}

.chart-dot {
    fill: var(--brand-primary);
}

.chart-dot.is-projected {
    fill: var(--surface-primary);
    stroke: var(--brand-primary);
    stroke-width: 1.5;
}

.chart-legend {
    display: flex;
    align-items: center;
    gap: 8px;
    flex-wrap: wrap;
    font-size: 11.5px;
    color: var(--text-tertiary);
    margin-top: 10px;
}

.chart-key {
    display: inline-block;
    width: 10px;
    height: 10px;
    border-radius: 2px;
    background: var(--brand-primary);
}

.chart-key.is-second {
    background: var(--status-warning);
}

.chart-key.is-line {
    background: var(--brand-primary);
}

.chart-key.is-projected {
    background: transparent;
    border: 1.5px dashed var(--brand-primary);
}

/* ── the breakdown (a length, not an angle) ── */
.chart-breakdown {
    margin: 0;
}

.breakdown-list {
    list-style: none;
    margin: 0;
    padding: 0;
    display: flex;
    flex-direction: column;
    gap: 8px;
}

.breakdown-row {
    display: grid;
    grid-template-columns: minmax(140px, 1.4fr) minmax(80px, 2fr) auto 52px;
    align-items: center;
    gap: 12px;
    font-size: 12.5px;
}

.breakdown-name {
    color: var(--text-primary);
    overflow: hidden;
    text-overflow: ellipsis;
    white-space: nowrap;
}

.breakdown-code {
    font-family: var(--font-mono, monospace);
    font-size: 10.5px;
    color: var(--text-tertiary);
    margin-right: 6px;
}

.breakdown-bar {
    display: block;
    height: 10px;
    border-radius: 5px;
    background: var(--surface-secondary);
    overflow: hidden;
}

.breakdown-fill {
    display: block;
    height: 100%;
    width: var(--fill, 0%);
    background: var(--brand-primary);
    border-radius: 5px;
}

.breakdown-amount {
    font-family: var(--font-mono, monospace);
    color: var(--text-primary);
    text-align: right;
    white-space: nowrap;
}

.breakdown-share {
    font-family: var(--font-mono, monospace);
    font-size: 11.5px;
    color: var(--text-tertiary);
    text-align: right;
}

/* ── the sparkline in the matrix's TREND column ── */
.sparkline {
    width: 80px;
    height: 18px;
    display: block;
}

.sparkline-line {
    stroke: var(--brand-primary);
    stroke-width: 1.5;
    stroke-linecap: round;
    stroke-linejoin: round;
}

.sparkline-flag {
    fill: var(--status-danger);
}

/* ── the owner report's preview of the email ── */
.send-bar {
    display: flex;
    align-items: center;
    justify-content: space-between;
    gap: 16px;
    flex-wrap: wrap;
    background: var(--surface-secondary);
    border: 1px solid var(--border-default);
    border-radius: var(--radius-lg);
    padding: 14px 18px;
    margin-bottom: 18px;
}

.send-bar-title {
    font-size: 14px;
    font-weight: 600;
    color: var(--text-primary);
    margin: 0;
}

.send-bar-sub {
    font-size: 12.5px;
    color: var(--text-secondary);
    margin: 2px 0 0;
}

.email-preview {
    max-width: 660px;
    margin: 0 auto 20px;
    background: var(--surface-primary);
    border: 1px solid var(--border-default);
    border-radius: var(--radius-lg);
    overflow: hidden;
    box-shadow: var(--shadow-elevated, 0 4px 24px rgba(0, 0, 0, .08));
}

.email-preview-head {
    padding: 28px 32px 24px;
    background: var(--surface-inverse);
    /* --text-on-inverse DOES NOT EXIST. It was written here as a guess with --text-primary as
       the fallback, which is DARK - so the whole header rendered dark-on-dark and the title,
       the sub-line and the net figure were invisible. Found by reading the built capture;
       Lessons.md 1.67 is the standing rule, and this is its second instance. The real token is
       --text-inverse. */
    color: var(--text-inverse);
}

.email-preview-eyebrow {
    font-family: var(--font-mono, monospace);
    font-size: 10.5px;
    letter-spacing: .08em;
    text-transform: uppercase;
    color: var(--brand-primary);
    margin: 0 0 8px;
}

.email-preview-title {
    font-size: 22px;
    font-weight: 700;
    margin: 0 0 4px;
}

.email-preview-sub {
    font-size: 12.5px;
    opacity: .75;
    margin: 0;
}

.email-preview-headline {
    font-size: 16px;
    font-weight: 700;
    margin: 18px 0 0;
}

.email-preview-margin {
    font-size: 12px;
    font-weight: 500;
    opacity: .75;
    margin-left: 8px;
}

.email-preview-body {
    padding: 24px 32px;
}

.email-preview-note {
    margin: 0 0 18px;
    padding: 12px 16px;
    border-left: 3px solid var(--brand-primary);
    background: var(--surface-secondary);
    border-radius: var(--radius-md);
    font-size: 13px;
    color: var(--text-secondary);
}

.email-preview-foot {
    padding: 16px 32px;
    border-top: 1px solid var(--border-default);
    background: var(--surface-secondary);
    font-size: 11.5px;
    line-height: 1.5;
    color: var(--text-tertiary);
}

/* ── small shared pieces ── */
.page-crumb {
    font-size: 12px;
    color: var(--text-tertiary);
    margin: 0 0 4px;
}

.toolbar {
    display: flex;
    align-items: flex-end;
    gap: 16px;
    flex-wrap: wrap;
    margin-bottom: 16px;
}

.toolbar-note {
    font-size: 12px;
    color: var(--text-tertiary);
    margin: 0;
}

.field-inline {
    display: flex;
    flex-direction: column;
    gap: 4px;
}

.field-label {
    font-size: 10.5px;
    letter-spacing: .07em;
    text-transform: uppercase;
    color: var(--text-tertiary);
}

.field-static {
    font-size: 13px;
    color: var(--text-primary);
    margin: 0 0 10px;
    display: flex;
    flex-direction: column;
    gap: 2px;
}

.kpi-sub {
    font-size: 11.5px;
    color: var(--text-tertiary);
}

.row-sub {
    display: block;
    font-size: 11.5px;
    color: var(--text-tertiary);
    margin-top: 2px;
}

.footnote {
    font-size: 12px;
    color: var(--text-tertiary);
    line-height: 1.5;
    margin: 12px 0 0;
}

.is-projected > td {
    color: var(--text-secondary);
    font-style: italic;
}

/* The success/neutral tones F17 needed. The port already had .is-warn and .is-danger; a report
   that BALANCES, an account with nothing outstanding and a growing cash position are all good
   news, and there was no token for saying so. */
.info-box.is-ok {
    border-color: var(--status-success);
    background: var(--status-success-soft);
    color: var(--status-success-text, var(--text-primary));
}

.status-pill.is-ok {
    background: var(--status-success-soft);
    color: var(--status-success-text, var(--status-success));
}

.status-pill.is-neutral {
    background: var(--surface-secondary);
    color: var(--text-secondary);
}

/* A segmented control that wraps, for the matrix filter rows. The port's .segmented is a fixed
   strip; five or six filters need to fall onto a second line on a narrow workspace. */
.segmented.is-wrap {
    flex-wrap: wrap;
}

/* ── F20: the work surfaces (My Work, Team Work, the board's Needs-you band) ──────────────────

   GROUPED TABLES. F19 wrote .group-head / .group-title / .group-meta into two scoped files and
   said the third caller would be the moment to stop copying (MyTasks.razor.css). My Work is the
   third, so the heading lives here and the two scoped copies are gone. One rule, three screens.

   The top rule was `var(--border-subtle)` in both copies — a token that does not exist, so the
   declaration was dropped and neither screen ever drew it (Lessons.md §1.67's third instance,
   P20-3). `--border-hairline` is the DS's name for that line. */
.group-head {
    display: flex;
    align-items: center;
    justify-content: space-between;
    gap: var(--space-4);
    padding: 14px 16px 10px;
    border-top: 1px solid var(--border-hairline);
}

/* The first group sits directly under the card's top edge, which already draws a line. */
.group-head:first-child,
.group-tools + .group-head {
    border-top: 0;
}

.group-title {
    font-weight: 600;
    font-size: 14px;
    color: var(--text-primary);
}

.group-meta {
    display: inline-flex;
    align-items: center;
    gap: var(--space-3);
    font-size: 12px;
    color: var(--text-secondary);
    white-space: nowrap;
}

/* A group heading that collapses. The whole title is the control, so the target is the width of
   the words and not a 12px chevron. */
.group-toggle {
    appearance: none;
    border: 0;
    background: transparent;
    padding: 0;
    display: inline-flex;
    align-items: baseline;
    gap: var(--space-2);
    font: inherit;
    color: inherit;
    cursor: pointer;
    min-width: 0;
}

.group-client {
    font-size: 12.5px;
    color: var(--text-label);
}

.group-frac {
    font-family: var(--font-mono);
    font-size: 11.5px;
    color: var(--text-label);
}

.group-chevron {
    display: inline-block;
    font-size: 14px;
    line-height: 1;
    color: var(--text-label);
    transition: transform var(--transition-fast);
}

.group-chevron.is-collapsed {
    transform: rotate(-90deg);
}

/* Collapse all / Expand all, only rendered when there is more than one group to act on. */
.group-tools {
    display: flex;
    justify-content: flex-end;
    gap: var(--space-2);
    padding: 8px 12px 0;
}

/* The toolbar's two ends. `.list-toolbar` lays its children in one row; the spacer pushes the
   sort and the count to the right edge, the way the reference's VIEWS row reads. */
.toolbar-spacer {
    flex: 1;
}

.sort-control {
    display: inline-flex;
    align-items: center;
    gap: var(--space-2);
}

.sort-control .form-select {
    width: auto;
    min-width: 150px;
}

/* The active-filter chips inside the amber banner. Each is a button that lifts its own filter. */
.filter-chips {
    display: flex;
    align-items: center;
    flex-wrap: wrap;
    gap: var(--space-2);
}

.filter-chips button.meta-chip {
    appearance: none;
    border: 0;
    font: inherit;
    cursor: pointer;
}

/* THE PHASE ROW. A lettered disc names the role (B/R/S/C, the reference's KIND_TONE map); red
   when the phase is blocked whatever its role, because blocked is the fact that matters. The disc
   is the DS avatar at 26px with a tone modifier — a colour on an avatar is a design-system
   question, so the tones sit here rather than in a page. */
.phase-cell {
    display: inline-flex;
    align-items: center;
    gap: var(--space-3);
}

.avatar.phase-disc {
    width: 26px;
    height: 26px;
    font-size: 11.5px;
    border-radius: var(--radius-md);
}

.avatar.tone-brand { background: var(--brand-primary-soft); color: var(--brand-on-soft); }
.avatar.tone-info { background: var(--status-info-soft); color: var(--status-info-text); }
.avatar.tone-accent { background: var(--brand-accent-soft); color: var(--text-primary); }
.avatar.tone-warning { background: var(--status-warning-soft); color: var(--status-warning-text); }
.avatar.tone-danger { background: var(--status-danger-soft); color: var(--status-danger-text); }

/* A blocked phase carries a red left edge, as the reference's rows do. `box-shadow` inset rather
   than a border, so the table's column model is untouched (TableCellLayoutTests). */
.work-table tr.phase-row.is-blocked > td:first-child {
    box-shadow: inset 3px 0 0 var(--status-danger);
}

.progress-pill {
    font-family: var(--font-mono);
    font-variant-numeric: tabular-nums;
    align-self: flex-start;
}

/* The verb and the peek chevron, side by side in the action column. */
.action-cell {
    display: inline-flex;
    align-items: center;
    gap: var(--space-2);
}

.peek-toggle {
    padding-inline: 6px;
}

/* THE TASK PEEK. One phase's tasks, expanded in place under its row: done disc, name, who, status.
   Tinted like the drawer body so it reads as a detail of the row above and not as more rows. */
.peek-row > td {
    padding: 0;
    background: var(--surface-secondary);
}

.peek-list {
    list-style: none;
    margin: 0;
    padding: 8px 16px 10px 52px;
    display: flex;
    flex-direction: column;
    gap: 6px;
}

.peek-item {
    display: grid;
    grid-template-columns: 18px minmax(0, 1fr) auto auto;
    align-items: center;
    gap: var(--space-3);
    font-size: 13px;
}

.peek-disc {
    width: 16px;
    height: 16px;
    border-radius: var(--radius-pill);
    border: 1.5px solid var(--border-strong);
    display: inline-flex;
    align-items: center;
    justify-content: center;
    font-size: 10px;
    line-height: 1;
    color: var(--status-success-text);
}

.peek-disc.is-done {
    background: var(--status-success-soft);
    border-color: var(--status-success);
}

.peek-name {
    min-width: 0;
    overflow: hidden;
    text-overflow: ellipsis;
    white-space: nowrap;
}

.peek-who {
    font-size: 12px;
    color: var(--text-label);
}

/* ── F20: the firm dashboard (D126–D128) ───────────────────────────────────────────────────────
   Composition only: the cards are the DS's own `.dash-card` family (unused until this screen), the
   tiles `.kpi`, the banner `.info-box`. What is new is the hero, the Getting-Started panel's
   internals, the quick-action tiles, the stacked health strip and the feed. */

.dash-hero {
    margin-bottom: var(--space-8);
}

.dash-date {
    display: block;
    margin-bottom: 6px;
}

.dash-greeting {
    margin: 0 0 6px;
}

.dash-sub {
    max-width: 70ch;
}

/* THE GETTING-STARTED PANEL. The head is one button (the whole bar toggles); the steps are an
   ordered list whose current row is tinted and carries the only CTA. */
.gs-panel {
    margin-bottom: var(--space-8);
}

.gs-head {
    appearance: none;
    border: 0;
    background: transparent;
    width: 100%;
    display: grid;
    grid-template-columns: auto minmax(0, 1fr) auto auto;
    align-items: center;
    gap: var(--space-4);
    padding: 14px 18px;
    font: inherit;
    color: inherit;
    text-align: left;
    cursor: pointer;
}

.gs-panel:not(.is-collapsed) > .gs-head {
    border-bottom: 1px solid var(--border-default);
}

.gs-star {
    display: inline-flex;
    align-items: center;
    justify-content: center;
    width: 30px;
    height: 30px;
    border-radius: var(--radius-md);
    background: var(--status-success-soft);
    color: var(--status-success-text);
}

.gs-head-text {
    display: flex;
    flex-direction: column;
    gap: 2px;
    min-width: 0;
}

.gs-title {
    font-weight: 600;
    font-size: 14px;
    color: var(--text-primary);
}

.gs-sub {
    font-size: 12.5px;
    color: var(--text-secondary);
}

.gs-progress {
    display: inline-flex;
    align-items: center;
    gap: var(--space-3);
    min-width: 160px;
}

.gs-steps {
    list-style: none;
    margin: 0;
    padding: 0;
}

.gs-step {
    display: grid;
    grid-template-columns: 24px minmax(0, 1fr) auto;
    align-items: center;
    gap: var(--space-4);
    padding: 12px 18px;
    border-bottom: 1px solid var(--border-hairline);
}

.gs-step.is-current {
    background: var(--brand-primary-soft);
}

.gs-disc {
    display: inline-flex;
    align-items: center;
    justify-content: center;
    width: 22px;
    height: 22px;
    border-radius: var(--radius-pill);
    font-size: 11px;
    font-weight: 600;
    background: var(--surface-tertiary);
    color: var(--text-secondary);
}

.gs-step.is-done .gs-disc {
    background: var(--status-success);
    color: #fff;
}

.gs-step.is-current .gs-disc {
    background: var(--brand-primary);
    color: #fff;
}

.gs-step-text {
    display: flex;
    flex-direction: column;
    gap: 2px;
    min-width: 0;
}

.gs-step-title {
    font-weight: 600;
    font-size: 13.5px;
    color: var(--text-primary);
}

.gs-step-blurb {
    font-size: 12.5px;
    color: var(--text-secondary);
}

.gs-foot {
    display: flex;
    align-items: center;
    justify-content: space-between;
    gap: var(--space-4);
    padding: 12px 18px;
    font-size: 12.5px;
    color: var(--text-secondary);
}

/* The setup banner that takes the panel's slot once it is dismissed. */
.setup-banner {
    display: flex;
    align-items: center;
    justify-content: space-between;
    gap: var(--space-4);
    flex-wrap: wrap;
    margin-bottom: var(--space-8);
}

.setup-banner-text {
    display: flex;
    flex-direction: column;
    gap: 2px;
}

.setup-banner-actions {
    display: flex;
    align-items: center;
    gap: var(--space-2);
    flex-wrap: wrap;
}

.dash-kpis {
    margin-bottom: var(--space-6);
}

/* Three quick-action tiles: an icon disc and two lines, the reference's exact anatomy. */
.quick-actions {
    display: grid;
    grid-template-columns: repeat(auto-fit, minmax(240px, 1fr));
    gap: var(--space-4);
    margin-bottom: var(--space-8);
}

.quick-action {
    display: flex;
    align-items: center;
    gap: var(--space-4);
    padding: 14px 16px;
    background: var(--surface-primary);
    border: 1px solid var(--border-default);
    border-radius: var(--radius-lg);
    color: inherit;
    text-decoration: none;
    transition: all var(--transition-fast);
}

.quick-action:hover {
    transform: translateY(-1px);
    box-shadow: var(--shadow-card);
    text-decoration: none;
}

.quick-action-icon {
    display: inline-flex;
    align-items: center;
    justify-content: center;
    width: 34px;
    height: 34px;
    border-radius: var(--radius-md);
    background: var(--brand-primary-soft);
    color: var(--brand-on-soft);
    font-size: 16px;
    flex: none;
}

.quick-action-text {
    display: flex;
    flex-direction: column;
    gap: 1px;
    font-size: 12.5px;
    color: var(--text-secondary);
}

.quick-action-text strong {
    font-size: 13.5px;
    color: var(--text-primary);
}

/* Two columns, main and aside, that stack on a narrow viewport. */
.dash-grid {
    display: grid;
    grid-template-columns: minmax(0, 2fr) minmax(0, 1fr);
    gap: var(--space-6);
    align-items: start;
}

@media (max-width: 960px) {
    .dash-grid {
        grid-template-columns: minmax(0, 1fr);
    }
}

.dash-main,
.dash-aside {
    display: flex;
    flex-direction: column;
    gap: var(--space-6);
}

/* The stacked health strip: one segment per bucket, sized by a runtime custom property
   (the sanctioned inline form), labelled with its count when it has room. */
.health-strip {
    display: flex;
    height: 28px;
    border-radius: var(--radius-md);
    overflow: hidden;
    margin: var(--space-4) 0;
}

.health-seg {
    flex: 0 0 var(--seg, 0%);
    display: inline-flex;
    align-items: center;
    justify-content: center;
    font-size: 11.5px;
    font-weight: 600;
    color: #fff;
    overflow: hidden;
    white-space: nowrap;
}

.health-legend {
    list-style: none;
    margin: 0;
    padding: 0;
    display: grid;
    grid-template-columns: repeat(2, minmax(0, 1fr));
    /* Column-major, three per column, so the legend reads as the reference's two columns —
       Bookkeeper / Supervisor / Ready to lock, then Reconciliation / Client / Locked — rather
       than pairing across (first capture). */
    grid-template-rows: repeat(3, auto);
    grid-auto-flow: column;
    gap: 6px var(--space-6);
    font-size: 12.5px;
    color: var(--text-secondary);
}

.health-legend li {
    display: inline-flex;
    align-items: center;
    gap: 8px;
}

.legend-dot {
    width: 8px;
    height: 8px;
    border-radius: var(--radius-pill);
    flex: none;
}

/* The six buckets, in the legend's order. Colours are the status tokens where the bucket has a
   status meaning, the brand tokens where it does not. */
.seg-bookkeeper { background: var(--brand-primary); }
.seg-supervisor { background: var(--status-warning); }
.seg-ready { background: var(--status-success); }
.seg-reconciliation { background: var(--status-info); }
.seg-client { background: var(--brand-accent); }
.seg-locked { background: var(--text-tertiary); }

/* One row of a summary card: an icon disc, two lines, and an optional verb on the right. */
.dash-row {
    display: flex;
    align-items: center;
    gap: var(--space-4);
    padding: 10px 0;
}

.dash-row + .dash-row {
    border-top: 1px solid var(--border-hairline);
}

.dash-row .identity-cell {
    flex: 1;
}

.dash-row-icon {
    display: inline-flex;
    align-items: center;
    justify-content: center;
    width: 32px;
    height: 32px;
    border-radius: var(--radius-md);
    font-size: 14px;
    flex: none;
    background: var(--surface-tertiary);
    color: var(--text-secondary);
}

.dash-row-icon.is-success { background: var(--status-success-soft); color: var(--status-success-text); }
.dash-row-icon.is-warning { background: var(--status-warning-soft); color: var(--status-warning-text); }
.dash-row-icon.is-danger { background: var(--status-danger-soft); color: var(--status-danger-text); }

/* The recent-activity feed: time on the left, the sentence on the right. */
.activity-feed {
    list-style: none;
    margin: 0;
    padding: 0;
    display: flex;
    flex-direction: column;
}

.activity-item {
    display: grid;
    grid-template-columns: 96px minmax(0, 1fr);
    gap: var(--space-4);
    padding: 8px 0;
    font-size: 13px;
    border-bottom: 1px solid var(--border-hairline);
}

.activity-item:last-child {
    border-bottom: 0;
}

.activity-when {
    font-family: var(--font-mono);
    font-size: 11.5px;
    color: var(--text-label);
    white-space: nowrap;
}

.activity-what {
    min-width: 0;
}

/* The kind eyebrow sits between the verb and the label; Razor eats the whitespace around an
   @if that follows it (Lessons.md §1.87), so the gap is a margin, never a space. */
.activity-what .eyebrow {
    margin: 0 6px 0 4px;
}

/* ── F20 — notifications (D130) ──────────────────────────────────────────────────────────
   The bell in the appbar and the inbox it opens. The count is the rail's .nav-count vocabulary
   pinned to the glyph's corner; the inbox is a .card of rows, unread rows carrying a brand
   hairline and the heavier title, the glyph disc reusing the avatar tones. */
.portal-bell {
    position: relative;
    overflow: visible;
}

/* Pinned to the glyph's top-right CORNER, outside its strokes: the first cut sat the count on
   the bell itself and "10" read as "1θ" in the capture (built-rail-firm-badges, run 3c). */
.portal-bell .bell-count {
    position: absolute;
    top: -3px;
    right: -5px;
    min-width: 16px;
    padding: 0 4px;
    border-radius: var(--radius-pill);
    background: var(--status-danger);
    color: var(--text-on-dark);
    font-family: var(--font-mono);
    font-size: 9.5px;
    font-weight: 700;
    line-height: 16px;
    text-align: center;
}

.inbox {
    padding: 0;
}

.inbox-list {
    list-style: none;
    margin: 0;
    padding: 0;
}

.inbox-row {
    display: grid;
    grid-template-columns: 32px minmax(0, 1fr) auto;
    gap: 12px;
    align-items: start;
    padding: 12px 16px;
    border-bottom: 1px solid var(--border-hairline);
}

.inbox-row:last-child {
    border-bottom: 0;
}

.inbox-row.is-unread {
    box-shadow: inset 3px 0 0 var(--brand-primary);
}

.inbox-glyph {
    width: 32px;
    height: 32px;
    border-radius: 50%;
    display: inline-flex;
    align-items: center;
    justify-content: center;
    font-size: 14px;
    font-weight: 700;
    background: var(--surface-tertiary);
    color: var(--text-secondary);
}

.inbox-glyph.tone-accent { background: var(--brand-accent-soft); color: var(--text-primary); }
.inbox-glyph.tone-warning { background: var(--status-warning-soft); color: var(--status-warning-text); }
.inbox-glyph.tone-danger { background: var(--status-danger-soft); color: var(--status-danger-text); }
.inbox-glyph.tone-neutral { background: var(--surface-tertiary); color: var(--text-secondary); }

.inbox-main {
    display: block;
    min-width: 0;
    color: inherit;
    text-decoration: none;
}

.inbox-main:hover .inbox-title {
    text-decoration: underline;
}

.inbox-title {
    display: block;
    font-weight: 500;
    color: var(--text-primary);
}

.inbox-row.is-unread .inbox-title {
    font-weight: 600;
}

.inbox-body {
    display: block;
    margin-top: 2px;
    font-size: 13px;
    color: var(--text-secondary);
}

.inbox-meta {
    display: flex;
    flex-direction: column;
    align-items: flex-end;
    gap: 4px;
}

.inbox-when {
    font-family: var(--font-mono);
    font-size: 11.5px;
    color: var(--text-label);
    white-space: nowrap;
}

.inbox-tools {
    display: inline-flex;
    gap: 2px;
}

.inbox-foot {
    display: flex;
    align-items: center;
    justify-content: space-between;
    gap: 12px;
    padding: 10px 16px;
    border-top: 1px solid var(--border-hairline);
    font-size: 13px;
    color: var(--text-secondary);
}

/* ── F20 — the workspace dashboard (D134) ───────────────────────────────────────────────────
   The per-business landing over the DS's .dash-card family (the same grid as the firm
   dashboard). New here: the ink period hero, and the named-stage timeline built on the DS
   stepper's vocabulary rather than a second one. */
.ws-hero {
    padding: 22px 24px;
    border-radius: var(--radius-lg);
    background: var(--brand-dark);
    color: var(--text-on-dark);
}

.ws-hero-head {
    display: flex;
    align-items: center;
    justify-content: space-between;
    gap: var(--space-4);
}

.ws-hero-eyebrow {
    font-family: var(--font-mono);
    font-size: 11px;
    font-weight: 600;
    letter-spacing: .08em;
    text-transform: uppercase;
    color: var(--brand-primary);
}

.ws-hero-badge {
    padding: 2px 10px;
    border-radius: var(--radius-pill);
    background: var(--status-danger);
    color: var(--text-on-dark);
    font-size: 12px;
    font-weight: 600;
}

.ws-hero-title {
    margin: 8px 0 2px;
    font-family: var(--font-display);
    font-size: 24px;
    font-weight: 600;
    color: var(--text-on-dark);
}

.ws-hero-sub {
    margin: 0 0 14px;
    font-size: 13.5px;
    color: var(--text-on-dark-muted);
}

.ws-hero-bar {
    height: 6px;
    border-radius: var(--radius-pill);
    background: rgba(255, 255, 255, .12);
    overflow: hidden;
}

.ws-hero-fill {
    display: block;
    width: var(--progress, 0%);
    height: 100%;
    border-radius: var(--radius-pill);
    background: var(--brand-primary);
}

.ws-hero-foot {
    display: flex;
    justify-content: space-between;
    gap: var(--space-4);
    margin-top: 10px;
    font-family: var(--font-mono);
    font-size: 12px;
    color: var(--text-on-dark-muted);
}

/* The timeline: one column per stage, a track between the dots, the name under each. */
/* Nine named stages share the card's width; a long name hyphenates at a syllable rather
   at 10px ("Categorization" is the widest the catalogue ships); the break-anywhere is the
   last resort for a firm-named stage longer than that, not the expected path. */
.ws-timeline {
    display: grid;
    grid-auto-flow: column;
    grid-auto-columns: minmax(0, 1fr);
    gap: 0;
    list-style: none;
    margin: 0;
    padding: 0;
}

.ws-stage {
    position: relative;
    display: flex;
    flex-direction: column;
    align-items: center;
    gap: 6px;
    padding: 6px 1px 0;
    text-align: center;
    min-width: 0;
}

.ws-stage-track {
    position: absolute;
    top: 0;
    left: 4px;
    right: 4px;
    height: 3px;
    border-radius: var(--radius-pill);
    background: var(--border-default);
}

.ws-stage.is-complete .ws-stage-track {
    background: var(--brand-primary);
}

.ws-stage.is-blocked .ws-stage-track {
    background: var(--status-danger);
}

.ws-stage-dot {
    display: grid;
    place-items: center;
    width: 27px;
    height: 27px;
    margin-top: 8px;
    border: 1.5px solid var(--border-strong);
    border-radius: var(--radius-pill);
    font-family: var(--font-mono);
    font-size: 12px;
    font-weight: 600;
    color: var(--text-secondary);
    background: var(--surface-primary);
}

.ws-stage.is-complete .ws-stage-dot {
    background: var(--brand-primary-soft);
    border-color: var(--brand-primary);
    color: var(--brand-on-soft);
}

.ws-stage.is-blocked .ws-stage-dot {
    background: var(--status-danger-soft);
    border-color: var(--status-danger);
    color: var(--status-danger-text);
}

.ws-stage-name {
    font-size: 10px;
    font-weight: 500;
    letter-spacing: -.015em;
    color: var(--text-primary);
    overflow-wrap: anywhere;
}

.ws-stage-meta {
    font-family: var(--font-mono);
    font-size: 11px;
    color: var(--text-label);
}

.ws-stage-chip {
    font-size: 10.5px;
}

.ws-clear {
    text-align: center;
    padding: 14px 0;
}

/* The status table's current month, and the Recent Periods "now" marker. */
.ws-now-row td {
    background: var(--brand-primary-soft);
}

.ws-now-row .cell-title {
    color: var(--brand-on-soft);
    font-weight: 600;
}

.ws-now {
    color: var(--brand-on-soft);
    font-weight: 600;
}

/* Quick actions inside a card stack two-up, not the firm dashboard's full-width row. */
.ws-quick {
    grid-template-columns: repeat(2, minmax(0, 1fr));
    margin-bottom: 0;
}

.ws-quick .quick-action {
    padding: 10px 12px;
}

.dash-row.is-unread .cell-title {
    font-weight: 600;
}

/* ── F20 S6 — the board regions (D132), the All-periods board (D133), Team Work (D129) ───────
   Composition over the DS: the stepper is the DS's own .step vocabulary laid on the board; the
   Needs-you band, the toolbar's toggle pills, the stage accordions, the team grid and the
   capacity aside are new. */

/* The "Needs you" band: eyebrow line, danger banner, next-task row. */
.needs-you {
    margin-bottom: var(--space-6);
    background: var(--surface-primary);
    border: 1px solid var(--border-default);
    border-radius: var(--radius-lg);
    overflow: hidden;
}

.needs-you-head {
    display: flex;
    align-items: center;
    gap: var(--space-3);
    padding: 10px 16px;
    border-bottom: 1px solid var(--border-hairline);
}

.needs-you-dot {
    width: 8px;
    height: 8px;
    border-radius: 50%;
    background: var(--brand-primary);
}

.needs-you-counts {
    font-family: var(--font-mono);
    font-size: 12px;
    color: var(--text-secondary);
}

.needs-you-blocker {
    display: flex;
    gap: var(--space-3);
    padding: 12px 16px;
    background: var(--status-danger-soft);
    color: var(--status-danger-text);
    font-size: 13.5px;
}

.needs-you-blocker .cell-sub {
    color: inherit;
    opacity: .85;
}

.needs-you-icon,
.needs-you-play {
    flex: none;
    width: 24px;
    height: 24px;
    display: inline-flex;
    align-items: center;
    justify-content: center;
    border-radius: var(--radius-md);
    font-size: 12px;
}

.needs-you-play {
    background: var(--brand-primary-soft);
    color: var(--brand-on-soft);
}

.needs-you-next {
    display: flex;
    align-items: center;
    gap: var(--space-4);
    padding: 12px 16px;
}

.needs-you-next .identity-cell {
    flex: 1;
}

.needs-you-actions {
    display: inline-flex;
    gap: var(--space-2);
}

/* The stage stepper on the board. */
.board-stepper {
    list-style: none;
    margin: 0 0 var(--space-6);
    padding: 16px 18px;
    background: var(--surface-primary);
    border: 1px solid var(--border-default);
    border-radius: var(--radius-lg);
    display: flex;
    align-items: flex-start;
    gap: 0;
    overflow-x: auto;
}

.board-stepper .step-text {
    align-items: center;
    text-align: center;
}

.board-stepper .step {
    flex-direction: column;
    align-items: center;
    position: relative;
    gap: 6px;
}

.board-stepper .step-line {
    position: absolute;
    top: 13px;
    right: 50%;
    width: 100%;
    margin: 0;
    min-width: 0;
}

.board-stepper .step-dot {
    position: relative;
    z-index: 1;
}

/* Eleven counted tabs keep one line each and the STRIP wraps — a scrolling strip hid the trailing
   "All months →" link off-canvas in the second capture, as an unwrapped one had in the first. */
.board-tabs {
    flex-wrap: wrap;
    align-items: flex-end;
}

.board-tabs .tab {
    white-space: nowrap;
    flex: none;
}

.board-tabs .tab-link {
    margin-left: auto;
    text-decoration: none;
    color: var(--text-link);
    font-weight: 500;
}

.board-tabs .toolbar-spacer {
    flex: 1;
}

/* The toolbar's status pills are toggles. */
.board-toolbar {
    margin-bottom: var(--space-4);
}

.status-pill.is-toggle {
    cursor: pointer;
    border: 1px solid transparent;
    background: var(--surface-primary);
    color: var(--text-secondary);
    border-color: var(--border-default);
}

.status-pill.is-toggle.is-on.active {
    background: var(--status-success-soft);
    color: var(--status-success-text);
    border-color: transparent;
}

.status-pill.is-toggle.is-on.error {
    background: var(--status-danger-soft);
    color: var(--status-danger-text);
    border-color: transparent;
}

.you-chip {
    display: inline-block;
    margin-left: 6px;
    padding: 0 6px;
    border-radius: var(--radius-sm);
    background: var(--brand-primary-soft);
    color: var(--brand-on-soft);
    font-family: var(--font-mono);
    font-size: 10px;
    font-weight: 700;
    letter-spacing: .06em;
    text-transform: uppercase;
    vertical-align: middle;
}

/* The stage accordions on the All-periods board. */
.stage-acc {
    margin-bottom: var(--space-3);
}

.stage-acc.is-active {
    border-color: var(--brand-primary);
}

.stage-acc.is-blocked {
    border-color: var(--status-danger);
}

.stage-acc-head {
    display: flex;
    align-items: center;
    gap: var(--space-4);
    width: 100%;
    padding: 14px 18px;
    background: transparent;
    border: 0;
    text-align: left;
    cursor: pointer;
    color: inherit;
}

.stage-acc-head .identity-cell {
    flex: 1;
}

.stage-acc-head .step-dot.is-done {
    background: var(--brand-primary-soft);
    border-color: var(--brand-primary);
    color: var(--brand-on-soft);
}

.stage-acc-tools {
    display: inline-flex;
    align-items: center;
    gap: var(--space-3);
}

/* The team-progress grid. */
.team-progress {
    margin-top: var(--space-6);
    background: var(--surface-primary);
    border: 1px solid var(--border-default);
    border-radius: var(--radius-lg);
}

.team-progress-head {
    display: flex;
    align-items: center;
    gap: var(--space-3);
    width: 100%;
    padding: 12px 16px;
    background: transparent;
    border: 0;
    text-align: left;
    cursor: pointer;
    color: inherit;
}

.team-progress-sum {
    flex: 1;
    font-size: 13px;
    color: var(--text-secondary);
}

.team-grid {
    display: grid;
    grid-template-columns: repeat(auto-fill, minmax(280px, 1fr));
    gap: var(--space-4);
    padding: 0 16px 16px;
}

.team-card {
    padding: 12px 14px;
    border: 1px solid var(--border-default);
    border-radius: var(--radius-lg);
    display: flex;
    flex-direction: column;
    gap: 10px;
}

.team-card.is-unassigned {
    border-style: dashed;
}

.team-card-head {
    display: flex;
    align-items: center;
    gap: var(--space-3);
}

.team-card-head .identity-cell {
    flex: 1;
}

.team-card-frac {
    font-family: var(--font-mono);
    font-size: 12px;
    color: var(--text-secondary);
}

.team-card .progress-bar {
    width: 100%;
}

.team-card-counts {
    display: flex;
    gap: var(--space-4);
    font-family: var(--font-mono);
    font-size: 12px;
    color: var(--text-secondary);
}

.tone-ok {
    color: var(--status-success-text);
}

.tone-danger {
    color: var(--status-danger-text);
}

/* Team Work: the list wins the width; the capacity aside is narrow. */
.team-work-grid {
    grid-template-columns: minmax(0, 3fr) minmax(260px, 1fr);
}

/* Team Work's due column never wraps a date. */
.team-work-grid td:nth-child(4) {
    white-space: nowrap;
}

.capacity-load {
    font-family: var(--font-mono);
    font-size: 13px;
    color: var(--text-secondary);
}

.capacity-load strong {
    font-size: 16px;
    color: var(--text-primary);
}

.dash-row.is-unassigned .cell-title {
    color: var(--text-secondary);
    font-style: italic;
}

.work-table tr.is-blocked td:first-child,
.data-table tr.is-blocked td:first-child {
    box-shadow: inset 3px 0 0 var(--status-danger);
}

/* ── F20b: the demo business, its links, and the walkthrough coach ────────────────────────── */

/*  "See it on the demo" on Getting-Started steps 6-10 (D146). A quiet second affordance beside
    the step's own "Start →": the primary button is what the firm should do with its own books,
    and this is the rehearsal. Deliberately a link, not a button — it navigates and nothing more. */
.gs-demo-link {
    flex-shrink: 0;
    font-size: 12.5px;
    font-weight: 500;
    color: var(--brand-primary-strong);
    text-decoration: none;
    white-space: nowrap;
}

.gs-demo-link:hover {
    text-decoration: underline;
}

/*  "Hide sample data" sits at the far end of the panel's footer, past the Dismiss control: it is
    the rarer and heavier of the two, and grouping them would invite the wrong one. */
.gs-hide-demo {
    margin-left: auto;
}

/*  The getting-ready card (D147): a firm that has just signed up, watching its sample data
    arrive. Same card frame as the panel it will be replaced by, so the swap does not jump. */
.welcome-card {
    display: flex;
    align-items: center;
    gap: var(--space-4);
    padding: 18px;
    margin-bottom: var(--space-8);
}

.welcome-spinner {
    flex-shrink: 0;
    width: 20px;
    height: 20px;
    border: 2px solid var(--border-default);
    border-top-color: var(--brand-primary);
    border-radius: 50%;
    animation: welcome-spin 0.9s linear infinite;
}

@keyframes welcome-spin {
    to { transform: rotate(360deg); }
}

@media (prefers-reduced-motion: reduce) {
    .welcome-spinner { animation: none; border-style: dashed; }
}

.welcome-text {
    display: flex;
    flex-direction: column;
    gap: 2px;
}

.welcome-title {
    margin: 0;
    font-size: 14px;
    font-weight: 600;
    color: var(--text-primary);
}

.welcome-sub {
    margin: 0;
    font-size: 12.5px;
    color: var(--text-secondary);
}

/*  The "Sample data" pill on the demo's row in Businesses (D145). Neutral on purpose: the demo is
    not a state the business is in, it is a note about what the row is — and every count above it
    has already left the row out. */
.demo-pill {
    margin-left: 6px;
}

/*  THE WALKTHROUGH COACH (D146). A bottom-right dock over the demo screen it narrates, above the
    page and below any modal — a caption, never a blocker: the newcomer can work the screen
    underneath while it is open, which is the whole point of narrating a real screen. */
.wc-dock {
    position: fixed;
    right: var(--space-6);
    bottom: var(--space-6);
    z-index: 40;
    max-width: 380px;
}

.wc-card {
    display: flex;
    flex-direction: column;
    gap: 10px;
    padding: 16px 18px;
    background: var(--surface-raised);
    border: 1px solid var(--border-default);
    border-radius: var(--radius-lg);
    box-shadow: var(--shadow-lg);
}

.wc-head {
    display: flex;
    align-items: center;
    justify-content: space-between;
    gap: var(--space-4);
}

.wc-badge {
    font-size: 11px;
    font-weight: 600;
    letter-spacing: 0.04em;
    text-transform: uppercase;
    color: var(--brand-primary-strong);
}

.wc-close {
    display: inline-flex;
    align-items: center;
    justify-content: center;
    width: 22px;
    height: 22px;
    padding: 0;
    background: none;
    border: none;
    border-radius: var(--radius-sm);
    color: var(--text-secondary);
    cursor: pointer;
}

.wc-close:hover {
    background: var(--surface-sunken);
    color: var(--text-primary);
}

.wc-title {
    font-size: 14px;
    font-weight: 600;
    color: var(--text-primary);
}

.wc-body {
    font-size: 12.5px;
    line-height: 1.5;
    color: var(--text-secondary);
}

.wc-foot {
    display: flex;
    align-items: center;
    justify-content: space-between;
    gap: var(--space-4);
    padding-top: 4px;
}

@media (max-width: 640px) {
    .wc-dock {
        right: var(--space-4);
        left: var(--space-4);
        max-width: none;
    }
}

/*  The dock's three controls. Named rather than styled by position so a test — and a reader —
    can point at "the Next button" in a capture: F20b's whole subject is a set of controls the
    reference renders and does not wire (M189), and evidence for the fix has to be nameable. */
.wc-back,
.wc-next,
.wc-finish {
    flex: 0 0 auto;
}

.wc-back[disabled] {
    opacity: 0.45;
}

/*  ROW MENUS ON A SHORT TABLE (F20b, P20b-1).

    A row's ⋯ menu is absolutely positioned inside the card, and .table-card clips it: its
    `overflow-x: auto` (load-bearing — a wide table scrolls inside its own card rather than
    dragging the page sideways) forces `overflow-y` to a scrolling value, so `visible` is not
    available on either axis. With several rows the menu opens over the rows beneath it and is
    seen; with ONE row there is nothing beneath it and the card clips it to a sliver.

    That is not a hypothetical: F20b makes it every new firm's first Businesses screen, whose only
    row is the demo — and the row menu is where "Remove sample data" lives. Found on
    built-demo-businesses-menu-light.png, which is kept as the evidence.

    The fix reserves menu room under a SHORT table only (three rows or fewer), so no table pays
    for it that does not need it. The better fix is to take the menu out of the card entirely —
    CSS anchor positioning, or a native popover — which is a change to every list screen's menu
    and belongs to whichever phase does that work, not to this one. */
/*  FA3: SCOPED TO CARDS THAT ACTUALLY HAVE A MENU.
    As first written this matched EVERY short table card, so a screen made of many small tables
    paid 190px of dead space per table. The period-close board is exactly that shape — ten stage
    sections, most holding one or two tasks — and it rendered as a column of near-empty cards with
    the content crammed into the top of each. Its rows carry plain action links and no ⋯ menu at
    all, so every one of those reserves was for a menu that does not exist. */
.table-card:has(.menu-anchor):has(.data-table tbody tr:nth-last-child(-n+3):first-child) {
    padding-bottom: 190px;
}


/* ── THE ACCOUNT FEED PICKER (FA3) ─────────────────────────────────────────────
   How one engagement account's data reaches the firm. Shared by the create wizard's Accounts
   step and the detail screen's Add-account modal, so the two cannot drift — which is how these
   flags came to have no writer in the first place.

   The chips are TOGGLES, not a segmented control: bank feed, CSV and PDF are independent
   opt-ins and an account may carry all three. `.meta-chip.is-off` already draws the unselected
   state (dashed, struck through) and `.is-brand` the selected one, so the only thing this block
   adds is layout and the warning tone. */
.account-feed-picker {
    display: flex;
    flex-direction: column;
    gap: var(--space-2);
}

/* The feed and its delivery are one question in two halves, so they share a line and align on
   their controls — the chips are the same height as the select beside them, and the select's hint
   hangs below without dragging the chips down with it. */
.account-feed-controls {
    display: flex;
    align-items: flex-start;
    gap: var(--space-5);
    flex-wrap: wrap;
}

.account-feed-picker .filter-chips {
    gap: var(--space-2);
    min-height: 34px;
    align-items: center;
}

/* Bigger than a read-only meta chip, because these are controls the user clicks. `.meta-chip.is-off`
   ships with a line-through — right for "this was removed", wrong for "not chosen yet". */
.account-feed-picker .filter-chips button.meta-chip {
    padding: 7px 14px;
    font-size: 10.5px;
    text-decoration: none;
    border: 1px solid transparent;
}

.account-feed-picker .filter-chips button.meta-chip.is-off {
    border-color: var(--border-default);
    background: transparent;
}

.account-feed-picker .filter-chips button.meta-chip:hover:not(:disabled) {
    border-color: var(--brand-primary);
}

.account-feed-picker .feed-delivery {
    margin-top: 0;
    min-width: 260px;
    flex: 1 1 260px;
}

/* A hint that is a warning rather than a note. Same size and rhythm as .form-hint so the field
   does not jump when it appears, and a token colour so it reads in both themes. */
.form-hint.is-warning {
    color: var(--status-warning-text);
    font-weight: 500;
}


/* ── THE CREATE WIZARD (FA3) ───────────────────────────────────────────────────

   The rail needs air beneath it. `.steps` is a bare flex row with no bottom margin — fine where
   it is the last thing in a header, wrong directly above the panel it indexes, where the current
   step's label sat on the card's top edge. */
.steps + .wizard-step,
.steps + .info-box { margin-top: var(--space-5); }

/* FIELD ALIGNMENT INSIDE A ROW.
   `.form-row > .form-field` is `justify-content: space-between` with the control pushed down by
   `margin-top: auto`, so the control sinks to the BOTTOM of the cell. Two fields side by side
   where only one carries a `.form-hint` then disagree: the hintless field's control drops to the
   cell floor while its neighbour's sits above its hint, and the pair reads as a stagger — which
   is exactly what step 1's Type/Setup row did.

   Aligning from the TOP instead and letting the hint sink is what makes controls line up: the
   labels already reserve a line (`min-height: 1.15em`), so the controls start together and a hint
   simply hangs below whichever field has one. */
.wizard-step .form-row > .form-field { justify-content: flex-start; }

.wizard-step .form-row > .form-field > .form-input,
.wizard-step .form-row > .form-field > .form-select,
.wizard-step .form-row > .form-field > .form-textarea { margin-top: 0; }

.wizard-step .form-row > .form-field > .form-hint { margin-top: auto; padding-top: 5px; }

   Step 2 declares accounts and staffs the team, step 4 previews the board the engagement will
   actually generate, and step 6 reviews it in four cards. All three were missing or thin before
   FA3, so the layout for them lives here rather than in the design system: they are one screen's
   composition of existing DS primitives (.form-row, .record-rows, .meta-chip), not new
   components. */

/* Two stacked cards on step 2 — accounts, then people — so the step reads as two questions
   rather than one long form. */
.wizard-card + .wizard-card {
    margin-top: var(--space-6);
    padding-top: var(--space-6);
    border-top: 1px solid var(--border-subtle, var(--border-default));
}

.wizard-accounts {
    display: flex;
    flex-direction: column;
    gap: var(--space-4);
}

.wizard-account {
    padding: var(--space-4);
    border: 1px solid var(--border-default);
    border-radius: var(--radius-md);
    background: var(--surface-primary);
}

/* A named account with no feed can never receive a statement. The row says so with a warning
   edge the moment it becomes true, instead of waiting for the Next click. */
.wizard-account.is-incomplete {
    border-color: var(--status-warning-text);
    box-shadow: inset 3px 0 0 var(--status-warning-text);
}

.form-field.is-narrow { max-width: 130px; }

/* The account row is a real form, not a table: name and kind on one line, institution and last
   four beneath, the feed picker under both. A single 5-column row squeezed every control and put
   the Remove button on the same baseline as the inputs, which read as a sixth field. */
.wizard-account .form-row { grid-template-columns: minmax(0, 2fr) minmax(0, 1fr); }

.wizard-account .account-feed-row {
    display: flex;
    align-items: flex-end;
    gap: var(--space-5);
    flex-wrap: wrap;
    margin-top: 18px;
    padding-top: var(--space-4);
    border-top: 1px dashed var(--border-hairline);
}

.wizard-account .account-feed-row > .form-field { margin-top: 0; }

.wizard-account .account-feed-row .feed-delivery { min-width: 260px; }

/* Remove sits with the card, not in the field grid — it acts on the whole account. */
.wizard-account-actions {
    display: flex;
    justify-content: flex-end;
    margin-top: var(--space-3);
}

/* The remove button sits on the field row and has no label of its own, so it needs the label's
   height back to line up with the inputs beside it. */
.form-field.is-action {
    display: flex;
    align-items: flex-end;
    padding-bottom: 2px;
}

.checkbox-row {
    display: flex;
    align-items: center;
    gap: var(--space-3);
    padding: var(--space-3) var(--space-4);
    margin: var(--space-4) 0 var(--space-5);
    border: 1px solid var(--border-default);
    border-radius: var(--radius-md);
    font-size: 13.5px;
    cursor: pointer;
}

.checkbox-row input[type=checkbox] { margin: 0; flex: none; }

.checkbox-row.is-on {
    border-color: transparent;
    background: var(--brand-primary-soft);
    color: var(--brand-on-soft);
}

/* THE ADD-A-MEMBER ROW.
   Three controls of different natures — two selects and a button — that must read as one action
   and line up with the table beneath. The default two-column .form-row cannot express it (the
   button has no label, so it floated to the top of its cell), so this row is its own grid with
   the button sized to its content and bottom-aligned to the selects. */
.member-add {
    display: grid;
    grid-template-columns: minmax(0, 1.4fr) minmax(0, 1fr) auto;
    gap: var(--space-4);
    align-items: end;
    margin-bottom: var(--space-5);
}

.member-add > .form-field { margin-top: 0; }

.member-add .member-add-action { justify-content: flex-end; }

/* ── Step 4: the generated board ─────────────────────────────────────────────── */
.task-stage {
    margin-top: var(--space-5);
    border: 1px solid var(--border-default);
    border-radius: var(--radius-md);
    overflow: hidden;
}

.task-stage-head {
    display: flex;
    align-items: center;
    gap: var(--space-3);
    padding: var(--space-3) var(--space-4);
    background: var(--surface-secondary, var(--surface-tertiary));
    border-bottom: 1px solid var(--border-default);
}

.task-stage-num {
    display: inline-flex;
    align-items: center;
    justify-content: center;
    width: 22px;
    height: 22px;
    border-radius: var(--radius-sm);
    background: var(--brand-primary-soft);
    color: var(--brand-on-soft);
    font-family: var(--font-mono);
    font-size: 11px;
    font-weight: 600;
}

.task-stage-name {
    font-weight: 600;
    color: var(--text-primary);
}

/* Pushes the assign-all control to the trailing edge, where a stage-wide action belongs. */
.task-stage-assign {
    display: flex;
    align-items: center;
    gap: var(--space-2);
    margin-left: auto;
}

.task-stage-assign .form-label { margin: 0; white-space: nowrap; }

.form-select-sm { padding: 3px 8px; font-size: 12px; }

/* One row per generated task. The grid is fixed so the assignee and override columns line up
   down the whole stage — a ragged assignee column makes "who has what" unreadable, which is the
   one question this step exists to answer. */
.task-row {
    display: grid;
    grid-template-columns: minmax(0, 2.4fr) 0.8fr 1.2fr 1.3fr auto;
    align-items: center;
    gap: var(--space-3);
    padding: var(--space-3) var(--space-4);
    border-top: 1px solid var(--border-subtle, var(--border-default));
}

.task-row:first-of-type { border-top: 0; }

.task-row-name {
    display: flex;
    align-items: center;
    gap: var(--space-2);
    color: var(--text-primary);
}

.task-row-role,
.task-row-assignee {
    font-size: 12.5px;
    color: var(--text-secondary);
}

/* Holds the per-task override select. min-width:0 so a long person name shrinks the control
   instead of widening the column and pushing the row off the card. */
.task-row-override { min-width: 0; }

.task-row-override .form-select { width: 100%; }

.task-row-action { text-align: right; }

/* Unassigned firm work is a real problem — somebody has to do it and nobody will. Client work
   with no customer is NOT flagged: it is expected, and the row says so in words instead. */
.task-row.is-unassigned {
    box-shadow: inset 3px 0 0 var(--status-warning-text);
}

.task-row.is-removed .task-row-name,
.task-stage.is-removed .task-stage-name {
    text-decoration: line-through;
    color: var(--text-label);
}

/* ── Step 6: review ──────────────────────────────────────────────────────────── */
/* THE REVIEW GRID.
   `minmax(300px, 1fr)` packed three cards onto a 1000px panel, and at ~320px each the k/v rows
   inside — a 200px label column from `.config-row` — left barely a hundred pixels for the value,
   so "Bookkeeper + customer + supervisor" broke to one word per line and the cards read as
   ransom notes. Wider cards and a label column that yields are what fix it; two-up is the honest
   maximum for this content. */
.review-grid {
    display: grid;
    grid-template-columns: repeat(auto-fit, minmax(min(100%, 420px), 1fr));
    gap: var(--space-4);
}

.review-card {
    padding: var(--space-5);
    border: 1px solid var(--border-default);
    border-radius: var(--radius-lg);
    background: var(--surface-primary);
}

/* Inside a review card the rows are a summary, not a settings list: the label is a quiet
   fixed-width gutter and the value takes everything else, right-aligned so the eye can scan the
   answers down the card rather than hunting for them at ragged left edges. */
.review-card .record-rows { border: 0; }

.review-card .config-row {
    grid-template-columns: 110px 1fr;
    gap: var(--space-4);
    padding: 10px 0;
    align-items: baseline;
    border-bottom: 1px solid var(--border-hairline);
}

.review-card .config-row:last-child { border-bottom: 0; padding-bottom: 0; }

.review-card .config-row .k {
    font-size: 12.5px;
    color: var(--text-label);
}

.review-card .config-row .v {
    text-align: right;
    font-weight: 500;
    color: var(--text-primary);
    overflow-wrap: anywhere;
}

.review-card-head {
    display: flex;
    align-items: baseline;
    justify-content: space-between;
    margin-bottom: var(--space-3);
}

.review-card-title {
    margin: 0;
    font-size: 14px;
    font-weight: 600;
    color: var(--text-primary);
}

/* Wide content must scroll inside its own box rather than pushing the page sideways. */
@media (max-width: 900px) {
    .task-row {
        grid-template-columns: 1fr;
        gap: var(--space-2);
    }

    .task-row-action { text-align: left; }
}

/* ── THE PERIOD-CLOSE BOARD'S STAGE TABLES (FA3) ───────────────────────────────
   Ten stages, ten tables, one board. `table-layout: fixed` with stated widths is what makes the
   columns line up down the whole page instead of each section sizing itself to its own longest
   task name — the difference between a board you scan and ten tables you read. */
.board-task-table {
    table-layout: fixed;
    width: 100%;
}

.board-task-table th:nth-child(1), .board-task-table td:nth-child(1) { width: 34%; }
.board-task-table th:nth-child(2), .board-task-table td:nth-child(2) { width: 22%; }
.board-task-table th:nth-child(3), .board-task-table td:nth-child(3) { width: 16%; }
.board-task-table th:nth-child(4), .board-task-table td:nth-child(4) { width: 12%; }
.board-task-table th:nth-child(5), .board-task-table td:nth-child(5) { width: 16%; text-align: right; }

/* A long task name wraps rather than forcing the table wider than its card. */
.board-task-table td { overflow-wrap: anywhere; }

/* ── THE ACCOUNT FEED PICKER'S TWO HALVES (FA3) ────────────────────────────────
   The chips and the delivery select are two answers to one question, so their LABELS have to sit
   on the same line. With the chip label rendered by the page and the delivery label by the
   component, the two started at different heights and the row read as two unrelated fields. Both
   labels now belong to the component, so they are siblings and align by construction. */
.account-feed-controls > .form-field { margin-top: 0; }

.account-feed-controls .form-label { min-height: 1.15em; }

/* The client-seat warning is a consequence of the checkbox above it, not a caption for the
   Person field below — it needs to sit with what it explains. */
.checkbox-row + .form-hint.is-warning {
    margin: calc(var(--space-4) * -1) 0 var(--space-5);
}


/* ── THE CSV IMPORT WIZARD (FA3) ───────────────────────────────────────────────
   Six columns of real statement data in a 640px shell left the preview scrolling on BOTH axes,
   with the horizontal bar sitting on the last row and the vertical bar inside the cell padding.
   The mapping selects above it are the actual task; the preview exists to confirm them, so it
   needs to be readable at a glance rather than navigated. */
.modal-card.csv-wizard {
    width: min(1240px, 100%);
    max-height: calc(100vh - 48px);
    display: flex;
    flex-direction: column;

    /* The processing overlay positions against the dialog, so the dialog is its containing block. */
    position: relative;
}

.csv-preview-label {
    margin: var(--space-5) 0 var(--space-2);
    display: block;
}

/* A bounded, scrollable window — not a table that grows the dialog past the viewport. The height
   shows about five rows, which is enough to recognise your own statement. */
.csv-preview {
    max-height: 260px;
    overflow: auto;
    border: 1px solid var(--border-default);
    border-radius: var(--radius-md);
    padding: 0;
}

/* The header stays put while the rows move under it — a mapping preview whose column names
   scroll away is asking the reader to remember which column they were checking. */
.csv-preview .data-table thead th {
    position: sticky;
    top: 0;
    z-index: 1;
    background: var(--surface-secondary, var(--surface-tertiary));
    box-shadow: inset 0 -1px 0 var(--border-default);
    white-space: nowrap;
}

.csv-preview .data-table {
    margin: 0;
    font-size: 12.5px;
}

.csv-preview .data-table th,
.csv-preview .data-table td {
    padding: 7px 12px;
    white-space: nowrap;
}

/* The description is the one column worth letting breathe; everything else is short and reads
   better held on one line. */
.csv-preview .data-table td:last-child {
    white-space: normal;
    min-width: 220px;
}

/* Money right-aligns so the decimal points line up and a mis-mapped column is obvious. */
.csv-preview .data-table td.td-mono {
    font-variant-numeric: tabular-nums;
}

/* A checkbox sharing a .form-row with a labelled select has no label of its own, so it floated
   to the top of its cell. Bottom-aligning it puts it on the select's baseline. */
.wiz-reverse {
    align-self: end;
    min-height: 38px;
}

/* The save-format check is a decision about the NEXT import, not part of the mapping above it. */
.csv-wizard .csv-preview + .form-field {
    margin-top: var(--space-4);
    padding-top: var(--space-4);
    border-top: 1px solid var(--border-hairline);
}


/* ── THE CSV IMPORT WIZARD, TWO-PANE SHELL (FA4) ───────────────────────────────────
   Rev 2.0's wizard is a full-viewport modal with a persistent left rail and a scrolling
   preview beside it, and the rail is what makes the thing readable: the reader can always
   see WHICH account and WHICH files they are mapping, on every step. D58 says the port
   renders the reference's layout in the DS rather than importing its stylesheet, so none of
   Rev 2.0's cw-* rules are here — this is the DS's own .stepper, .kpi-strip, .tabs,
   .status-pill and .data-table, laid out in a rail-plus-pane shell. */

.csvw-head {
    display: flex;
    align-items: flex-start;
    justify-content: space-between;
    gap: var(--space-6);
    padding: 20px 24px 16px;
    border-bottom: 1px solid var(--border-default);
    flex-wrap: wrap;
}

/* The rail is a fixed reading column; only the pane beside it scrolls. A stepper that
   stretched with the dialog would put the last step off the edge on a narrow laptop. */
.csvw-stepper {
    flex: 1 1 420px;
    max-width: 620px;
    min-width: 0;
}

.csvw-stepper .step-dot { flex: none; }

.csvw-body {
    display: grid;
    grid-template-columns: 280px minmax(0, 1fr);
    gap: 0;
    min-height: 0;
    flex: 1;
    overflow: hidden;
}

.csvw-rail {
    border-right: 1px solid var(--border-default);
    background: var(--surface-secondary, var(--surface-tertiary));
    padding: 20px;
    overflow-y: auto;
    display: flex;
    flex-direction: column;
    gap: var(--space-6);
}

.csvw-rail-block { display: flex; flex-direction: column; gap: var(--space-3); }

.csvw-rail-title {
    margin: 0;
    font-size: 11px;
    font-weight: 600;
    letter-spacing: .06em;
    text-transform: uppercase;
    color: var(--text-label);
}

.csvw-facts { margin: 0; padding-left: 18px; font-size: 12.5px; color: var(--text-secondary); }
.csvw-facts li { margin-bottom: 4px; }

.csvw-pane { display: flex; flex-direction: column; min-width: 0; min-height: 0; }

.csvw-pane-head {
    display: flex;
    align-items: baseline;
    justify-content: space-between;
    gap: var(--space-4);
    padding: 14px 24px;
    border-bottom: 1px solid var(--border-hairline);
    flex-wrap: wrap;
}

.csvw-pane-title { font-weight: 600; font-size: 14px; }
.csvw-pane-meta { font-size: 12.5px; color: var(--text-label); }

.csvw-pane-body {
    padding: 20px 24px;
    overflow-y: auto;
    display: flex;
    flex-direction: column;
    gap: var(--space-5);
    min-height: 0;
}

.csvw-center { align-items: center; justify-content: center; text-align: center; }

/* A centred column shrinks its children to min-content, which turned the success summary's three
   tiles into a single stacked file. The strip is the one child that wants the full width. */
.csvw-center .kpi-strip,
.csvw-center .info-box { align-self: stretch; }

/* The file picker. A label wrapping an InputFile is the whole control, and the input stays
   focusable inside it so the keyboard reaches the picker. */
.csvw-dropzone {
    display: flex;
    flex-direction: column;
    align-items: center;
    gap: var(--space-3);
    width: min(520px, 100%);
    padding: 36px 28px;
    border: 1.5px dashed var(--border-default);
    border-radius: var(--radius-lg);
    background: var(--surface-primary);
    cursor: pointer;
    transition: border-color var(--transition-fast), background var(--transition-fast);
}

.csvw-dropzone:hover { border-color: var(--brand-primary); background: var(--surface-tertiary); }
.csvw-dropzone:focus-within { border-color: var(--brand-primary); }
.csvw-dropzone input[type="file"],
.csvw-add input[type="file"] {
    position: absolute;
    width: 1px;
    height: 1px;
    padding: 0;
    margin: -1px;
    overflow: hidden;
    clip-path: inset(50%);
    white-space: nowrap;
}
.csvw-dropzone-title { font-weight: 600; font-size: 15px; }
.csvw-dropzone-sub { font-size: 12.5px; color: var(--text-secondary); max-width: 40ch; }

.csvw-formats { display: flex; gap: 6px; flex-wrap: wrap; justify-content: center; }

.csvw-chip {
    font-family: var(--font-mono);
    font-size: 10px;
    padding: 3px 8px;
    border-radius: var(--radius-pill);
    background: var(--surface-tertiary);
    color: var(--text-secondary);
}

.csvw-foot {
    display: flex;
    align-items: center;
    justify-content: space-between;
    gap: var(--space-4);
    padding: 14px 24px;
    border-top: 1px solid var(--border-default);
    flex-wrap: wrap;
}

.csvw-foot-status { font-size: 12.5px; color: var(--text-secondary); display: flex; gap: 10px; align-items: center; }
.csvw-foot-actions { display: flex; gap: 10px; }

/* ── The file list, in the rail and over each preview table ─────────────────────── */

.csvw-file {
    display: flex;
    align-items: center;
    gap: var(--space-2);
    padding: 8px 10px;
    background: var(--surface-primary);
    border: 1px solid var(--border-hairline);
    border-radius: var(--radius-md);
}

.csvw-file-meta { display: flex; flex-direction: column; min-width: 0; flex: 1; }

.csvw-file-name {
    font-size: 12.5px;
    font-weight: 600;
    overflow: hidden;
    text-overflow: ellipsis;
    white-space: nowrap;
}

.csvw-file-size { font-size: 11.5px; color: var(--text-label); }

.csvw-add {
    position: relative;
    display: inline-flex;
    align-items: center;
    justify-content: center;
    gap: 6px;
    padding: 8px 10px;
    font-size: 12.5px;
    font-weight: 600;
    color: var(--brand-primary);
    border: 1px dashed var(--border-default);
    border-radius: var(--radius-md);
    cursor: pointer;
}

.csvw-add:hover { border-color: var(--brand-primary); background: var(--surface-primary); }
.csvw-add:focus-within { border-color: var(--brand-primary); }
.csvw-dropzone { position: relative; }



.csvw-file-card { padding: 0; }

.csvw-file-card-head {
    display: flex;
    align-items: center;
    justify-content: space-between;
    gap: var(--space-3);
    padding: 10px 14px;
    border-bottom: 1px solid var(--border-hairline);
    background: var(--surface-tertiary);
}

.csvw-pad { padding: 10px 14px; margin: 0; }

/* ── The mapping controls, IN the preview's header row (G4) ─────────────────────── */

.csvw-map-table thead th { vertical-align: top; }

.csvw-map-th { display: flex; flex-direction: column; gap: 4px; min-width: 128px; }

.csvw-map-field {
    font-size: 10.5px;
    font-weight: 700;
    letter-spacing: .05em;
    text-transform: uppercase;
    color: var(--text-label);
}

/* Deliberately smaller than a form select: it lives inside a table header, and a control at
   full form scale makes the header taller than the rows it labels. */
.csvw-map-select { font-size: 12px; padding: 4px 6px; height: auto; }

.csvw-scope { cursor: pointer; border: 0; font: inherit; }

/* ── Inline cell fixing ─────────────────────────────────────────────────────────── */

.csvw-fix { display: flex; align-items: center; gap: 10px; flex-wrap: wrap; }
.csvw-fix-input { max-width: 220px; }
.csvw-fix-reason { flex: 1; min-width: 140px; margin: 0; }

.csvw-bad { color: var(--status-danger); font-family: var(--font-mono); }

.csvw-reason { display: block; font-size: 11.5px; color: var(--text-label); margin-top: 2px; }

/* ── The confirmed-mapping summary in the rail ──────────────────────────────────── */

.csvw-summary {
    display: grid;
    grid-template-columns: auto auto;
    gap: 4px var(--space-3);
    margin: 0;
    font-size: 12px;
}

.csvw-summary dt { color: var(--text-label); }
.csvw-summary dd { margin: 0; text-align: right; font-weight: 600; }

.csvw-success-title { font-size: 18px; font-weight: 600; margin: 0; }
.csvw-success-sub { font-size: 13px; color: var(--text-secondary); margin: 0; max-width: 48ch; }

/* ── The processing overlay: four phases, a real percentage ─────────────────────── */

.csvw-proc {
    position: absolute;
    inset: 0;
    display: flex;
    align-items: center;
    justify-content: center;
    background: color-mix(in srgb, var(--surface-primary) 88%, transparent);
    border-radius: var(--radius-xl);
    z-index: 1;
}

.csvw-proc-card {
    width: min(440px, 92%);
    display: flex;
    flex-direction: column;
    gap: var(--space-4);
    padding: 28px;
    background: var(--surface-primary);
    border: 1px solid var(--border-default);
    border-radius: var(--radius-lg);
    box-shadow: var(--shadow-elevated);
    text-align: center;
}

/*  D159 - the ONE moving thing on this card. The AI phase is a single opaque call of about
    a minute, so the bar is parked at 45% by design (D150: a bar that creeps through a call it
    cannot see is a lie told smoothly). Parked and motionless, though, reads as hung - which is
    exactly what the owner reported. INDETERMINATE motion is not false progress: this ring says
    "working, duration unknown" while the percentage under it keeps saying only what it knows. */
.csvw-proc-spinner {
    align-self: center;
    width: 26px;
    height: 26px;
    border: 2.5px solid var(--border-default);
    border-top-color: var(--brand-primary);
    border-radius: 50%;
    animation: csvw-proc-spin .85s linear infinite;
}

@keyframes csvw-proc-spin {
    to { transform: rotate(360deg); }
}

.csvw-proc-title { margin: 0; font-size: 16px; font-weight: 600; }
.csvw-proc-sub { margin: 0; font-size: 12.5px; color: var(--text-secondary); }

.csvw-proc-track {
    height: 6px;
    border-radius: var(--radius-pill);
    background: var(--surface-tertiary);
    overflow: hidden;
}

/* The width is the ONE runtime-bound value, passed as a custom property — the sanctioned
   inline form (InlineStyleGuardrailTests). */
.csvw-proc-fill {
    display: block;
    height: 100%;
    width: var(--csvw-pct, 0%);
    background: var(--brand-primary);
    transition: width .4s ease;
}

/*  While the phase can report no sub-progress, a sheen travels across the fill. Note what is
    NOT animated: the width. The bar keeps the exact extent it has earned and the sheen says
    only that work is in flight - the same distinction the spinner makes, on the same card. */
.csvw-proc-fill.is-working {
    position: relative;
    overflow: hidden;
}

.csvw-proc-fill.is-working::after {
    content: "";
    position: absolute;
    inset: 0;
    background: linear-gradient(90deg,
        transparent 0%,
        color-mix(in srgb, var(--surface-primary) 65%, transparent) 50%,
        transparent 100%);
    animation: csvw-proc-sheen 1.4s ease-in-out infinite;
}

@keyframes csvw-proc-sheen {
    from { transform: translateX(-100%); }
    to { transform: translateX(100%); }
}

.csvw-proc-steps { list-style: none; margin: 0; padding: 0; display: flex; flex-direction: column; gap: 8px; }

.csvw-proc-step {
    display: flex;
    align-items: center;
    gap: 10px;
    font-size: 12.5px;
    color: var(--text-label);
    text-align: left;
}

.csvw-proc-step.is-active { color: var(--text-primary); font-weight: 600; }
.csvw-proc-step.is-done { color: var(--text-secondary); }

.csvw-proc-dot {
    width: 18px;
    height: 18px;
    flex: none;
    display: inline-flex;
    align-items: center;
    justify-content: center;
    font-size: 11px;
    border-radius: 50%;
    border: 1.5px solid var(--border-default);
}

.csvw-proc-step.is-done .csvw-proc-dot { border-color: var(--status-success); color: var(--status-success); }
.csvw-proc-step.is-active .csvw-proc-dot {
    border-color: var(--brand-primary);
    animation: csvw-proc-pulse 1.4s ease-in-out infinite;
}

@keyframes csvw-proc-pulse {
    0%, 100% { box-shadow: 0 0 0 0 color-mix(in srgb, var(--brand-primary) 50%, transparent); }
    50% { box-shadow: 0 0 0 4px transparent; }
}

/*  The DS reduced-motion block only shortens DURATIONS (ds/tokens/motion.css); it does not
    stop a keyframe loop, so every loop above states its own opt-out. What survives is the
    text: the title, the phase line and the step list still say what is happening. */
@media (prefers-reduced-motion: reduce) {
    .csvw-proc-spinner { animation: none; border-style: dashed; }
    .csvw-proc-fill.is-working::after { animation: none; }
    .csvw-proc-step.is-active .csvw-proc-dot { animation: none; }
}

.csvw-proc-lbl { flex: 1; }
.csvw-proc-cnt { font-family: var(--font-mono); font-size: 11.5px; }
.csvw-proc-pct { margin: 0; font-size: 11.5px; color: var(--text-label); font-variant-numeric: tabular-nums; }

/* A laptop in portrait, or a narrow window: the rail stacks above the pane rather than
   squeezing the preview into a column too narrow to map columns in. */
@media (max-width: 900px) {
    .csvw-body { grid-template-columns: minmax(0, 1fr); overflow-y: auto; }
    .csvw-rail { border-right: 0; border-bottom: 1px solid var(--border-default); overflow: visible; }
    .csvw-pane-body { overflow: visible; }
}


/* ── STAGING TOOLBAR, RESHAPED (FA5) ───────────────────────────────────────────────
   The filter block had grown to six control groups in one wrapping row: a VIEW switch, three
   FILTERS, a DISPLAY toggle, a search and a period select — every one of them the same size and
   weight, so a control that reshapes the table looked exactly like one that narrows it. Rev 2.0
   splits the same controls across two rows and the split is the whole idea: the tab row carries
   what changes the SHAPE of the table, the row beneath carries what changes its POPULATION, and
   the tab row's own bottom border separates them.

   These rules are the split plus a size pass. No new vocabulary — .tabs, .segmented, .btn and
   .form-select are the DS's, and .is-compact is a modifier on the existing chip. */

/* The tab row becomes a flex line that can carry trailing controls. .tabs already sets
   display:flex; this only lets the trailing group sit at the far end and stay off the border. */
.tabs .tabs-spacer { flex: 1 1 auto; }

.tabs > .segmented,
.tabs > .btn {
    align-self: center;
    margin-bottom: 6px;
    flex: none;
}

.tabs > .btn { margin-right: 8px; }

/* One filter row, and the things in it line up. Measured before the fix, the row held three
   different heights — search 40, chips 35, period select 31 — which is what made it read as ragged
   however the widths fell. One declared height across all three is the only way that stays true
   when a control's padding changes underneath. */
.list-toolbar.is-filters {
    align-items: center;
    gap: 8px;
    row-gap: 8px;
}

.list-toolbar.is-filters .input-search,
.list-toolbar.is-filters .segmented,
.list-toolbar.is-filters .form-select {
    height: 34px;
    box-sizing: border-box;
}

.list-toolbar.is-filters .input-search {
    padding-top: 0;
    padding-bottom: 0;
    align-items: center;
}

.list-toolbar.is-filters .form-select {
    padding-top: 0;
    padding-bottom: 0;
}

.list-toolbar.is-filters .input-search {
    flex: 1 1 220px;
    min-width: 180px;
    max-width: 300px;
}

/* The count belongs to the ROW, not to the control it happens to follow. */
.list-toolbar .toolbar-count { margin-left: auto; }

/* Rev 2.0's chip metrics — 6px/13px at 12.5px, against the DS default of 7px/14px at 13px. The
   difference is small and it is the difference between six chip groups reading as a toolbar and
   reading as a wall. */
.segmented.is-compact { padding: 2px; }

.segmented.is-compact button,
.segmented.is-compact a {
    padding: 6px 13px;
    font-size: 12.5px;
}

/* The period select is the only form control on the row; matched to the chips so the row has one
   baseline rather than three. */
.form-select.is-compact {
    padding-top: 6px;
    padding-bottom: 6px;
    font-size: 12.5px;
    height: auto;
    width: auto;
    min-width: 160px;
}

/* ── The accept verb (FA5) ─────────────────────────────────────────────────────────
   A tick is a legible verb only if it is unmistakably a button. Squared off and given a fixed
   width so a column of them lines up, and so it cannot be read as the row's status. */
.btn.btn-icon-check {
    min-width: 30px;
    padding-left: 8px;
    padding-right: 8px;
    font-weight: 700;
}

/* The group band's action cell holds Accept all; keep it off the band's right edge and stop the
   button stretching when the cell is wide. */
.grp-row .col-action { text-align: right; white-space: nowrap; }


/* ── The review step's rows scroll inside their card (FA5) ─────────────────────────
   The tab strip, the four KPI tiles and the tab's banner are the frame a reader reads the rows
   AGAINST — which tab, how many, what the tab means. Letting the whole pane scroll took all three
   off screen after the first dozen rows, on a step whose job is working down a few hundred.

   Bounded in viewport units rather than pixels: the dialog is already capped at the viewport, so a
   fixed pixel height either wastes a tall screen or overflows a short one. */
.csvw-review-scroll {
    max-height: min(46vh, 520px);
    overflow-y: auto;
    overflow-x: auto;
}

/* The header stays put while the rows move under it. A row-status column whose heading has
   scrolled away is a column of unlabelled pills — the same reasoning as .csv-preview's sticky
   header on the mapping step, which this deliberately mirrors rather than re-invents. */
.csvw-review-scroll .data-table thead th {
    position: sticky;
    top: 0;
    z-index: 1;
    background: var(--surface-secondary, var(--surface-tertiary));
    box-shadow: inset 0 -1px 0 var(--border-default);
}

/* An inline fix opens a row-wide editor; it must not be clipped by the scroll box's own padding
   when it lands on the last visible row. */
.csvw-review-scroll .csvw-fix { padding: 2px 0; }


/* ── THE STAGING GRID SCROLLS, AND PAGINATION IS GONE (D158) ───────────────────────
   Paging a table that is GROUPED ON THE CLIENT is the defect underneath the one the owner
   reported: each band counted only the rows that landed on the current page, so a group header
   read "51 txns · 36% of total" over an account whose group held more, and the percentages were
   percentages of a page. The whole job now loads in one read and the rows scroll inside their
   card, which also keeps the KPI strip, the tab rail and the filters — the frame the rows are read
   against — on screen while the reader works down them. */
.staging-scroll {
    max-height: min(58vh, 640px);
    overflow-y: auto;
    overflow-x: auto;
}

/* The header stays put while the rows move under it. Six columns of pills and selects with no
   headings is not a table you can read — the same reasoning as .csv-preview on the import wizard,
   mirrored rather than re-invented. */
.staging-scroll .data-table thead th {
    position: sticky;
    top: 0;
    z-index: 2;
    background: var(--surface-secondary, var(--surface-tertiary));
    box-shadow: inset 0 -1px 0 var(--border-default);
}

/* A group band is a heading INSIDE the scroll box, so it sticks under the column header rather
   than scrolling away from the rows it owns — on a long job the reader can otherwise no longer
   see which account the rows beneath belong to. */
.staging-scroll .grp-row > td,
.staging-scroll .grp-row > th {
    position: sticky;
    top: 38px;
    z-index: 1;
    background: var(--surface-tertiary);
}

/* The show-more row that replaces paging inside a band. */
.grp-more {
    text-align: center;
    padding: 8px 12px;
}

.grp-more .btn + .btn { margin-left: 8px; }
