/* ==========================================================================
   THEME — PAGE-SPECIFIC  (3 of 3)
   ==========================================================================
   Report date controls, filter rows, settings and routing pages, portfolio
   tabs. Rules that belong to one surface rather than to the system.

   ⛔ SPLIT FROM ONE 2,708-LINE FILE (2026-09-04) so no stylesheet is too long to
   review. Cut at DEPTH-0 BOUNDARIES — points where no rule, media query or
   comment was open — and reconstruction was verified byte-identical.

   ⛔ THE LOAD ORDER IN base.html IS THE CASCADE. These three are separate
   `<link>`s that load AFTER styles.css, so a rule here beats an equally specific
   rule in any @import'ed module on source order. Measured twice while fixing an
   icon leak: a rule in _10-components.css could not win against one here.

   Every rule is scoped `body.laser-theme`, gated by LASER_THEME_ENABLED — which
   defaults to False, so this whole layer is inert unless the flag is on.
   ========================================================================== */


body.laser-theme .help-text,
body.laser-theme .field-hint,
body.laser-theme .note,
body.laser-theme .hint {
    font-size: var(--font-size-caption);
    line-height: 1.5;
}

/* Tables: one head size, one cell size, everywhere. */
body.laser-theme table th {
    font-size: var(--font-size-caption);
}

body.laser-theme table td {
    font-size: var(--font-size-body-small);
}

/* Buttons all speak at one volume — a control that is quieter than its neighbour reads as
   secondary even when it is not. */
body.laser-theme .btn-action,
body.laser-theme .btn-success,
body.laser-theme .btn-primary,
body.laser-theme .btn-secondary,
body.laser-theme .btn-danger,
body.laser-theme .btn-edit,
body.laser-theme .btn-filter,
body.laser-theme .btn-report {
    font-size: var(--font-size-body-small);
    font-weight: 600;
}

/* ─── The report's date controls (#2095) ───────────────────────────────────── */

body.laser-theme .report-day-form,
body.laser-theme .report-range-form,
body.laser-theme .report-month-form {
    display: flex;
    flex-wrap: wrap;
    align-items: center;
    gap: var(--spacing-2);
    margin: 0;
}

body.laser-theme .report-day-step {
    display: flex;
    align-items: center;
    gap: var(--spacing-1);
}

/* `<input type="date">` — the native picker, themed. The calendar button is a UA shadow
   element, so `color-scheme: dark` is what makes the browser draw a DARK popup; without it
   the picker opens as a white panel on a near-black page. That is the whole reason this
   declaration exists, and it cannot be done any other way in CSS. */
body.laser-theme .report-date-input,
body.laser-theme input[type="date"] {
    color-scheme: dark;
    /* ⛔ THE PANEL FILL, NOT THE GLASS TOKENS — a date field is a CONTROL. Glass on a
       form control is where contrast fails worst: the fill goes translucent over
       whatever sits behind it, so the field's own extent stops being perceivable
       (#2085 measured exactly that failure — fill 1.08:1, border 1.60:1, an input
       whose boundary nothing conveyed until it took focus).
       ⚠️ `--laser-border-control` at 0.5 alpha is the value #2085 ruled sufficient
       for a control boundary (3.72:1); the glass rim's 0.35 is a decorative card
       edge and does NOT clear SC 1.4.11 for a field. */
    background: var(--laser-bg-card);
    border: 1px solid var(--laser-border-control);
    border-radius: var(--border-radius-md);
    color: var(--laser-text-primary);
    font-family: inherit;
    font-size: var(--fs-input);         /* >=16px or iOS zooms the field on focus */
    /* ⚠️ Geometry deliberately matches `.form-group input` (_01) and the themed field
       rule (_11) — one control height, owner ruling 2026-09-04. This rule loads LAST,
       so any number here silently wins: it measured 40px tall with 8px/16px padding
       while both earlier rules asked for 36px and `0 12px`, and a date field sat 4px
       proud of the text field beside it.
       ★ What this rule actually exists for is `color-scheme: dark` above — the only way
       to make the UA draw a dark picker popup. Keep that; do not re-add a size here. */
    height: var(--h-input);
    padding: 0 var(--s-3);
}

body.laser-theme input[type="date"]:hover {
    border-color: var(--laser-primary);
}

body.laser-theme input[type="date"]:focus {
    border-color: var(--laser-primary);
    box-shadow: var(--laser-glow-subtle);
    outline: none;
}

/* WCAG 2.4.7 -- `_11-theme-chrome.css:295` rings `body.laser-theme input:focus-visible`,
   but that is (0,2,2) and this file's `input[type="date"]` selector is (0,3,2), so the
   attribute selector wins and the date field alone loses its ring. `--laser-glow-subtle`
   is named for being subtle and is not a substitute. Matching the theme's own ring here
   keeps the date input consistent with every other themed control. */
body.laser-theme input[type="date"]:focus-visible {
    outline: 2px solid var(--laser-primary);
    outline-offset: 2px;
}

/* Tint the UA's calendar glyph to the brand — it ships near-black, which is invisible here. */
body.laser-theme input[type="date"]::-webkit-calendar-picker-indicator {
    filter: invert(0.8) sepia(1) saturate(6) hue-rotate(150deg);
    cursor: pointer;
}

body.laser-theme .report-window-label {
    color: var(--laser-text-secondary);
    font-size: var(--font-size-body-small);
}

/* A disabled step control must not look pressable. */
body.laser-theme .btn-action.disabled,
body.laser-theme .btn-action[aria-disabled="true"] {
    border-color: var(--laser-border-divider);
    color: var(--laser-text-disabled);
    cursor: not-allowed;
    pointer-events: none;
}

/* ═══ Fixes found by looking at the real UI (2026-09-03, org-admin session) ══ */

/* ⛔ THE SOLID CYAN FILL WAS REMOVED 2026-09-08 (owner, on sight of the rendered page).
   Three rules of the system said the same thing and the fill broke all three:

   1. The One Accent Rule (DESIGN.md Colors) — cyan is the PRIMARY ACTION and the current
      state. A data tile is neither. A filled tile competes with the buttons for the one
      colour that is supposed to mean "act here".
   2. Flat by hairline (DESIGN.md Elevation) — `box-shadow: var(--laser-glow)` was the only
      glow in the component layer. The single sanctioned shadow in this product is the
      dialog float; `laser.css`'s glow tokens are explicitly "not used by the component
      layer and not to be reached for as decoration".
   3. `_10-components.css:612` already argued the case in prose while this rule did the
      opposite: "a fully coloured tile pulls the eye to the container instead of the
      figure. The bar says 'this one' without competing with the value."

   ★ The accent BAR is the system's answer, and it already exists for the four tone
   variants. `.highlight` now takes the same 3px cyan rail, so all five read as one family.
   The paired near-black text goes with the fill — on the normal surface it would be the
   1.14:1 defect this block was originally written to fix, in reverse. */
body.laser-theme .metric-card.highlight {
    border-left: 3px solid var(--laser-primary);
}

/* ⛔ The hover fill was removed with the resting fill 2026-09-08: it repainted the tile
   solid cyan on pointer-over, which reintroduced exactly what the fill removal fixed.
   A KPI tile is not interactive — it has no href, no handler and no focus stop — so it
   takes no hover state at all. */

/* A `<select>` styled as a solid brand slab ("All Portfolios" rendered as a cyan block).
   A select is an INPUT: it takes the input treatment, or it reads as a pressed button. */
body.laser-theme .portfolio-select,
body.laser-theme select.portfolio-select,
body.laser-theme .portfolio-selector select {
    background-color: rgba(26, 26, 26, 0.7);
    color: var(--laser-text-primary);
    border: 1px solid var(--laser-border-cyan);
    font-weight: 400;
}

/* ─── Filter rows: Search LEFT, Clear RIGHT ────────────────────────────────
   Owner, 2026-09-03: "Search / Clear should be in one line and on different corners."
   `.filter-row` was a plain flow container, so the controls stacked and the buttons
   drifted to wherever the wrap left them.

   The pattern is standard for a filter bar and worth stating: inputs flow from the left,
   the SUBMIT sits at the end of that flow, and the DESTRUCTIVE reset is pushed to the far
   edge with `margin-left: auto` — so "Clear" is never adjacent to "Search" and cannot be
   hit by mistake while aiming for it. */
/* ⚠️ `.filter-row` is a COLUMN on the CSR dashboard (a narrow filter sidebar) and a ROW
   elsewhere (a wide filter bar). So the row treatment is scoped to `:not(...)` on the
   column case, and `flex-grow` is never set blind — on a column child it is a HEIGHT
   basis, which stretched the search box to 192px tall the first time this was written. */
body.laser-theme .filter-row {
    display: flex;
    flex-wrap: wrap;
    gap: var(--spacing-2);
}

/* Row layout: inputs flow left, submit ends the flow, reset is pushed to the far edge. */
body.laser-theme .filter-row:not(.filter-row-stacked) {
    align-items: center;
}

/* Never let a text input grow on the CROSS axis. A single-line control has a single-line
   height, whichever direction its parent flows. */
body.laser-theme .filter-input,
body.laser-theme .filter-row input[type="text"],
body.laser-theme .filter-row input[type="search"] {
    flex: 0 1 auto;
    min-width: 0;
    /* One control height (owner ruling 2026-09-04). A filter row is the surface the
       ruling is FOR — a select, a text input and a button standing at three heights is
       the thing it fixes — so this must not carry a number of its own. */
    height: var(--h-input);
    align-self: stretch;
}

body.laser-theme .filter-row > .btn-clear,
body.laser-theme .filter-row > a.btn-clear {
    margin-left: auto;
    /* A reset is not a primary action: outlined, and in the warning hue rather than the
       brand, so it never reads as the thing to press. */
    border-color: var(--warning-border-color);
    color: var(--laser-warning);
}

body.laser-theme .filter-row > .btn-clear:hover {
    background: var(--laser-warning);
    color: var(--laser-bg-page);
    box-shadow: none;
}

/* ─── A page title must not wrap at 118px ──────────────────────────────────
   "CSR Dashboard" broke across two lines inside a 118px box because the h1 sat in a flex
   row sized to its content. A title takes the width it needs. */
body.laser-theme .page-header,
body.laser-theme .dashboard-header {
    display: flex;
    flex-wrap: wrap;
    align-items: baseline;
    gap: var(--spacing-2);
}

body.laser-theme .page-header h1,
body.laser-theme .dashboard-header h1,
body.laser-theme h1.laser-title {
    flex: 0 1 auto;
    min-width: max-content;   /* never wrap a short title */
    margin: 0;
    text-wrap: balance;
}

/* ═══ NOTIFICATION PAGES ════════════════════════════════════════════════════

   Owner, 2026-09-03: "the notification pages are not having the same UI and they are
   very dirty and very bad… there is LASER, you can check the laser code anytime."

   Root cause, measured: **all 15 `.notif-*` classes had ZERO rules.** The whole
   notification UI (#1738/#1743) shipped with structurally-sound markup and no stylesheet
   at all, which is why it rendered as raw browser defaults — unstyled `<ul>` bullets,
   default link blue, no card, no spacing. It was never a "bad design"; it was an
   un-designed one, and the same omission as the bell being mounted nowhere.

   Ported from laser-platform/packages/frontend/src/pages/NotificationsPage.tsx, which is
   the reference the owner pointed at:

     <HolographicText variant="h4">Notifications</…>   the gradient page title
     <GlassCard>                                        the list lives in ONE glass card
     <List disablePadding> + <Divider>                  rows separated by hairlines
     <CircleIcon fontSize=10 color={unread?PRIMARY:transparent}>
                                                        a 10px cyan dot marks unread —
                                                        and it stays in the layout when
                                                        read (transparent, not removed),
                                                        so rows never shift
     ListItem "&:hover": rgba(0,229,255,0.03)           a 3% cyan wash on hover
     selected: rgba(0,229,255,0.06)                     6% for the active row
     primary fontWeight: readAt ? 400 : 600             unread is BOLD, not coloured
   ═══════════════════════════════════════════════════════════════════════════ */

body.laser-theme .notif-page {
    /* ⛔ WAS `--page-width-read` (60rem). That is the READING measure — the 60-75 character
       line length for prose — and this page is a LIST of notification rows, each a short
       title, a timestamp and an action. MEASURED at a 1872px viewport: 960px inside a
       1552px main, 62% utilisation with 296px of dead margin on each side, while the rows
       themselves wrapped.
       ⚠️ Same token misuse as payment-settings and the colour editor; the user guide keeps
       the reading measure because it genuinely IS prose (30 paragraphs, longest 449
       characters). The content decides, not the number. */
    width: clamp(var(--page-width-min), var(--page-width-preferred-wide), var(--page-max-width-wide));
    max-width: 100%;
    margin-inline: auto;
    padding-inline: var(--page-gutter);
    padding-block: var(--spacing-3);
}

body.laser-theme .notif-page-header {
    display: flex;
    flex-wrap: wrap;
    align-items: center;
    justify-content: space-between;
    gap: var(--spacing-2);
    margin-bottom: var(--spacing-3);
}

/* ⛔ Gradient removed 2026-09-08 with the rest of the page titles (see laser.css).
   A notifications page title is a page title; it took the same treatment as every other
   `<h1>` the moment the shared rule stopped being a gradient. */
body.laser-theme .notif-page-header h1 {
    color: var(--text-primary-color);
    margin: 0;
}

@media (forced-colors: active) {
    body.laser-theme .notif-page-header h1 {
        background: none;
        -webkit-text-fill-color: currentColor;
        color: CanvasText;
    }
}

body.laser-theme .notif-page-actions {
    display: flex;
    flex-wrap: wrap;
    align-items: center;
    gap: var(--spacing-2);
    /* ⛔ THE ROW HAD A GAP BETWEEN ITS BUTTONS BUT NO SPACE ABOVE THEM, so on the settings
       and routing pages the action row sat flush against the bottom edge of the table it
       acts on — the buttons read as part of the table frame rather than as the form's
       controls. `gap` spaces siblings from each other; it says nothing about the element's
       own relationship to what precedes it. */
    margin-top: var(--spacing-3);
}

body.laser-theme .notif-page-actions form {
    margin: 0;
}

/* The All / Unread pair — the record-tabs treatment, so a filter strip looks the same
   everywhere in the product. */
body.laser-theme .notif-filters {
    display: flex;
    gap: var(--spacing-3);
    border-bottom: 1px solid var(--laser-border-cyan);
    margin-bottom: var(--spacing-3);
}

body.laser-theme .notif-filter {
    padding: 9px 2px;
    border-bottom: 3px solid transparent;
    color: var(--laser-text-secondary);
    font-size: var(--font-size-body-small);
    font-weight: 600;
    text-decoration: none;
    white-space: nowrap;
}

body.laser-theme .notif-filter:hover {
    color: var(--laser-primary);
}

body.laser-theme .notif-filter[aria-current="page"] {
    border-bottom-color: var(--laser-primary);
    color: var(--laser-primary);
}

/* GlassCard: the list is ONE card, and the rows are hairline-separated inside it —
   not 20 individually-bordered boxes, which is what makes a feed read as noise. */
body.laser-theme .notif-list {
    list-style: none;
    margin: 0;
    padding: 0;
    background: var(--laser-glass-sheen);
    -webkit-backdrop-filter: var(--laser-glass-filter);
    backdrop-filter: var(--laser-glass-filter);
    border: 1px solid var(--laser-glass-border);
    /* ⚠️ `--r-card` (12px), NOT `--border-radius-md` (8px). This list IS a card — same
       fill, blur and hairline — so an 8px corner made it the one glass surface drawing a
       different curve from every other. */
    border-radius: var(--r-card);
    /* ★ The top-lit lip and cast shadow every other glass surface carries. The fill and
       blur were already right; the specular edge is what separates "glass" from "flat
       tinted box". */
    box-shadow: var(--laser-glass-edge);
    overflow: hidden;
}

/* ⛔ THE HEADINGS HAD NO SPACING OF THEIR OWN. `.notif-page` is a bare block, so its
   `<h1>` and intro paragraph took UA margins and sat flush against the page chrome above
   and the controls below — reported by the owner as "the titles are not having proper
   margin padding". Every other page in this product gets its heading rhythm from
   `page_header`; these templates predate it, so the spacing is stated here rather than
   rewriting three templates onto a component mid-sweep. */
body.laser-theme .notif-page > h1 {
    margin: 0 0 var(--spacing-1);
}

body.laser-theme .notif-intro,
body.laser-theme .notif-page > p {
    margin: 0 0 var(--spacing-3);
    color: var(--laser-text-secondary);
    max-width: var(--page-width-read);
}

body.laser-theme .notif-page > form {
    margin-top: var(--spacing-3);
}

@supports not ((backdrop-filter: blur(1px)) or (-webkit-backdrop-filter: blur(1px))) {
    body.laser-theme .notif-list { background: var(--laser-bg-card); }
}

body.laser-theme .notif-item {
    display: flex;
    align-items: flex-start;
    gap: var(--spacing-2);
    padding: var(--spacing-3);
    border-bottom: 1px solid var(--laser-border-divider);
    transition: background-color var(--laser-transition-fast);
}

body.laser-theme .notif-item:last-child {
    border-bottom: 0;
}

body.laser-theme .notif-item:hover {
    /* LASER's own 3% — deliberately fainter than a table row's 4%: a feed is read, not
       scanned across, so the hover is an aid rather than a tracking line. */
    background-color: rgba(0, 229, 255, 0.03);
}

/* ⛔ The unread dot occupies its slot whether or not it is visible. LASER renders it
   `color: transparent` when read rather than removing it, so a row does not shift 18px
   sideways the moment it is marked read — the layout stays still while the list changes. */
body.laser-theme .notif-item::before {
    content: "";
    flex: 0 0 auto;
    width: 8px;
    height: 8px;
    margin-top: var(--s-2);
    border-radius: 50%;
    background: transparent;
}

body.laser-theme .notif-item.is-unread::before {
    background: var(--laser-primary);
    box-shadow: 0 0 8px rgba(0, 229, 255, 0.6);
}

body.laser-theme .notif-item-main {
    flex: 1 1 auto;
    min-width: 0;
}

/* Unread is signalled by WEIGHT, not colour (LASER: `fontWeight: readAt ? 400 : 600`).
   Colour alone would fail WCAG 1.4.1, and weight survives greyscale. */
body.laser-theme .notif-item-subject {
    margin: 0 0 2px;
    font-size: var(--font-size-body-small);
    font-weight: 400;
    color: var(--laser-text-primary);
}

body.laser-theme .notif-item.is-unread .notif-item-subject {
    font-weight: 600;
}

body.laser-theme .notif-item-subject a {
    color: inherit;
    text-decoration: none;
}

body.laser-theme .notif-item-subject a:hover {
    color: var(--laser-primary);
    text-decoration: underline;
}

body.laser-theme .notif-item-body {
    margin: 0 0 var(--spacing-1);
    color: var(--laser-text-secondary);
    font-size: var(--font-size-body-small);
    line-height: 1.5;
}

body.laser-theme .notif-item-meta {
    margin: 0;
    color: var(--laser-text-disabled);
    font-size: var(--font-size-caption);
    font-variant-numeric: tabular-nums;
}

body.laser-theme .notif-item form {
    margin: 0;
    flex: 0 0 auto;
}

/* "Mark read" is a per-row action: quiet until hovered, so 20 rows do not present 20
   equally-loud buttons. */
body.laser-theme .btn-link {
    background: none;
    border: 0;
    padding: var(--s-1) var(--s-2);
    border-radius: var(--border-radius-sm);
    color: var(--laser-text-secondary);
    font: inherit;
    font-size: var(--font-size-caption);
    cursor: pointer;
    white-space: nowrap;
}

body.laser-theme .btn-link:hover {
    background: var(--laser-wash-8);
    color: var(--laser-primary);
}

body.laser-theme .notif-pagination {
    display: flex;
    flex-wrap: wrap;
    align-items: center;
    justify-content: center;
    gap: var(--spacing-3);
    margin-top: var(--spacing-3);
    color: var(--laser-text-secondary);
    font-size: var(--font-size-body-small);
}

body.laser-theme .notif-pagination a {
    color: var(--laser-primary);
    text-decoration: none;
}

body.laser-theme .notif-pagination a:hover {
    text-decoration: underline;
}

/* An empty inbox is a FINDING, not an error — body text in the secondary colour, inside
   the same card the list would occupy, so the page does not visibly collapse. */
body.laser-theme .notif-empty {
    display: flex;
    flex-direction: column;
    align-items: center;
    justify-content: center;
    gap: var(--spacing-2);
    padding: var(--spacing-7) var(--spacing-3);
    background: var(--laser-glass-sheen);
    -webkit-backdrop-filter: var(--laser-glass-filter);
    backdrop-filter: var(--laser-glass-filter);
    border: 1px solid var(--laser-glass-border);
    border-radius: var(--border-radius-md);
    text-align: center;
}

body.laser-theme .notif-empty p {
    margin: 0;
    color: var(--laser-text-secondary);
}

body.laser-theme .notif-empty a {
    color: var(--laser-primary);
}

/* ─── Settings + routing pages ─────────────────────────────────────────────── */

body.laser-theme .notif-table {
    width: 100%;
    border-collapse: collapse;
    background: var(--laser-glass-sheen);
    -webkit-backdrop-filter: var(--laser-glass-filter);
    backdrop-filter: var(--laser-glass-filter);
    border: 1px solid var(--laser-glass-border);
    border-radius: var(--border-radius-md);
    overflow: hidden;
}

body.laser-theme .notif-table-wrap {
    overflow-x: auto;     /* the TABLE scrolls, never the page */
}

/* ⛔ `min-height: 40px` removed 2026-09-08 — a third button height in the product.
   `--h-button` is 32px and the touch floor is raised to 44px by `_09-a11y-and-touch.css`
   under `(pointer: coarse), (max-width: 640px)`, so a hardcoded 40px was neither the
   desktop size nor the touch size: it just made these two buttons taller than every
   other button on every other page. */

/* ─── Portfolio tabs: chips, not full-width slabs ───────────────────────────
   "All Portfolios" rendered as a solid cyan bar spanning the whole filter column. The
   fill is deliberate (it is the ACTIVE tab), but a chip that stretches edge-to-edge reads
   as a pressed button or a banner, not as one option among several. `.portfolio-tabs` is
   a column here, so each child stretched on the cross axis.
   ⚠️ Same root cause as the 192px search box: a cross-axis stretch nobody asked for. */
body.laser-theme .portfolio-tabs {
    display: flex;
    flex-wrap: wrap;
    align-items: flex-start;      /* not `stretch` — a chip is content-width */
    gap: var(--spacing-1);
}

body.laser-theme .portfolio-tab {
    background: transparent;
    border: 1px solid var(--laser-border-cyan);
    color: var(--laser-text-secondary);
}

/* ⚠️ Pill shape ONLY in the horizontal strip. `.left-panel .portfolio-tab` is deliberately
   `width: 100%` + `justify-content: space-between` — inside the narrow filter panel these
   are full-width LIST ROWS with the count pushed to the right edge, which is the correct
   design for a vertical list. Rounding them to 999px there would make a stack of pills
   pretending to be a menu. */
body.laser-theme .portfolio-tabs:not(.left-panel .portfolio-tabs) > .portfolio-tab {
    flex: 0 1 auto;
    align-self: flex-start;
    border-radius: 999px;
}

/* The panel's list rows: a squarer radius, and a left rail on the active one instead of a
   full fill — the same treatment the sidebar nav used, since this IS a nav list. */
body.laser-theme .left-panel .portfolio-tab {
    border-radius: var(--border-radius-sm);
}

body.laser-theme .portfolio-tab:hover {
    border-color: var(--laser-primary);
    background: var(--laser-wash-8);
    color: var(--laser-primary);
}

/* The active tab keeps its solid fill — it is the current selection — but as a chip. */
body.laser-theme .portfolio-tab.active,
body.laser-theme .portfolio-tab[aria-current="page"] {
    background: var(--laser-primary);
    border-color: var(--laser-primary);
    color: var(--laser-bg-page);
}

/* In the LIST, the active row takes the nav treatment (8% wash + a 3px cyan rail) rather
   than a solid fill: a full-width saturated bar dominates a narrow panel and reads as a
   banner. Same pattern as `.sidebar-nav a[aria-current]` and MuiListItemButton.Mui-selected. */
body.laser-theme .left-panel .portfolio-tab.active {
    background: var(--laser-wash-8);
    border-color: var(--laser-border-cyan);
    border-left: 3px solid var(--laser-primary);
    color: var(--laser-primary);
    font-weight: 600;
}

body.laser-theme .portfolio-tab .tab-count {
    background: rgba(10, 10, 10, 0.18);
    border-radius: 999px;
    margin-left: var(--s-2);
    padding: 0 var(--s-2);
    font-variant-numeric: tabular-nums;
}

/* A stacked filter column: the label above its control, one row per filter. Stated so a
   column container never inherits the row treatment's alignment. */
body.laser-theme .filter-row {
    row-gap: var(--spacing-2);
    /* ⛔ The filter row had `gap` for its OWN children and nothing separating it from
       what follows, so on `/accounts/users/` the search + role controls sat flush
       against the table header beneath them — the two read as one crowded block.
       `gap` spaces items INSIDE a flex container; it does nothing below it.
       ⚠️ `margin-bottom` on the row rather than `margin-top` on the table: the table
       is a shared component used in many places that do NOT follow a filter row, so
       the spacing belongs to the element that creates the need for it. */
    margin-bottom: var(--spacing-2);
}

body.laser-theme .filter-row > .btn-filter {
    align-self: flex-start;
}

/* ─── The remaining stale-fill buttons ─────────────────────────────────────
   `.btn-report` on the dashboard still painted `--info-color` because a page-level rule
   set the background directly rather than through the class. Caught by reading computed
   style on the live page (2026-09-03), not by grepping. */
body.laser-theme .staff-tools-card .btn-action,
body.laser-theme .staff-tools-card .btn-report,
body.laser-theme .admin-links .btn-action,
body.laser-theme .admin-links a {
    background-color: transparent;
    border: 1px solid var(--laser-border-cyan);
    color: var(--laser-primary);
}

body.laser-theme .staff-tools-card .btn-action:hover,
body.laser-theme .staff-tools-card .btn-report:hover,
body.laser-theme .admin-links .btn-action:hover,
body.laser-theme .admin-links a:hover {
    background-color: var(--laser-primary);
    border-color: var(--laser-primary);
    color: var(--laser-bg-page);
    box-shadow: var(--laser-glow-subtle);
}

/* ─── SC 1.4.11: a form control's boundary must be PERCEIVABLE ───────────────

   #2085, found by measuring every pair rather than sampling: an input's extent was
   conveyed by nothing above the 3.0 non-text bar —

     input fill   vs page ground   1.08:1
     input border vs page ground   1.60:1   (rgba(0,229,255,0.2))

   so a user could not see where a field was until it took focus. WCAG SC 1.4.11 applies
   to "visual information required to identify user interface components", which is
   exactly a text field's outline.

   `--laser-border-control` (0.5 alpha) measures **3.72:1** over the card ground. Applied
   to every control that draws its own edge. ⚠️ Deliberately NOT applied to card edges or
   dividers: those are decorative by design and raising them would turn a quiet surface
   into a wireframe.
   -------------------------------------------------------------------------- */

body.laser-theme input[type="text"],
body.laser-theme input[type="email"],
body.laser-theme input[type="password"],
body.laser-theme input[type="number"],
body.laser-theme input[type="tel"],
body.laser-theme input[type="date"],
body.laser-theme input[type="search"],
body.laser-theme select,
body.laser-theme textarea,
body.laser-theme .filter-input,
body.laser-theme .filter-select,
body.laser-theme .csr-select,
body.laser-theme .report-date-input,
body.laser-theme .status-select {
    border-color: var(--laser-border-control);
}

/* A checkbox / radio is a control too, and its box is the whole affordance. */
body.laser-theme input[type="checkbox"],
body.laser-theme input[type="radio"] {
    accent-color: var(--laser-primary);
    /* `accent-color` lets the UA draw a native control in the brand hue — it keeps the
       platform's own focus ring and checked state, which a hand-styled box loses. */
}

/* A BUTTON's boundary matters for the same reason: an outlined button whose edge is
   invisible is a button whose extent is unknown. Filled buttons are exempt — their fill
   already clears 3.0 against the ground. */
body.laser-theme .btn-action,
body.laser-theme .btn-report,
body.laser-theme .btn-edit,
body.laser-theme .btn-filter,
body.laser-theme .btn-clear,
body.laser-theme .btn-secondary,
body.laser-theme .btn-table-action,
body.laser-theme .laser-btn-outline,
body.laser-theme .topbar-pill:hover {
    border-color: var(--laser-border-control);
}

/* ─── Self-describing tool cards (#2085) ────────────────────────────────────
   Replaces seven hover-only `title=` explanations with visible second lines. A `title`
   is invisible on touch, unreachable by keyboard, and appears after ~1s of hover — so an
   explanation living there is an explanation most users never read. The repo already
   records this on csr_account_detail.html: "Visible, not a tooltip — a warning nobody
   hovers is unread."
   Two lines per control means these stop being buttons-in-a-grid and become a menu, which
   is what they always were. */
body.laser-theme .admin-links-described {
    display: grid;
    grid-template-columns: repeat(auto-fit, minmax(min(240px, 100%), 1fr));
    gap: var(--spacing-2);
}

/* ==========================================================================
   STAFF TOOLS — every tool is a KPI card (owner ruling, 2026-09-09)
   ==========================================================================
   "the kpi card kind of stuff design is needed for the buttons and for the dsc
   portal that itself is card is should be the label and below that the other kpi
   card of its button".

   So: each tool reads as a KPI card, and DSC Portal is a card that CONTAINS its
   five destination cards — a labelled group, not a link list.

   ⛔ These are `<a>`s carrying `.btn-action`, a class used as the secondary-action
   button in 97 places. The button shape is overridden HERE rather than by changing
   the markup's class, because `.btn-action` is what gives these anchors their focus
   ring, disabled handling and touch target — dropping it to restyle them would
   silently lose all three on the one surface an admin uses most.

   ⛔ The KPI shape is taken from `docs/design/samples/_tokens.css` `.kpi` — the
   approved sample — NOT invented: a hairline box at `--r-card`, `--s-4` of padding,
   a column with the label above and quiet supporting text below. Its `.kpi-label`
   is uppercase caption-sized and muted, `.kpi-value` is the large line. On a tool
   tile the NAME is the value (it is what you read first and click) and the
   description is the delta line, so the roles map without a new component. */

body.laser-theme .admin-links-described > .btn-action {
    display: flex;
    flex-direction: column;
    align-items: flex-start;
    justify-content: flex-start;
    gap: var(--spacing-1);
    height: auto;
    min-height: 84px;
    /* `--s-4` is the sample's KPI padding; the old value was a button's `2/3`. */
    padding: var(--s-4);
    text-align: left;
    white-space: normal;
    border: 1px solid var(--laser-glass-border);
    border-radius: var(--r-card);
    background: var(--laser-glass-sheen);
    -webkit-backdrop-filter: var(--laser-glass-filter);
    backdrop-filter: var(--laser-glass-filter);
    box-shadow: var(--laser-glass-edge);
    transition: border-color var(--laser-transition), box-shadow var(--laser-transition);
}

/* ⛔ THE FILL DOES NOT CHANGE ON HOVER, and that is a fix, not a preference. The
   previous tile flooded with cyan on hover while `.tool-desc` stayed at 75%
   `currentColor` — the owner reported the text became unreadable mid-hover. Moving
   the feedback to the BORDER and the shadow keeps every foreground contrast ratio
   identical between the resting and hovered states. */
body.laser-theme .admin-links-described > .btn-action:hover,
body.laser-theme .admin-links-described > .btn-action:focus-visible {
    background: var(--laser-glass-sheen);
    border-color: var(--laser-primary);
    box-shadow: var(--laser-glow);
    /* ⛔ THE COLOUR RESET IS THE LOAD-BEARING LINE. Two older rules —
       `_12-theme-nav.css:701` and `_13-theme-pages.css:644` — set
       `color: var(--laser-bg-page)` on `.btn-action:hover`. That is NEAR-BLACK, and it
       was correct for the button these used to be: a cyan fill needs dark text on it.

       These are KPI cards now and the fill no longer changes, so that inherited black
       landed on an unchanged translucent card and the description went unreadable on
       hover — reported by the owner. Resetting the background without the colour fixed
       half the rule and left the half that actually broke the text.

       ⚠️ `.tool-desc` takes `currentColor` at 75% opacity, so pinning the colour here
       fixes BOTH lines at once; giving the description its own hover colour would be a
       second place to keep in sync. */
    color: var(--laser-text-primary);
}

/* The tool name is the KPI's value line: the thing read first and clicked. */
body.laser-theme .tool-name {
    font-size: var(--font-size-section);
    font-weight: 600;
    line-height: 1.2;
    letter-spacing: -0.01em;
    color: var(--laser-text-primary);
}

/* The description is the KPI's delta line — supporting, never competing.
   Inherits `currentColor` at reduced opacity so it stays legible on every state. */
body.laser-theme .tool-desc {
    font-size: var(--font-size-caption);
    font-weight: 400;
    line-height: 1.4;
    opacity: 0.75;
}

/* ─── DSC Portal: a labelled card CONTAINING its own KPI cards ─────────────
   The group is the outer card. Its label sits at the top, and the five
   destinations below it are full KPI cards in their own right rather than a row
   of small links — which is what the owner asked for, and also fixes a measured
   defect: those five were 20px-tall text links, less than half the 44px touch
   floor, and two of them (settlement approvals, document review) reach money
   surfaces. */
body.laser-theme .admin-links-described > .btn-action-group {
    grid-column: 1 / -1;
    display: flex;
    flex-direction: column;
    align-items: stretch;
    gap: var(--s-3);
    /* The outer card is a quieter ground than its children, so the nesting reads
       as containment rather than as two competing card layers. */
    background: rgba(255, 255, 255, 0.03);
}

/* The group's label — the sample's `.kpi-label`: uppercase, caption-sized, muted.
   It names the subsystem; the cards below it are the actions. */
/* ⛔ THE LABEL MUST NOT LOOK LIKE A FIELD. `.tool-primary` is an `<a>` inside a
   `.btn-action` group, so it picked up the tile's border, radius and glass fill and
   rendered as a bordered box around the words "DSC PORTAL" — a heading that reads as
   an empty input. The group's own card is the only box here; its label is text. */
body.laser-theme .btn-action-group .tool-primary {
    display: flex;
    flex-direction: column;
    gap: 2px;
    text-decoration: none;
    color: inherit;
    border: 0;
    border-radius: 0;
    background: none;
    -webkit-backdrop-filter: none;
    backdrop-filter: none;
    box-shadow: none;
    padding: 0;
    min-height: 0;
}

body.laser-theme .btn-action-group .tool-primary .tool-name {
    font-size: var(--font-size-caption);
    font-weight: 600;
    text-transform: uppercase;
    letter-spacing: 0.08em;
    color: var(--laser-text-secondary);
}

/* ⛔ A GRID, NOT A WRAPPED FLEX ROW. As flex items these five sized to their own
   text, so "Pipeline" and "Assign accounts" came out different widths and the row
   read as ragged links rather than as a set of cards. `auto-fit` + `minmax` gives
   them one shared width and lets the count per row follow the container. */
body.laser-theme .btn-action-group .tool-links {
    display: grid;
    grid-template-columns: repeat(auto-fit, minmax(min(180px, 100%), 1fr));
    gap: var(--s-3);
    margin: 0;
    padding: 0;
    border-top: 0;
}

/* Each destination is its own KPI card, matching the top-level tiles at a smaller
   scale — same border, same radius, same hover contract. */
body.laser-theme .btn-action-group .tool-links a {
    display: flex;
    align-items: center;
    /* 44px touch floor: these were 20px text links, and two reach money surfaces. */
    min-height: 44px;
    padding: var(--spacing-2) var(--s-3);
    border: 1px solid var(--laser-glass-border);
    border-radius: var(--r-card);
    background: var(--laser-glass-sheen);
    color: var(--laser-text-primary);
    font-size: var(--font-size-body-small);
    font-weight: 500;
    line-height: 1.3;
    text-decoration: none;
    transition: border-color var(--laser-transition), box-shadow var(--laser-transition);
}

body.laser-theme .btn-action-group .tool-links a:hover,
body.laser-theme .btn-action-group .tool-links a:focus-visible {
    border-color: var(--laser-primary);
    box-shadow: var(--laser-glow);
    /* ⛔ NOT `text-decoration: underline`. These are card surfaces now, not inline
       links; an underline inside a bordered box reads as a defect. */
    text-decoration: none;
}

/* ==========================================================================
   List toolbar — the filter strip ABOVE a list (#2168)
   ==========================================================================
   ⛔ Filters go ABOVE the table, not beside it. The previous left-panel/right-panel
   split cost the table roughly a third of the page width and pushed the search box
   below the sidebar's own fold, so the most-used control sat furthest from the eye.

   ★ The sidebar-vs-toolbar choice turns on FILTER COUNT: a sidebar earns its
   horizontal cost at ~8+ filter dimensions, where nesting and always-visible
   multi-select pay for themselves. These pages carry four. Below that threshold a
   horizontal bar keeps the filters adjacent to the data they change and returns the
   full width to the table.

   ⚠️ `flex-wrap` on the field row, not a media query: the bar has to survive a
   caller adding a fifth field without a new breakpoint. Each field carries its own
   `min-width` so wrapping produces whole controls rather than slivers. */
body.laser-theme .laser-toolbar {
    display: flex;
    flex-wrap: wrap;
    align-items: flex-end;
    gap: var(--spacing-3);
    padding: var(--spacing-3);
    margin-bottom: var(--spacing-3);
    background: var(--laser-glass-sheen);
    border: 1px solid var(--laser-glass-border);
    border-radius: var(--border-radius-md);
}

body.laser-theme .laser-toolbar-fields {
    display: flex;
    flex-wrap: wrap;
    align-items: flex-end;
    gap: var(--spacing-3);
    flex: 1 1 auto;
    min-width: 0;
}

body.laser-theme .laser-toolbar-actions {
    display: flex;
    align-items: flex-end;
    gap: var(--spacing-2);
    flex: 0 0 auto;
}

/* One labelled control. The label is always rendered and never a placeholder:
   a placeholder disappears the moment a value is chosen, so the operator loses
   the name of the thing they filtered by (WCAG 3.3.2 — labels are persistent). */
body.laser-theme .laser-toolbar-field {
    display: flex;
    flex-direction: column;
    gap: var(--s-1);
    /* `min(220px, 100%)` — the inner min() stops the track overflowing a viewport
       narrower than the floor, the same guard the card grids use. */
    flex: 1 1 min(220px, 100%);
    min-width: 0;
}

body.laser-theme .laser-toolbar-field-wide {
    flex: 2 1 min(280px, 100%);
}

body.laser-theme .laser-toolbar-label {
    font-size: var(--font-size-caption);
    font-weight: 600;
    letter-spacing: 0.04em;
    text-transform: uppercase;
    color: var(--text-secondary-color);
}

body.laser-theme .laser-toolbar-field select,
body.laser-theme .laser-toolbar-field input[type="text"],
body.laser-theme .laser-toolbar-field input[type="search"] {
    width: 100%;
    /* 44px min target: the toolbar is the one strip on the page that is touched on
       every visit, so it holds the full touch floor even where the table is dense. */
    min-height: var(--h-touch);
    padding: 0 var(--spacing-3);
    color: var(--text-primary-color);
    background: var(--surface-color);
    border: 1px solid var(--border-color);
    border-radius: var(--border-radius-sm);
    font-size: var(--font-size-body-small);
}

body.laser-theme .laser-toolbar-field select:focus-visible,
body.laser-theme .laser-toolbar-field input:focus-visible {
    outline: 2px solid var(--primary-color);
    outline-offset: 1px;
}

/* Stat tiles: a row ACROSS THE TOP, above the toolbar. They were a 2x2 block in the
   old left panel, which is why the table started a third of the way down the page. */
body.laser-theme .laser-stat-row {
    display: grid;
    grid-template-columns: repeat(auto-fit, minmax(min(180px, 100%), 1fr));
    gap: var(--spacing-3);
    margin-bottom: var(--spacing-3);
}

/* The stacked page: stats, then toolbar, then the table at full width. */
body.laser-theme .laser-list-page {
    display: flex;
    flex-direction: column;
    min-width: 0;
}

/* ⚠️ Table cells were `padding: 5px 6px`, so the first column read as stuck to the
   border -- 6px is not a gutter, it is a rounding error.

   ★ 6px vertical / 16px horizontal is the laser-platform value, not a guess: MUI's
   `size="small"` TableCell resolves to `6px 16px`, and laser's theme overrides
   MuiTableCell for BORDERS ONLY, so the whole app runs on that default. Matching it
   keeps the two products' tables visually identical.

   ⛔ The first column gets NO special gutter, deliberately. Laser gives column one
   the same 16px as every other column -- a wider first cell would be a NEW decision
   rather than mirroring the reference, and it misaligns the column against a sticky
   header or a checkbox cell. */
body.laser-theme .laser-list-page table th,
body.laser-theme .laser-list-page table td {
    padding: var(--cell-pad-y) var(--cell-pad-x);
}

/* ─── `.laser-table` — the nine DSC pages' table ──────────────────────────────
   ⛔ MEASURED 2026-09-04: `.laser-table` had NO styling at all. Only
   `.laser-table-scroll` existed, so every DSC table inherited the bare reset and
   rendered with `padding: 0` on every `th` and `td` — columns touching the frame and
   each other, 45px rows against the CSR pages' 57px, and a near-black header band.
   Side by side with `csr_dashboard` the DSC pages read as a different, unfinished
   product.

   ⚠️ The padding rule DID exist — as `body.laser-theme .laser-list-page table th/td`
   just above. But 0 of the 9 DSC templates carry `.laser-list-page`, so not one of
   them qualified. That is the whole defect: a correct rule behind a class nobody used.

   ★ Values are the CSR baseline verbatim (`6px 16px`, 12px/600/uppercase header on
   `--surface-alt-color`) so the two systems are indistinguishable in a row of tabs.
   `6px 16px` is laser-platform's own MUI `size="small"` value. */
body.laser-theme .laser-table {
    width: 100%;
    border-collapse: collapse;
}

body.laser-theme .laser-table th,
body.laser-theme .laser-table td {
    padding: var(--cell-pad-y) var(--cell-pad-x);
    text-align: left;
    vertical-align: middle;
    border-bottom: 1px solid var(--border-color);
}

body.laser-theme .laser-table thead th {
    /* Matches `.accounts-table th`: the header is a LABEL, not data — smaller, heavier,
       upper-case, on the raised surface so it separates from the rows without a rule. */
    font-size: var(--font-size-caption);
    font-weight: 600;
    text-transform: uppercase;
    letter-spacing: 0.04em;
    color: var(--text-secondary-color);
    background: var(--surface-alt-color);
    white-space: nowrap;
}

body.laser-theme .laser-table tbody tr:hover {
    background: var(--laser-wash-4, rgba(0, 229, 255, 0.04));
}

body.laser-theme .laser-table tbody tr:last-child td {
    border-bottom: 0;
}

/* ★ A money/count column right-aligns so digits line up on the decimal; its HEADER
   must follow, or the label floats away from the numbers it describes. `.numeric`
   already exists globally — this only makes sure the header inherits it inside a
   `.laser-table`, which is where the mismatch you can see comes from. */
body.laser-theme .laser-table th.numeric,
body.laser-theme .laser-table td.numeric {
    text-align: right;
    font-variant-numeric: tabular-nums;
}

/* A cell holding a machine value — a username, a slug, an enum — is capitalised for
   display only. The stored value is untouched. */
body.laser-theme .laser-table td.cap-first {
    text-transform: capitalize;
}

/* Wide content scrolls INSIDE its own container so the page body never scrolls
   sideways. `overflow-x: auto` clips both axes, so nothing positioned (a row menu,
   a tooltip) may live inside this wrapper — that is the #2167 defect. */
body.laser-theme .laser-table-scroll {
    overflow-x: auto;
    min-width: 0;
}

@media (max-width: 720px) {
    body.laser-theme .laser-toolbar {
        /* Actions go full-width at the end rather than squeezing beside a field. */
        flex-direction: column;
        align-items: stretch;
    }

    body.laser-theme .laser-toolbar-actions .laser-btn {
        flex: 1 1 auto;
    }

}

/* ⛔ THE GROUP IS NOT A BUTTON, SO IT DOES NOT HOVER (owner, 2026-09-10).
   `.btn-action-group` carries `.btn-action` for its card shape, which meant the rule
   above flushed the WHOLE DSC Portal group cyan whenever the pointer crossed any part
   of it — including the label, which is a heading rather than a control.

   ⚠️ A hover on a container is a lie about what is clickable. The group holds several
   real links (`.tool-primary` and each `.tool-links` item); lighting up the container
   says "this is one target" while the actual targets are inside it, and the reader
   cannot tell which one the pointer would activate.

   ★ The links inside KEEP their own hover — the affordance moves to the thing that is
   actually clickable, which is where it belonged. Nothing here disables that. */
body.laser-theme .staff-tools-card .btn-action-group:hover,
body.laser-theme .admin-links .btn-action-group:hover,
body.laser-theme .btn-action-group:hover {
    /* ⚠️ These are `.btn-action`'s OWN RESTING VALUES (`_12-theme-nav.css:717-719`),
       repeated so hovering changes nothing at all. Setting `transparent` / `inherit`
       instead would have been a guess that happened to look similar. */
    background-color: transparent;
    border-color: var(--laser-border-cyan);
    color: var(--laser-primary);
    box-shadow: none;
    transform: none;
}

/* ⛔ AND THE LABEL ITSELF. `.tool-primary` is an `<a>` inside `.admin-links`, so the
   `.admin-links a:hover` arm of the rule above matched it directly and painted a SOLID
   CYAN BAR across "DSC PORTAL / Reps, firms and account assignment" — measured in the
   browser, and the only rule matching that element.

   ⚠️ Fixing the CONTAINER was not enough, which is the whole lesson here: the group and
   its label are two elements, and a container-level override says nothing about a child
   that matches a rule on its own. The first fix looked right in the stylesheet and
   changed nothing on screen.

   ★ The heading is not a control. It happens to be a link so the whole subsystem is
   reachable, but the five cards below it are the actions — so it takes no hover paint,
   only the pointer. */
body.laser-theme .admin-links .btn-action-group .tool-primary:hover,
body.laser-theme .staff-tools-card .btn-action-group .tool-primary:hover,
body.laser-theme .btn-action-group .tool-primary:hover {
    background: none;
    background-color: transparent;
    border-color: transparent;
    color: inherit;
    box-shadow: none;
    transform: none;
}
